How We Improved the Accessibility of Our Single Page App Menu

I recently started working on a Progressive Web App (PWA) for a client with my team. We’re using React with client-side routing via React Router, and one of the first elements that we made was the main menu. Menus are a key component of any site or app. That’s really how folks get around, so making it accessible was a super high priority for the team.

But in the process, we learned that making an accessible main menu in a PWA isn’t as obvious as it might sound. I thought I’d share some of those lessons with you and how we overcame them.

As far as requirements go, we wanted a menu that users could not only navigate using a mouse, but using a keyboard as well, the acceptance criteria being that a user should be able to tab through the top-level menu items, and the sub-menu items that would otherwise only be visible if a user with a mouse hovered over a top-level menu item. And, of course, we wanted a focus ring to follow the elements that have focus.

The first thing we had to do was update the existing CSS that was set up to reveal a sub-menu when a top-level menu item is hovered. We were previously using the visibility property, changing between visible and hidden on the parent container’s hovered state. This works fine for mouse users, but for keyboard users, focus doesn’t automatically move to an element that is set to visibility: hidden (the same applies for elements that are given display: none). So we removed the visibility property, and instead used a very large negative position value:

.menu-item {
  position: relative;
}

.sub-menu {
  position: absolute
  left: -100000px; /* Kicking off  the page instead of hiding visiblity */
}

.menu-item:hover .sub-menu {
  left: 0;
}

This works perfectly fine for mouse users. But for keyboard users, the sub menu still wasn’t visible even though focus was within that sub menu! In order to make the sub-menu visible when an element within it has focus, we needed to make use of :focus and :focus-within on the parent container:

.menu-item {
  position: relative;
}

.sub-menu {
  position: absolute
  left: -100000px;
}

.menu-item:hover .sub-menu,
.menu-item:focus .sub-menu,
.menu-item:focus-within .sub-menu {
  left: 0;
}

This updated code allows the the sub-menus to appear as each of the links within that menu gets focus. As soon as focus moves to the next sub menu, the first one hides, and the second becomes visible. Perfect! We considered this task complete, so a pull request was created and it was merged into the main branch.

But then we used the menu ourselves the next day in staging to create another page and ran into a problem. Upon selecting a menu item—regardless of whether it’s a click or a tab—the menu itself wouldn’t hide. Mouse users would have to click off to the side in some white space to clear the focus, and keyboard users were completely stuck! They couldn’t hit the esc key to clear focus, nor any other key combination. Instead, keyboard users would have to press the tab key enough times to move the focus through the menu and onto another element that didn’t cause a large drop down to obscure their view.

The reason the menu would stay visible is because the selected menu item retained focus. Client-side routing in a Single Page Application (SPA) means that only a part of the page will update; there isn’t a full page reload.

There was another issue we noticed: it was difficult for a keyboard user to use our “Jump to Content” link. Web users typically expect that pressing the tab key once will highlight a “Jump to Content” link, but our menu implementation broke that. We had to come up with a pattern to effectively replicate the “focus clearing” that browsers would otherwise give us for free on a full page reload.

The first option we tried was the easiest: Add an onClick prop to React Router’s Link component, calling document.activeElement.blur() when a link in the menu is selected:

const Menu = () => {
  const clearFocus = () => {
    document.activeElement.blur();
  }

  return (
    <ul className="menu">
      <li className="menu-item">
        <Link to="/" onClick={clearFocus}>Home</Link>
      </li>
      <li className="menu-item">
        <Link to="/products" onClick={clearFocus}>Products</Link>
        <ul className="sub-menu">
          <li>
            <Link to="/products/tops" onClick={clearFocus}>Tops</Link>
          </li>
          <li>
            <Link to="/products/bottoms" onClick={clearFocus}>Bottoms</Link>
          </li>
          <li>
            <Link to="/products/accessories" onClick={clearFocus}>Accessories</Link>
          </li>
        </ul>
      </li>
    </ul>
  );
}

This approach worked well for “closing” the menu after an item is clicked. However, if a keyboard user pressed the tab key after selecting one of the menu links, then the next link would become focused. As mentioned earlier, pressing the tab key after a navigation event would ideally focus on the “Jump to Content” link first.

At this point, we knew we were going to have to programmatically force focus to another element, preferably one that’s high up in the DOM. That way, when a user starts tabbing after a navigation event, they’ll arrive at or near the top of the page, similiar to a full page reload, making it much easier to access the jump link.

We initially tried to force focus on the <body> element itself, but this didn’t work as the body isn’t something the user can interact with. There wasn’t a way for it to receive focus.

The next idea was to force focus on the logo in the header, as this itself is just a link back to the home page and can receive focus. However, in this particular case, the logo was below the “Jump To Content” link in the DOM, which means that a user would have to shift + tab to get to it. No good.

We finally decided that we had to render an interact-able element, for example, an anchor element, in the DOM, at a point that’s above than the “Jump to Content” link. This new anchor element would be styled so that it’s invisible and that users are unable to focus on it using “normal” web interactions (i.e. it’s taken out of the normal tab flow). When a user selects a menu item, focus would be programmatically forced to this new anchor element, which means that pressing tab again would focus directly on the “Jump to Content” link. It also meant that the sub-menu would immediately hide itself once a menu item is selected.

const App = () => {
  const focusResetRef = React.useRef();

  const handleResetFocus = () => {
    focusResetRef.current.focus();
  };

  return (
    <Fragment>
      <a
        ref={focusResetRef}
        href="javascript:void(0)"
        tabIndex="-1"
        style={{ position: "fixed", top: "-10000px" }}
        aria-hidden
      >Focus Reset</a>
      <a href="#main" className="jump-to-content-a11y-styles">Jump To Content</a>
      <Menu onSelectMenuItem={handleResetFocus} />
      ...
    </Fragment>
  )
}

Some notes of this new “Focus Reset” anchor element:

  • href is set to javascript:void(0) so that if a user manages to interact with the element, nothing actually happens. For example, if a user presses the return key immediately after selecting a menu item, that will trigger the interaction. In that instance, we don’t want the page to do anything, or the URL to change.
  • tabIndex is set to -1 so that a user can’t “normally” move focus to this element. It also means that the first time a user presses the tab key upon loading a page, this element won’t be focused, but the “Jump To Content” link instead.
  • style simply moves the element out of the viewport. Setting to position: fixed ensures it’s taken out of the document flow, so there isn’t any vertical space allocated to the element
  • aria-hidden tells screen readers that this element isn’t important, so don’t announce it to users

But we figured we could improve this even further! Let’s imagine we have a mega menu, and the menu doesn’t hide automatically when a mouse user clicks a link. That’s going to cause frustration. A user will have to precisely move their mouse to a section of the page that doesn’t contain the menu in order to clear the :hover state, and therefore allow the menu to close.

What we need is to “force clear” the hover state. We can do that with the help of React and a clearHover class:

// Menu.jsx
const Menu = (props) => {
  const { onSelectMenuItem } = props;
  const [clearHover, setClearHover] = React.useState(false);

  const closeMenu= () => {
    onSelectMenuItem();
    setClearHover(true);
  }

  React.useEffect(() => {
    let timeout;
    if (clearHover) {
      timeout = setTimeout(() => {
        setClearHover(false);
      }, 0); // Adjust this timeout to suit the applications' needs
    }
    return () => clearTimeout(timeout);
  }, [clearHover]);

  return (
    <ul className={`menu ${clearHover ? "clearHover" : ""}`}>
      <li className="menu-item">
        <Link to="/" onClick={closeMenu}>Home</Link>
      </li>
      <li className="menu-item">
        <Link to="/products" onClick={closeMenu}>Products</Link>
        <ul className="sub-menu">
          {/* Sub Menu Items */}
        </ul>
      </li>
    </ul>
  );
}

This updated code hides the menu immediately when a menu item is clicked. It also hides immediately when a keyboard user selects a menu item. Pressing the tab key after selecting a navigation link moves the focus to the “Jump to Content” link.

At this point, our team had updated the menu component to a point where we were super happy. Both keyboard and mouse users get a consistent experience, and that experience follows what a browser does by default for a full page reload.

Our actual implementation is slightly different than the example here so we could use the pattern on other projects. We put it into a React Context, with the Provider set to wrap the Header component, and the Focus Reset element being automatically added just before the Provider’s children. That way, the element is placed before the “Jump to Content” link in the DOM hierarchy. It also allows us to access the focus reset function with a simple hook, instead of having to prop drill it.

We have created a Code Sandbox that allows you to play with the three different solutions we covered here. You’ll definitely see the pain points of the earlier implementation, and then see how much better the end result feels!

We would love to hear feedback on this implementation! We think it’s going to work well, but it hasn’t been released to in the wild yet, so we don’t have definitive data or user feedback. We’re certainly not a11y experts, just doing our best with what we do know, and are very open and willing to learn more on the topic.


The post How We Improved the Accessibility of Our Single Page App Menu appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Fixing Smooth Scrolling with Find-on-Page

Back when we released the v17 design (we’re on v18 now) of this site. I added html { scroll-behavior: smooth; } to the CSS. Right away, I got comments like this (just one example):

… when you control+f or command+f and search on CSS-Tricks, it’ll scroll very slowly instead of snapping to the result, which makes finding information and keywords on CSS-Tricks much slower. As someone who uses this shortcut frequently, this is a usability issue for me.

Not terribly long after, I just removed it. I didn’t feel that strongly about it, and the fact that you have almost zero control over it, made me just can the idea.

I see it come up as a “CSS tip” a lot, so I chimed in with my experience:

After mentioning that, Christian Schaefer chimed in with a great idea:

Love that!

Christian blogged it:

Smooth scrolling is consequently applied to everything. Always. Even when cycling through the browser’s page search results. At least that’s the case for Chromium. So for the page search it would be desirable for the browser to make an exception to that rule and to deactivate smooth scrolling. Until the Chromium team fixes it, here is a trick how to solve the problem on your own with a little bit of extra CSS and HTML.

I’m not sure if Chrome (or any other browser) would consider that a bug or not. I doubt it’s specced since find-on-page isn’t really a web technology feature. But anyway, I much prefer find-on-page without it.

html:focus-within {
  scroll-behavior: smooth;
}

It mostly works. The bummer part about it is situations like this…

<a href="#link-down-the-page">Jump down</a>

...

<h2 id="link-down-the-page">Header</h2>

That will jump the page down. With scroll-behavior: smooth; in place, that’s kinda nice. But <h2> is typically not a “focusable” element. So, with the trick above, there is now no focus within <html> anymore, and the smooth scrolling is lost. If you want to preserve that, you’d have to do:

<h2 tabindex="-1" id="link-down-the-page">Header</h2>

The post Fixing Smooth Scrolling with Find-on-Page appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

How to Create a “Skip to Content” Link

Skip links are little internal navigation links that help users move around a page. It’s possible you’ve never actually seen one before because they’re often hidden from view and used as an accessibility enhancement that lets keyboard users and screen readers jump from the top of the page to the content without have to go through other elements on the page first.

In fact, you can find one right here on CSS-Tricks if you crack DevTools open.

In my opinion, the best way to implement a skip link is to hide it and then bring it into view when it is focused. So, let’s say we have a link in the HTML:

<a class="skip-to-content-link" href="#main">
  Skip to content
</a>

…we can give it an absolute position and translate it off the screen:

.skip-to-content-link {
  left: 50%;
  position: absolute;
  transform: translateY(-100%);
}

Then we can bring it back into view when it’s in focus and style it up a bit in the process:

.skip-to-content-link {
  background: #e77e23;
  height: 30px;
  left: 50%;
  padding: 8px;
  position: absolute;
  transform: translateY(-100%);
  transition: transform 0.3s;
}

.skip-to-content-link:focus {
  transform: translateY(0%);
}

This will hide our link until it is focused and then put it into view when it becomes focused.

Now, allow me to elaborate, starting with this quote from Miles Davis:

Time isn’t the main thing. It’s the only thing.

As I sit down to write this article in a rainy Ireland, I think of the challenge that many users face as they use the web that I take for granted. We put so much thought into creating a great user experience without thinking of all our users and how to meet their needs. Admittedly, a skip link was something I had never heard of until completing a course on Frontend Masters by Marcy Sutton. Since learning the power and simplicity of using a skip link, I decided to make it my mission to spread more awareness — and what platform better than CSS-Tricks!

A solution is an answer to a problem, so what’s the solution for helping keyboard users and screen readers find the content of a page quickly? In short, the solution is time. Giving users the ability to navigate to parts of our website that they are most interested in gives them the power to save valuable time.

Take the Sky News website as an example. It offers a “Skip to content” button that allows users to skip all the navigation items and jump straight to the main content.

You can see this button by navigating to the top of the page using your keyboard.  This is similar to the implementation shown above. The link is always in the document but only becomes visible when it’s in focus.

This is the type of skip link we are going to create together in this post.

Our sample website

I built a sample website that we will use to demonstrate a skip link.

The website has a number of navigation links but, in the interest of time, there are only two pages: the home page and the blog page. This is all we need to see how things work.

Identifying the problem

Here’s the navigation we’re working with:

In total, we have eight navigation items that a keyboard user and screen reader must tab through before reaching the main content below the navigation.

That’s the problem. The navigation may be irrelevant for the user. Perhaps the user was given a direct link to an article and they simply want to get to the content.

This is a perfect use case for a skip link.

Creating the link

There are a couple of approaches to creating a Skip to content link.  What I like to do is similar to the Sky News example, which is hiding the link until it is focused.  That means we can drop the link at or near the top of the page, like inside the <header> element.

<header>
  <a class="skip-link" href='#main'>Skip to content</a>
  <!-- Navigation-->
</header>

This link has a .skip-link class so we can style it. Thehref attribute points to #main, which is the ID we will add to the <main> element that comes further down the page. That’s what the link will jump to when clicked.

<header>
  <a class="skip-link" href='#main'>Skip to content</a>
  <!-- Navigation-->
</header>


<!-- Maybe some other stuff... -->


<main id="main">
  <!-- Content -->
</main>

Here’s what we have if we simply drop the link into the header with no styling.

This does not look great, but the functionality is there. Try navigating to the link with your keyboard and pressing Enter when it’s in focus.

Now it’s time to make it look pretty. We must first fix the positioning and only show our skip link when it is in focus.

.skip-link {
  background: #319795;
  color: #fff;
  font-weight: 700;
  left: 50%;
  padding: 4px;
  position: absolute;
  transform: translateY(-100%);
}

.skip-link:focus {
  transform: translateY(0%);
}

The magic here is in the transform property, which is hiding and showing our skip link depending on whether it is focused or not. Let’s make it look a little nicer with a quick transition on the transform property.

.skip-link {
  /* Same as  before */
  transition: transform 0.3s;
}

It will now transition into view which makes that bit better.

You should now (hopefully) have what I have below:

As you can see, the skip link bypasses the navigation and jumps straight down to the <main> element when clicked.

It’s OK to use more than one link!

Right now, the link only serves one purpose and that is skip to the content of our website. But we don’t have to stop there.

We can go even further and create a skip link that has more options, such as a way to jump to the footer of the site. As you might imagine, this is quite similar to what we’ve already done.

Let’s make the blog page of the example site a little more usable by using multiple skip links. It’s common for blogs to use AJAX that loads in more posts when reaching the bottom of the page. This can make it very difficult to get to the footer of the website. That’s the problem we want to solve in this case.

So let’s add a second link that bypasses all that auto-loading business and jumps the user straight to a #footer element on the page.

<div class="skip-link" >
  Skip to <a href='#main'>content</a> or <a href='#footer'>footer</a>
</div>

We also need to amend our CSS a little and use the :focus-within pseudo selector.

.skip-link {
  transform: translateY(-100%);
}

.skip-link:focus-within {
  transform: translateY(0%);
}

This is saying if anything within our .skip-link element has focus, then we show it. Sadly, neither Internet Explorer nor Opera Mini support focus-within, but its coverage is pretty darn good and there is a polyfill available.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

ChromeFirefoxIEEdgeSafari
6052No7910.1

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
8068No10.3

Last thing we need to do is make sure there's an ID on our footer element so the link has something to jump to.

<footer id="footer">

Here's what this gives us:

If we wanted to go one step further (and I’d encourage it), we could style each link a little differently so users can distinguish between the two. Both links in this example are plain white which works great for a single button that does a single thing, but it’d be clearer that we’re dealing with two links if they were presented differently.

Jumping to conclusions

Are you using skip links on your site? Or, if not, does this convince you to use one? I hope it’s clear that skip links are a great value add when it comes to the accessibility of a site. While it’s not a silver bullet that solves every accessibility need, it does solve some use cases that make for a much more polished user experience.

Here are some prominent sites that are using this or a similar technique:

The post How to Create a “Skip to Content” Link appeared first on CSS-Tricks.

Don’t comma-separate :focus-within if you need deep browser support

I really like :focus-within. It's a super useful selector that allows you to essentially select a parent element when any of its children are in focus.

Say you wanted to reveal some extra stuff when a <div> is hovered...

div:hover .extra-stuff {
  /* reveal it */
}

That's not particularly keyboard-friendly. But if something in .extra-stuff is tab-able anyway (meaning it can be focused), that means you could write it like this to make it a bit more accessible:

div:hover .extra-stuff,
div:focus-within .extra-stuff {
  /* reveal it */
}

That's nice, but it causes a tricky problem.

Browsers ignore entire selectors if it doesn't understand any part of them. So, if you're dealing with a browser that doesn't support :focus-within then it would ignore the CSS example above, meaning you've also lost the :hover state.

Instead:

div:hover .extra-stuff {
  /* reveal it */
}
div:focus-within .extra-stuff {
  /* reveal it */
}

That is safer. But it's repetitive. If you have a preprocessor like Sass...

@mixin reveal {
  .extra-stuff {
     transform: translateY(0);
  }
}
div:hover {
  @include reveal;
}
div:focus-within {
  @include reveal;
}

See the Pen
Mixing for :focus-within without comma-separating
by Chris Coyier (@chriscoyier)
on CodePen.

I'd say it's a pretty good use-case for having native CSS mixins.

The post Don’t comma-separate :focus-within if you need deep browser support appeared first on CSS-Tricks.