Updated! I added a v2 that includes lightbox modal support for video and images. Scroll to very bottom to get it!
Inspiration
- WordPress.com Help Center
- BetterDocs
- HelpScout/Crisp/Etc
Goal
- Create a custom Help Center that pops up in the bottom right of the viewport on WordPress sites. Triggered by menu item with
.open-help-center
class.
Functionality
- AJAX-powered Help Center loads posts from “help” category
- Clicking a post title loads full content into the popup
- Includes search box (appears only when popup is open or user clicks “Go Back”)
- Two action buttons per article: “Go Back” and “Open support page”
Frontend Features
- Responsive popup (400×700 desktop, full width mobile)
- Injected via wp_footer
- Uses jQuery, wp_localize_script, AJAX and nonce verification
- Minimal CSS for layout only — theme styles inherit everything else
- Autofocus search input when opening popup
- Add “Back to top” at bottom off every article that is loaded
Integration
- All PHP goes in a single Snippets plugin block
- CSS goes in Customizer → Additional CSS
Version 1
Copy/Paste this PHP Function Snippet:
// Help Center Popup - Optimized with ESC key and accessibility
add_action('wp_enqueue_scripts', function () {
wp_enqueue_script('jquery');
$js = <<<EOT
jQuery(document).ready(function($) {
const popup = $('#help-center-popup');
const searchWrap = $('#help-center-search');
const searchInput = $('#help-center-search-input');
const resultsWrap = $('#help-center-results');
let lastFocusedElement;
let searchTimeout;
function loadArticles(search = '') {
$.ajax({
url: helpCenter.ajax_url,
method: 'POST',
data: {
action: search ? 'search_help_articles' : 'get_popular_help_articles',
nonce: helpCenter.nonce,
search
},
success: (res) => {
if (res.success) {
resultsWrap.html(res.data);
searchWrap.show();
searchInput.focus();
}
}
});
}
function loadArticle(postId) {
$.ajax({
url: helpCenter.ajax_url,
method: 'POST',
data: {
action: 'load_help_article',
nonce: helpCenter.nonce,
post_id: postId
},
success: (res) => {
if (res.success) {
resultsWrap.html(res.data);
searchWrap.hide();
}
}
});
}
$(document).on('click', '.open-help-center a', (e) => {
e.preventDefault();
lastFocusedElement = $(document.activeElement); // Store last focused element
popup.fadeIn(200).attr('aria-hidden', 'false');
loadArticles();
});
searchInput.on('input', function() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const val = $(this).val().trim();
val.length >= 3 ? loadArticles(val) : loadArticles();
}, 300);
});
popup.on('click', '.help-article-title', function() {
loadArticle($(this).data('post-id'));
});
popup.on('click', '#help-go-back', () => loadArticles());
popup.on('click', '#help-center-close', () => {
popup.fadeOut(200).attr('aria-hidden', 'true');
lastFocusedElement.focus(); // Return focus to last element
});
popup.on('click', '.back-to-top', (e) => {
e.preventDefault();
popup.animate({ scrollTop: 0 }, 400);
});
// Close popup with ESC key
$(document).on('keydown', (e) => {
if (e.keyCode === 27 && popup.is(':visible')) { // ESC key
popup.fadeOut(200).attr('aria-hidden', 'true');
lastFocusedElement.focus(); // Return focus to last element
}
});
});
EOT;
wp_add_inline_script('jquery', $js);
wp_localize_script('jquery', 'helpCenter', [
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('help_center_nonce')
]);
});
add_action('wp_footer', function () {
?>
<div id="help-center-popup" class="help-center-popup" role="dialog" aria-modal="true" aria-labelledby="help-center-title" aria-hidden="true">
<div class="help-center-header">
<strong id="help-center-title">Help Center</strong>
<button id="help-center-close" class="help-center-close" aria-label="Close Help Center">×</button>
</div>
<div id="help-center-search" class="help-center-search">
<input type="text" id="help-center-search-input" placeholder="Search for help" aria-label="Search help articles">
</div>
<div id="help-center-results" class="help-center-results" tabindex="0"></div>
</div>
<?php
});
add_filter('nav_menu_css_class', function ($classes) {
if (in_array('open-help-center', $classes)) {
$classes[] = 'open-help-center';
}
return $classes;
}, 10, 1);
function render_help_articles($query) {
if (!$query->have_posts()) {
return '<p>No help articles found.</p>';
}
ob_start();
while ($query->have_posts()) {
$query->the_post();
printf(
'<div class="help-article"><h3 class="help-article-title" data-post-id="%d">%s</h3></div>',
get_the_ID(),
esc_html(get_the_title())
);
}
wp_reset_postdata();
return ob_get_clean();
}
add_action('wp_ajax_get_popular_help_articles', 'load_help_articles');
add_action('wp_ajax_nopriv_get_popular_help_articles', 'load_help_articles');
function load_help_articles() {
check_ajax_referer('help_center_nonce', 'nonce');
$query = new WP_Query([
'post_type' => 'post',
'category_name' => 'help',
'posts_per_page' => 6,
'orderby' => 'date',
'order' => 'DESC'
]);
wp_send_json_success(render_help_articles($query));
}
add_action('wp_ajax_search_help_articles', 'search_help_articles');
add_action('wp_ajax_nopriv_search_help_articles', 'search_help_articles');
function search_help_articles() {
check_ajax_referer('help_center_nonce', 'nonce');
$search = sanitize_text_field($_POST['search'] ?? '');
$query = new WP_Query([
'post_type' => 'post', // Fixed typo from 'content' to 'post'
'category_name' => 'help',
'posts_per_page' => 6,
's' => $search
]);
wp_send_json_success(render_help_articles($query));
}
add_action('wp_ajax_load_help_article', 'load_help_article');
add_action('wp_ajax_nopriv_load_help_article', 'load_help_article');
function load_help_article() {
check_ajax_referer('help_center_nonce', 'nonce');
$post_id = intval($_POST['post_id']);
$post = get_post($post_id);
if (!$post || !has_category('help', $post)) {
wp_send_json_error('Invalid article');
}
ob_start();
?>
<div class="help-article-controls">
<button id="help-go-back" class="help-go-back" aria-label="Go back to article list">Go Back</button>
<a href="<?php echo esc_url(get_permalink($post)); ?>" target="_blank" class="help-open-page" aria-label="Open support page in new tab">Open Support Page</a>
</div>
<h2><?php echo esc_html(get_the_title($post)); ?></h2>
<?php echo apply_filters('the_content', $post->post_content); ?>
<div id="sticky-back-to-top">
<a class="back-to-top" href="#help-center-popup" role="button" aria-label="Scroll back to top">Back to top ↑</a>
</div>
<?php
wp_send_json_success(ob_get_clean());
}
Copy / Paste this CSS:
#help-center-popup {
position: fixed;
bottom: 0;
right: 0;
width: 400px;
max-width: 100%;
height: 55vh;
max-height: 90vh;
border: 1px solid;
box-shadow: 0 0 6px rgba(0,0,0,0.1);
z-index: 10000;
display: none;
flex-direction: column;
overflow-y: auto;
padding: 0 15px 0 15px;
background: inherit;
transition: opacity 0.2s ease;
}
.help-center-header {
display: flex;
justify-content: space-between;
align-items: center;
z-index: 10;
background: inherit;
}
.help-center-close {
background: none;
border: none;
font-size: 1.5em;
cursor: pointer;
line-height: 1;
}
#help-center-search {
display: none;
position: sticky;
top: 40px;
z-index: 9;
background: inherit;
padding-bottom: 10px;
}
#help-center-search-input {
width: 100%;
border: 1px solid;
font-size: inherit;
background: inherit;
color: inherit;
padding: 3px;
}
.help-center-results {
display: flex;
flex-direction: column;
/* gap: 1em; */
}
.help-article-title {
cursor: pointer;
margin: 0;
padding: 0;
scroll-margin-top: 60px;
}
.help-article-controls {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.help-go-back,
.help-open-page {
flex: 1;
text-align: center;
border: 1px solid;
background: inherit;
color: inherit;
font-size: small;
padding: 8px;
text-decoration: none;
cursor: pointer;
}
#sticky-back-to-top {
position: sticky;
bottom: 0;
z-index: 8;
text-align: center;
padding: 10px;
background: #000;
}
.back-to-top {
color: inherit;
text-decoration: none;
cursor: pointer;
}
@media (max-width: 600px) {
#help-center-popup {
width: 100%;
right: 0;
bottom: 0;
border-radius: 0;
}
}
Updated v2
~266 lines of code PHP Function:
add_action('wp_enqueue_scripts', function () {
wp_enqueue_script('jquery');
$js = <<<EOT
jQuery(document).ready(function($) {
const popup = $('#help-center-popup');
const lightbox = $('#help-center-lightbox');
const lightboxContent = $('.lightbox-content');
const searchWrap = $('#help-center-search');
const searchInput = $('#help-center-search-input');
const resultsWrap = $('#help-center-results');
let lastFocusedElement;
let searchTimeout;
function loadArticles(search = '') {
$.ajax({
url: helpCenter.ajax_url,
method: 'POST',
data: {
action: search ? 'search_help_articles' : 'get_popular_help_articles',
nonce: helpCenter.nonce,
search
},
success: (res) => {
if (res.success) {
resultsWrap.html(res.data);
searchWrap.show();
searchInput.focus();
}
}
});
}
function loadArticle(postId) {
$.ajax({
url: helpCenter.ajax_url,
method: 'POST',
data: {
action: 'load_help_article',
nonce: helpCenter.nonce,
post_id: postId
},
success: (res) => {
if (res.success) {
resultsWrap.html(res.data);
searchWrap.hide();
}
}
});
}
// Open help center popup
$(document).on('click', '.open-help-center a', (e) => {
e.preventDefault();
lastFocusedElement = $(document.activeElement);
popup.fadeIn(200).attr('aria-hidden', 'false');
loadArticles();
});
// Search input handling
searchInput.on('input', function() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
const val = $(this).val().trim();
val.length >= 3 ? loadArticles(val) : loadArticles();
}, 300);
});
// Load single article
popup.on('click', '.help-article-title', function() {
loadArticle($(this).data('post-id'));
});
// Go back to article list
popup.on('click', '#help-go-back', () => loadArticles());
// Close help center popup
popup.on('click', '#help-center-close', () => {
popup.fadeOut(200).attr('aria-hidden', 'true');
lastFocusedElement.focus();
});
// Scroll to top
popup.on('click', '.back-to-top', (e) => {
e.preventDefault();
popup.animate({ scrollTop: 0 }, 400);
});
// Open lightbox when clicking images or videos
resultsWrap.on('click', 'img, video', function(e) {
e.preventDefault();
const src = $(this).attr('src');
const tag = $(this).prop('tagName').toLowerCase();
let content;
if (tag === 'img') {
content = $('<img>').attr('src', src);
} else if (tag === 'video') {
content = $('<video>').attr({
src: src,
controls: true,
autoplay: true
});
}
lightboxContent.empty().append(content);
lightbox.fadeIn(200).attr('aria-hidden', 'false');
$('#lightbox-close').focus();
});
// Close lightbox
lightbox.on('click', '#lightbox-close', () => {
lightbox.fadeOut(200).attr('aria-hidden', 'true');
lightboxContent.empty();
resultsWrap.focus();
});
// Close lightbox or popup with ESC key
$(document).on('keydown', (e) => {
if (e.keyCode === 27) { // ESC key
if (lightbox.is(':visible')) {
lightbox.fadeOut(200).attr('aria-hidden', 'true');
lightboxContent.empty();
resultsWrap.focus();
} else if (popup.is(':visible')) {
popup.fadeOut(200).attr('aria-hidden', 'true');
lastFocusedElement.focus();
}
}
});
// Close lightbox when clicking outside content
lightbox.on('click', (e) => {
if ($(e.target).hasClass('help-center-lightbox')) {
lightbox.fadeOut(200).attr('aria-hidden', 'true');
lightboxContent.empty();
resultsWrap.focus();
}
});
});
EOT;
wp_add_inline_script('jquery', $js);
wp_localize_script('jquery', 'helpCenter', [
'ajax_url' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce('help_center_nonce')
]);
});
add_action('wp_footer', function () {
?>
<div id="help-center-popup" class="help-center-popup" role="dialog" aria-modal="true" aria-labelledby="help-center-title" aria-hidden="true">
<div class="help-center-header">
<strong id="help-center-title">Help Center</strong>
<button id="help-center-close" class="help-center-close" aria-label="Close Help Center">×</button>
</div>
<div id="help-center-search" class="help-center-search">
<input type="text" id="help-center-search-input" placeholder="Search for help" aria-label="Search help articles">
</div>
<div id="help-center-results" class="help-center-results" tabindex="0"></div>
</div>
<?php
});
add_filter('nav_menu_css_class', function ($classes) {
if (in_array('open-help-center', $classes)) {
$classes[] = 'open-help-center';
}
return $classes;
}, 10, 1);
function render_help_articles($query) {
if (!$query->have_posts()) {
return '<p>No help articles found.</p>';
}
ob_start();
while ($query->have_posts()) {
$query->the_post();
printf(
'<div class="help-article"><h5 class="help-article-title" data-post-id="%d">%s</h5></div>',
get_the_ID(),
esc_html(get_the_title())
);
}
wp_reset_postdata();
return ob_get_clean();
}
add_action('wp_ajax_get_popular_help_articles', 'load_help_articles');
add_action('wp_ajax_nopriv_get_popular_help_articles', 'load_help_articles');
function load_help_articles() {
check_ajax_referer('help_center_nonce', 'nonce');
$query = new WP_Query([
'post_type' => 'post',
'category_name' => 'help',
'posts_per_page' => 6,
'orderby' => 'date',
'order' => 'DESC'
]);
wp_send_json_success(render_help_articles($query));
}
add_action('wp_ajax_search_help_articles', 'search_help_articles');
add_action('wp_ajax_nopriv_search_help_articles', 'search_help_articles');
function search_help_articles() {
check_ajax_referer('help_center_nonce', 'nonce');
$search = sanitize_text_field($_POST['search'] ?? '');
$query = new WP_Query([
'post_type' => 'post', // Fixed typo from 'content' to 'post'
'category_name' => 'help',
'posts_per_page' => 6,
's' => $search
]);
wp_send_json_success(render_help_articles($query));
}
add_action('wp_ajax_load_help_article', 'load_help_article');
add_action('wp_ajax_nopriv_load_help_article', 'load_help_article');
function load_help_article() {
check_ajax_referer('help_center_nonce', 'nonce');
$post_id = intval($_POST['post_id']);
$post = get_post($post_id);
if (!$post || !has_category('help', $post)) {
wp_send_json_error('Invalid article');
}
ob_start();
?>
<div class="help-article-controls">
<button id="help-go-back" class="help-go-back" aria-label="Go back to article list">Go Back</button>
<a href="<?php echo esc_url(get_permalink($post)); ?>" target="_self" class="help-open-page" aria-label="Open support page in new tab">Open Support Page</a>
</div>
<h3><?php echo esc_html(get_the_title($post)); ?></h3>
<?php echo apply_filters('the_content', $post->post_content); ?>
<div id="sticky-back-to-top">
<a class="back-to-top" href="#help-center-popup" role="button" aria-label="Scroll back to top">Back to top ↑</a>
</div>
<?php
wp_send_json_success(ob_get_clean());
}
add_action('wp_footer', function () {
?>
<div id="help-center-popup" class="help-center-popup" role="dialog" aria-modal="true" aria-labelledby="help-center-title" aria-hidden="true">
<div class="help-center-header">
<strong id="help-center-title">Help Center</strong>
<button id="help-center-close" class="help-center-close" aria-label="Close Help Center">×</button>
</div>
<div id="help-center-search" class="help-center-search">
<input type="text" id="help-center-search-input" placeholder="Search for help" aria-label="Search help articles">
</div>
<div id="help-center-results" class="help-center-results" tabindex="0"></div>
</div>
<div id="help-center-lightbox" class="help-center-lightbox" role="dialog" aria-modal="true" aria-hidden="true">
<button id="lightbox-close" class="lightbox-close" aria-label="Close Lightbox">×</button>
<div class="lightbox-content"></div>
</div>
<?php
});
~170 lines of CSS:
#help-center-popup {
background: inherit;
border: 1px solid;
bottom: 0;
box-shadow: 0 0 6px rgba(0, 0, 0, 0.1);
display: none;
flex-direction: column;
height: 55vh;
max-height: 90vh;
max-width: 100%;
overflow-x: hidden;
overflow-y: auto;
padding: 0 15px;
position: fixed;
right: 0;
transition: opacity 0.2s ease;
width: 400px;
z-index: 10000;
}
.help-center-header {
align-items: center;
background: inherit;
display: flex;
justify-content: space-between;
z-index: 10;
}
.help-center-close {
background: none;
border: none;
cursor: pointer;
line-height: 1;
}
#help-center-search {
background: inherit;
display: none;
padding-bottom: 10px;
position: sticky;
top: 40px;
z-index: 9;
}
#help-center-search-input {
background: inherit;
border: 1px solid;
color: inherit;
font-size: inherit;
padding: 3px;
width: 100%;
}
.help-center-results {
display: flex;
flex-direction: column;
gap: 10px;
}
.help-article-title {
cursor: pointer;
margin: 0;
padding: 0;
scroll-margin-top: 60px;
}
.help-article:hover {
border-right: 4px solid;
}
.help-article-controls {
display: flex;
gap: 10px;
margin-bottom: 10px;
}
.help-center-results video {
width: 100%;
}
.help-center-results video:hover,
.help-center-results img:hover {
cursor: pointer;
}
.help-go-back,
.help-open-page {
background: inherit;
border: 1px solid;
color: inherit;
cursor: pointer;
flex: 1;
font-size: small;
padding: 8px;
text-align: center;
text-decoration: none;
}
#sticky-back-to-top {
background: var(--contrast);
bottom: 0;
position: sticky;
text-align: center;
z-index: 8;
}
.back-to-top {
color: inherit;
cursor: pointer;
display: block;
font-size: initial;
padding: 10px;
text-decoration: none;
}
.help-center-lightbox {
align-items: center;
background: rgba(0, 0, 0, 0.9);
display: none;
height: 100vh;
justify-content: center;
left: 0;
opacity: 0;
position: fixed;
top: 0;
transition: opacity 0.2s ease;
width: 100vw;
z-index: 10001;
}
.help-center-lightbox[aria-hidden="false"] {
display: flex !important;
opacity: 1;
}
.lightbox-close {
background: none;
border: none;
color: #fff;
cursor: pointer;
font-size: 2em;
line-height: 1;
position: absolute;
right: 15px;
top: 15px;
}
.lightbox-content {
align-items: center;
display: flex;
flex-shrink: 0;
justify-content: center;
max-height: 90vh;
max-width: 90vw;
}
.lightbox-content img,
.lightbox-content video {
max-height: 100%;
max-width: 100%;
object-fit: contain;
vertical-align: middle;
}
@media (max-width: 600px) {
#help-center-popup {
border-radius: 0;
bottom: 0;
right: 0;
width: 100%;
}
}
So far so good, I think this deserves a solid C+ for results… MORE TO COME!!