Add a Notification Top Bar to Your Theme

Add a notification top bar to your theme without using a plug-in. Learn how to use a little bit of PHP, JavaScript and some CSS to create a top bar that doesn’t need to access the WordPress database (so it’s fast) and also supports multiple languages, so it works with WPML and PolyLang.

We’ll also add a Close/Dismiss button so users can dismiss the notification. We’ll store whether-or-not the top bar has been dismissed in a cookie.

image-5-7788238
An example top bar

It’s a common site requirement and I looked at some Top Bar plugins, but I really wanted something very light, with no database calls. I also wanted full control of where the top bar gets rendered. All the top bars I found use the WordPress action hook wp_body_open. In most instances this is fine, but sometimes it’s not. Maybe some quirky theme means you need to render it underneath a screen component, like a sticky menu.

So I decided to write my own.

infoBefore you start this tutorial, make sure you’re using a custom child theme.

Sort Out the Structure

We’ll build the module using some PHP, a few CSS definitions, and client-side JavaScript (jQuery) to show/hide the bar and handle when the user clicks on the Close button. Because we’re dealing with several files here, we’ll start by creating a new directory inside our custom child theme – a place we can stash all our files/assets without making the child theme’s main directory messy. Create a directory called hw-top-bar, then create three new/empty text files in it, so you have a folder that looks like this:

image-9-5340615
Files for our Top Bar module – starting off

We’ll create a main function that outputs the HTML for the top bar, and we’ll do this without it needing any parameters. This is so we can call the function from anywhere in our code (such as in header.php or maybe page.php), or we can connect it directly to an action handler. So… if we want our module to work like all the other Top Bar plugins out there, we could invoke it like this:

// Render the top bar just inside the document's <body> element.
add_action('wp_body_open', 'hwtb_render_top_bar');

Or if our child theme uses a custom header.php file, we could insert the top bar ourselves by adding the following to our header.php file:

<!DOCTYPE html><?php
// ...
// The bulk of our header.php file
// ...
?><body <?php body_class(); ?>>
<header>
   <!-- Usually a top bar be inserted into here -->
   <ul class="main-menu">
     <li><a href="/">Home</a></li>
     <li><a href="/blog/">Our Blog</a></li>
     <li><a href="/contact/">Contact Us</a></li>
     <li><a href="/about/">About Us</a></li>
   </ul>
<?php

// Render the top bar.
hwtb_render_top_bar();

?>
</header>

We don’t want to mess about putting an options page into the WordPress Admin area of our site, just to set the HTML content of our top bar. We want to be able to do something like the following in our custom child theme’s function.php file:

// Enable the site's top bar
define('HWTB_IS_ENABLED', true);

// HTML for our top bar (different languages)
define('HWTB_MESSAGE', array(
	'_default' => 'Hello World! <a href="#">More Info</a>',
	'fr_FR' => 'Bounjour le Monde! <a href="#">Plus d\'infos...</a>',
	'es_ES' => 'Hola Mundo! <a href="#">Más información...</a>',
));

Now we’ve got an idea of how we’re going to lay it out, it’s time to get stuck-in.

Let’s Write some Code – The PHP Bit

In the hw-top-bar folder, edit the file top-bar.php and paste the following into it.

<?php

/**
 * top-bar.php
 *
 * To enable the top bar, include this file from your function.php file.
 *
 * Then set the constants:
 *
 *    const HWTB_IS_ENABLED = true;
 *    const HWTB_MESSAGE = 'Notification Bar <a href="#">More info...</a>';
 *
 * Call hwtb_render_top_bar() from somewhere, or connect it to wp_body_open
 * like this:
 *
 *    add_action('wp_body_open', 'hwtb_render_top_bar');
 */

// Block direct access.
if (!defined('WPINC')) {
	exit('Do NOT access this file directly.');
}

function hwtb_init() {
	$is_top_bar_enabled = hwtb_is_top_bar_enabled();
	$is_close_enabled = hwtb_is_close_button_enabled();

	if ($is_top_bar_enabled) {
		// Include JS/CSS.
		$base_dir = basename(dirname(__FILE__)); // 'login-page'
		$base_full_path = dirname(__FILE__);
		$theme_version = wp_get_theme()->get('Version');

		$stylesheet_file_name = 'top-bar.css';
		$stylesheet_full_path = trailingslashit($base_full_path) . $stylesheet_file_name;
		if (is_readable($stylesheet_full_path)) {
			$stylesheet_url = get_stylesheet_directory_uri() . '/' .
				$base_dir . '/' . $stylesheet_file_name;

			wp_enqueue_style('hw-top-bar-css', $stylesheet_url, false);
		}

		if ($is_close_enabled) {
			$javascript_file_name = 'top-bar.js';
			$javascript_full_path = trailingslashit($base_full_path) . $javascript_file_name;
			if (is_readable($javascript_full_path)) {
				$javascript_url = get_stylesheet_directory_uri() . '/' .
					$base_dir . '/' . $javascript_file_name;

				wp_enqueue_script('js-cookie',
					'https://cdn.jsdelivr.net/npm/js-cookie@rc/dist/js.cookie.min.js',
					false,
					'3.0.0',
					true
				);

				wp_register_script('hw-top-bar-js',
					$javascript_url,
					array('js-cookie'),
					$theme_version,
					true
				);

				if (current_user_can('administrator')) {
					wp_localize_script(
						'hw-top-bar-js',
						'hwtbState',
						array('isCookieEnabled' => false)
					);
				}

				wp_enqueue_script('hw-top-bar-js');
			}
		}
	}
}
add_action('init', 'hwtb_init');

function hwtb_render_top_bar() {
	$is_top_bar_enabled = hwtb_is_top_bar_enabled();

	if ($is_top_bar_enabled) {
		$top_bar_html = hwtb_get_html();
		$is_close_enabled = hwtb_is_close_button_enabled();
		$top_bar_classes = apply_filters(
			'hwtb_container_classes',
			array('hwtb-top-bar-container')
		);

		$props = '';
		if ($is_close_enabled) {
			$props = 'style="display:none;"';
		}

		printf('<div class="%s" %s>', implode(' ', $top_bar_classes), $props);
		echo '<span class="hwtb-content">';

		if ($is_close_enabled) {
			echo '<a class="hwtb-close-button" href="javascript:void(0);">';
			echo '<i class="fa fa-times-circle" aria-hidden="true"></i>';
			echo '</a>'; //.hwtb-close-button
		}

		echo $top_bar_html;
		echo '</span>'; // .hwtb-content
		echo '</div>'; // .hwtb-top-bar-container
	}
}

function hwtb_is_top_bar_enabled() {
	$is_enabled = (defined('HWTB_IS_ENABLED') &&
		(HWTB_IS_ENABLED === true) &&
		defined('HWTB_MESSAGE') &&
		!empty(HWTB_MESSAGE));

	return apply_filters('hwtb_is_top_bar_enabled', $is_enabled);
}

function hwtb_is_close_button_enabled() {
	$is_enabled = true;

	if (defined('HWTB_IS_CLOSE_BUTTON_ENABLED')) {
		$is_enabled = (HWTB_IS_CLOSE_BUTTON_ENABLED === true);
	}

	return apply_filters('hwtb_is_close_button_enabled', $is_enabled);
}

function hwtb_get_html() {
	$html = null;

	if (defined('HWTB_MESSAGE')) {
		if (is_string(HWTB_MESSAGE)) {
			$html = HWTB_MESSAGE;
		} elseif (is_array(HWTB_MESSAGE) && (count(HWTB_MESSAGE) > 0)) {
			// Get the default fall-back translation.
			if (array_key_exists('_default', HWTB_MESSAGE)) {
				$html = HWTB_MESSAGE['_default'];
			}

			// Look for a more specific translated HTML message.
			$locale = get_locale();
			if (empty($locale)) {
				// No language set?!
			} elseif (!array_key_exists($locale, HWTB_MESSAGE)) {
				// No message found for our language.
			} else {
				// We found a language-specific message, so let's use it.
				$html = HWTB_MESSAGE[$locale];
			}
		} else {
			// Unsupported data in HWTB_MESSAGE.
		}
	}

	if (empty($html)) {
		$html = 'HWTB_MESSAGE should be a string or an array.';
	}

	return $html;
}

There’s not really very much in here. Essentially, we hook WordPress’ main init action handler so we can do some set-up. Then there’s just the main hwtb_render_top_bar( ) function that we can either connect to an action, or call directly from wherever we want.

Activate the Module

To activate the top bar, edit your custom child theme’s function.php file and paste the following into it.

// Comment-out this line (or set it to false) to disable the Top Bar.
const HWTB_IS_ENABLED = true;
const HWTB_MESSAGE = 'Notification Bar <a href="#">More info...</a>';

/*
 * Render the Top Bar straight after the <body> element.
 * If you want more control of where the Top Bar is rendered, comment
 * this out and call hwtb_render_top_bar() from somewhere in your
 * theme - probably in your header.php file. Or maybe just connect it
 * to a different action hook.
 */
add_action('wp_body_open', 'hwtb_render_top_bar');

// Include the Headwall Top Bar module.
require_once 'hw-top-bar/top-bar.php';

Some of the Nice Bits

When we render the HTML, it’s really just a <div> element with some HTML in it – nothing complicated. But… on some sites we might want to add some additional CSS classes to the <div> container to make it easier to customise. So we added our own ad-hoc filter in there:

$top_bar_classes = apply_filters(
	'hwtb_container_classes',
	array('hwtb-top-bar-container')
);

This means that we can add something in our main functions.php to add some custom CSS classes to our top bar’s <div> element:

// This code could go in to your custom child theme's function.php file,
// if you want to add to the top bar's CSS classes.
function custom_top_bar_classes($classes) {
	// $classes is just an array of CSS class names.
	array_push($classes, 'custom-top-bar-class');
	return $classes;
}
add_filter('hwtb_container_classes', 'custom_top_bar_classes');

We can also use the hwtb_is_top_bar_enabled and hwtb_is_close_button_enabled filters to override whether or not the top bar is enabled, and whether or not the close button is enabled.

Although there are some other neat things in here, that’ll do for the PHP stuff for now.

Adding Some Style

IN our module, we want to make some simple styles that are generic-looking and will work everywhere. In the hw-top-bar folder, edit the file top-bar.css, paste the following into it and then save.

/*
 * Top Bar styles.
 *
 * See functions-top-bar.php
 */

.hwtb-top-bar-container {
	background-color:  #ff00ff;
	color:  white;
	text-align:  center;
	position:  relative;
	padding:  0.5em 2em 0.5em 2em;
}

.hwtb-content {
	line-height: 1.5em;
}

.hwtb-top-bar-container a.hwtb-button {
	background-color:  rgba( 255, 255, 255, 0.40 );
	color:  white;
	text-shadow: 0 0 0.125em black;
	font-weight: bolder;
	padding:  0.25em 0.5em 0.25em 0.5em;
	transition: 0.3s;
	margin: 0 1em 0 1em;
	border-radius: 0.25em;
}

.hwtb-top-bar-container a.hwtb-button:hover {
	text-decoration: none;
	box-shadow: 0 0 0.5em white, 0 0 0.5em white;
}

.hwtb-close-button {
	position: absolute;
	background-color: transparent;
	border:  none;
	color:  white;
	opacity: 0.80;
	font-size:  150%;
	right:  0.5em;
	top:  50%;
	transform: translateY(-50%);
	font-weight: normal;
	transition: 0.3s;
}

.hwtb-close-button:hover {
	color:  white;
	opacity: 1;
}

Customising the Top Bar is easy, but don’t change anything in top-bar.css, because we’ll want to use this file in other WordPress projects. Instead, if you want to change the colour of the top bar, edit style.css in your custom child theme and do something like this:

/* Override key properties of our Top Bar */
.hwtb-top-bar-container {
	background-color:  blue;
}

Styling the top bar is the easy bit 🙂

Finally – Coding the JavaScript Bit

Now let’s do the stuff to handle closing/dismissing the top bar, and storing that preference in a cookie. In your custom child theme’s hw-top-bar folder, open the file top-bar.js and paste the following into it.

/*
 * Top Bar client-side scripts.
 */

(function($) {
    'use strict';

    $(window).on('load', function() {
        // console.log('Top Bar');
        var cookieName = 'hwtb_is_hidden';
        var isFound = ($('.hwtb-top-bar-container').length > 0);
        var isCloseButtonFound = ($('a.hwtb-close-button').length > 0);

        var isCookieEnabled = isCloseButtonFound;
        if (typeof hwtbState !== 'undefined') {
            isCookieEnabled = hwtbState.isCookieEnabled;
        }

        if (isFound) {
            var isVisible = isFound && (
                !isCookieEnabled ||
                (isCookieEnabled && (Cookies.get(cookieName) != '1'))
            );

            // Show the top bar here.
            if (isVisible) {
                $('.hwtb-top-bar-container').slideDown();
            }

            $('a.hwtb-close-button').click(function() {
                $(this).closest('.hwtb-top-bar-container').slideUp();

                // 7 day expiration.
                if (isCookieEnabled) {
                    Cookies.set(
                        cookieName, '1', { expires: 7, sameSite: 'strict' }
                    );
                }
            });
        }
    });
})(jQuery);

We won’t go too much into this because JavaScript and jQuery are a beast in their own right, but here’s the skinny on what’s going on:

After the document’s loaded…

  • We check to see if there are any elements with the class hwtb-top-bar-container.
    • If there are, then there is a top bar on the page.
  • We check to see if there are any link elements with the class hwtb-close-button.
    • If there are, then there is a top bar close button on the page.
  • Back in the PHP file, we checked to see if the current user is an administrator.
    • If so, we used wp_localize_script() to pass some data into the hwtbState object.
    • This means that if we’re logged-in to the site as an administrator, we can’t make the Top Bar go away. It’s useful for reminding us that the top bar is enabled… so we don’t forget :S
  • We check to see if the user has previously dismissed the top bar (if cookies are enabled).
    • If they haven’t disabled it then we call slideDown() on the top bar so that it becomes visible.
  • Finally, we connect the click() method of the close button so that it hides the top bar with a call to slideUp() and stores the number “1” in our “hwtb_is_hidden” cookie… so we can avoid calling slideDown() next time we load a page.

Final Tests

Now we’ve got all three files, you should be able to reload your browser (don’t forget to clear/bypass the cache) and… Boom! You should see your shiny new top bar.

image-5-7788238
The finished top bar

Additional Languages

A cool little feature we added was support for multiple languages. We do this in top-bar.php, in the function hwtb_get_html(). What we do is check the HWTB_MESSAGE constant. If it’s a string, then we assume that the string is some HTML and we just use it. However, if it’s an array, we assume it’s structured like thiis:

define('HWTB_MESSAGE', array(
	'_default' => 'Hello World! <a href="#">More Info</a>',
	'fr_FR' => 'Bounjour le Monde! <a href="#">Plus d'infos...</a>',
	'es_ES' => 'Hola Mundo!' <a href="#">Más información...</a>',
));

By putting the languages in here, as raw strings in PHP, we’re avoid slow/expensive calls to the database. It keeps everything clean and simple.

Wrapping it Up

That’s about it. We’ve got three little files in a self-contained directory, so we can drop the module into any of our future WordPress projects. We can override the top bar’s behaviour by hooking a couple of filters in our functions.php file. We can override the top bar’s appearance in our style.css file.

…and we’ve had a negligible effect on our site’s performance because there aren’t any database calls.

Now that’s what I call tidy 🙂

Leave a Comment

Your email address will not be published. Required fields are marked *