How to build semantic, responsive website navigation without JavaScript

Building beautiful website navigation with popover menus using just HTML and CSS

Building beautiful website navigation

We recently rolled out an update to the main navigation on the GoSquared site. If you’ve been to gosquared.com lately, you may have already noticed the update – it looks like this:

Or this, if you’re on a mobile device:

Here’s a little information on how we built it, without using a single line of JavaScript.

Building semantic navigation

We knew from the outset of this update that we wanted to build a dropdown-style navigation for desktop browsers, and fullscreen accordion-style navigation for smaller screens.

Besides that, we also had some other requirements:

  1. It should be simple for us to maintain if we want to add or modify items.
  2. It should work in as many browsers as possible without any ugly hacks.
  3. It should play nicely if JavaScript is disabled.
  4. It should be lightweight – that is, if it’s included on every page of the GoSquared site, it shouldn’t require any weighty library code either slowing down the page or leading to weird broken states while code loads.

This led us to set a few implementation goals to direct how we built it:

  • Make the markup as semantic as possible (i.e. use the well-defined HTML elements in a logical way, no putting bits and pieces of markup in illogical places just to make dropdowns work)
  • Reuse the same elements for both mobile and desktop (i.e. no duplicating of code)
  • If possible, build the whole thing without any JavaScript

These three goals in combination would hopefully mean that we get the benefits of performance, graceful degradation, accessibility and maintainability pretty much for free.

The code behind the navigation – start with a list

At its core, the GoSquared site navigation is, just like any on any other site, a collection of links. It starts out life as an unordered list inside a <nav>, with each list item containing an anchor tag.

<nav>
  <ul>
    <li>
      <a href="/one">Item 1</a>
    </li>
    <li>
      <a href="/two">Item 2</a>
    </li>
  </ul>
</nav>

We can put some styles on this to align the items horizontally, and give them some whitespace and hover states. That looks something like this:

See the Pen Super simple nav by JT (@_jt) on CodePen.

Introducing sub-navigation

Now that’s all well and good if your navigation has only two or three links that you want to include, but we have a whole hierarchy that we want to make accessible: under each item in the main navigation, we have one or two lists of places we want to link to.

We start out by putting each of these sub-navigations inside the corresponding item in the root list. This starts out with another <nav>; since we have a couple of different sections to include, each gets its own <section> with a <header>, and then the familiar <ul> with a list of anchor tags.

<nav>
  <ul>
    <li>
      <a>Item 1</a>

      <nav class="subnav">
        <section>
          <header>Subnav list title</header>
          <ul>
            <li>
              <a>
                <h5>Subitem 1.1</h5>
                <p>A short description</p>
              </a>
            </li>
            <li>
              ...
            </li>
          </ul>
        </section>
        <section>
          <header>A second subnav column</header>
          <ul>
            ...
          </ul>
        </section>
      </nav>
    </li>
    <li>
      <a>Item 2</a>

      <nav class="subnav">
        ...
      </nav>
    </li>
  </ul>
</nav>

We can then position these subnavs absolutely to show as dropdowns, then use CSS :hover, :focus and :focus-within pseudo-selectors to show them when the visitor hovers or focuses the parent list item.

By putting the sub-navigation directly inside the parent elements (as opposed to in a separate <div> that gets shown by some JavaScript, for example), we can handle this showing and hiding purely in CSS – since it’s a child of the main list item, it will still match the :hover pseudoselector on the parent when the visitor moves their mouse away from the root list and starts interacting with the dropdown.

Throw in some CSS transitions on transform, opacity and visibility (we don’t want the dropdowns intercepting the visitor’s mouse when they’re not visible) and the result looks something like this:

See the Pen Navigation with dropdowns by JT (@_jt) on CodePen.

Making our responsive navigation work on mobile devices

Time to go mobile

So far we’ve focused on the desktop experience for this navigation. But nowadays the mobile version of your experience is just as important, if not more so.

Not only do mobile devices come with smaller screens, but they also have a different set of interactions – there’s no such thing as a “hover” state on a touch display without a mouse, for example.

By and large browsers normalise these differences fairly well, so they are at least usable (for example, triggering a hover state after one tap, and the actual “click” only on the second tap), but this often feels like a “fix” retrofitted to an experience solely to prevent it from being broken. So we decided to do away with the hover-based approach and create a better-designed experience for mobile devices.

The mobile design calls for toggling the sub-navs between visible and hidden based on clicking or tapping the header of the corresponding section. This can also be achieved using CSS by incorporating a hidden checkbox and using the :checked and ~ (sibling) rules.

The changed markup looks something like this:

<li>
  <input type="checkbox" id="toggle">
  <label for="toggle">
    <a>Item</a>
  </label>
  <nav class="subnav">
    ...
  </nav>
</li>

… and the only change to the CSS to make this work is to add a rule that shows the subnav under :checked ~ .subnav.

For the whole of the rest of the mobile navigation we can reuse all of the existing markup we already have, simply overriding and resetting any rules we previously had to show subnavs as dropdowns.

The end result is something like this (you’ll need to view on CodePen and resize your browser to see the difference between desktop and mobile versions)

That’s all, folks!

Obviously there’s a lot more little things that go into the navigation that you see on the GoSquared site today (icons, arrows, and a few more other bits specific to mobile), but hopefully this post has helped explain the interesting parts of how we built the new navigation for GoSquared.

Have any thoughts? Anything we could have done differently or better? Let us know via Twitter!

Never miss a post