Mark the current navigation link in PHP

Render accessible active navigation from a small PHP route list, using exact path matching and escaped output instead of parsing HTML strings.

Let the template mark the current page

For a PHP-rendered site, the server can send the active navigation state with the first HTML response. Keep the links in a small array and compare their paths. Parsing a block of HTML line by line, or comparing only its first five characters, can mark the wrong link.

A complete navigation example

<?php
function navPath(string $path): string {
    return rtrim($path, '/') ?: '/';
}
function navEscape(string $value): string {
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

$requestPath = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
$current = is_string($requestPath) ? navPath($requestPath) : null;
$links = [
    ['href' => '/', 'label' => 'Home'],
    ['href' => '/about/', 'label' => 'About'],
    ['href' => '/work/', 'label' => 'Work'],
    ['href' => '/contact/', 'label' => 'Contact'],
];
?>
<nav aria-label="Main">
  <ul>
    <?php foreach ($links as $link):
        $active = $current === navPath($link['href']);
    ?>
      <li>
        <a href="<?= navEscape($link['href']) ?>"<?= $active ? ' class="is-active" aria-current="page"' : '' ?>>
          <?= navEscape($link['label']) ?>
        </a>
      </li>
    <?php endforeach; ?>
  </ul>
</nav>

Put the functions in a shared helper loaded once if several templates use them. The example link list is trusted application configuration; escaping an arbitrary URL does not make an unsafe URL scheme safe.

Matching rules

/work, /work/ and /work/?utm_source=newsletter match the Work link. /workshop/ does not. This ignores the query string deliberately; an application whose pages are selected by query parameters should use its router's resolved route instead.

For a section highlight on /work/example/, add an explicit slash-boundary descendant check. Keep the Home link exact, or every page will match it. Do not decode arbitrary request paths differently from your router.

Style .is-active or [aria-current="page"] in the existing navigation stylesheet. Include a visible cue such as weight or an underline and preserve keyboard focus styles. No JavaScript is needed for this version.

References: PHP parse_url, HTML escaping, and aria-current.

Original version25 May 2014

Kept here for reference and earlier links. The updated guide above is the recommended starting point; older code may depend on retired services or different software versions.

Making it evident which page the user is on in relation to the menu is a very common technique, however, there are lots of convoluted ways of doing it, many of which are manual, or require javascript.

The below script is the simplest way and my personal favourite for small - medium sized websites.

You simply define your menu in the $nav variable, and the following PHP automatically sees if the current page matches the link, if so it adds class="active" to the a link.

Using the class of active you can then style that link differently, making it clear the user is currently on that page.

<?php
$nav = <<<NAV
<ul>
  <li><a href="/">Home</a></li>
  <li><a href="/about">About Us</a></li>
  <li><a href="/work">Work</a></li>
  <li><a href="/blog">Blog</a></li>
  <li><a href="/contact">Contact</a></li>
</ul>
NAV;

$lines = explode("\n", $nav);
foreach ($lines as $line) {
    $current = false;
    preg_match('/href="([^"]+)"/', $line, $url);
    if (substr($_SERVER["REQUEST_URI"], 0, 5) == substr(@$url[1], 0, 5)) {
        $line = str_replace('<a h', '<a  class="active" h', $line);
        }
    echo $line."\n";
}
?>

I've also documented a JavaScript version if you want to add class active to menu with JavaScript instead.

Keep exploring

A couple more useful notes.