react

Building Accessible Restaurant Menus in React

7 min
reacthospitalityweb developmentux

Building Accessible Restaurant Menus in React

April 5, 20267 min read
Building Accessible Restaurant Menus in React

How to build WCAG-compliant restaurant menus in React — semantic HTML, keyboard navigation, allergen markup, and Natasha's Law compliance.

One in Five of Your Diners Has an Accessibility Need

One in five people in the UK live with a disability. That includes visual impairments, motor conditions, dyslexia, cognitive differences, and more. Your restaurant menu — the single most important piece of content on your website — is also, statistically, one of the least accessible parts of any hospitality site.

The most common offenders we see are menus presented as PDFs, images, or low-contrast decorative fonts. For a screen reader user, these menus are either impossible to use or actively frustrating. For someone with low vision, a decorative serif font in light grey on white might look elegant — but it's effectively illegible.

The good news is that building an accessible restaurant menu in React is not difficult. It requires disciplined use of semantic HTML, a few ARIA attributes, and a proper understanding of what "accessible" actually means in practice. Here's how to do it right.

What "Accessible" Actually Means for a Restaurant Menu

Accessibility is often assumed to mean "works with screen readers." That's part of it, but WCAG 2.1 AA — the recognised standard for UK web accessibility — covers a broader set of requirements.

Perceivable

  • Text must be readable: a minimum contrast ratio of 4.5:1 for normal text, 3:1 for large text
  • Images of menus must have descriptive alt text — or, better, be replaced with actual HTML content
  • Content must not rely on colour alone to communicate meaning (e.g. "green = vegan" needs a text label too)

Operable

  • Every interactive element (filters, tabs, accordions) must be keyboard-navigable
  • Focus indicators must be visible — never hide the browser default outline without replacing it
  • Users must not be trapped in a component (e.g. a modal that can't be closed with Escape)

Understandable

  • Allergen information must be clearly labelled and predictably placed
  • Error messages (if allergen preferences are set) must be clear and specific

Robust

  • The markup must be valid and semantic — screen readers rely on the DOM structure to communicate the hierarchy of information

The most common accessibility violation we see on restaurant websites is a combination of: an image-based or PDF menu, low-contrast decorative text, and interactive filters that can't be reached by keyboard. All three are completely avoidable with proper development.

The PDF Menu: The Single Biggest Mistake

If your restaurant menu is a PDF, it is almost certainly inaccessible. PDFs can technically be made accessible through careful tagging in Adobe Acrobat, but this is rarely done, requires specialist knowledge, and still delivers a poor experience on mobile.

Beyond accessibility, a PDF menu is also an SEO problem. Search engines cannot easily index PDF content the same way as HTML. Your menu items — "Heritage tomato salad with aged balsamic", "24-hour braised short rib with celeriac purée" — are real search terms that real people use. If they're locked inside a PDF, Google can't surface them.

The fix is straightforward: build your menu in HTML. In React, this means a dedicated Menu component that renders structured data as semantic markup.

Building the Menu Component in React

Here's the foundational approach we use for accessible restaurant menu components.

Semantic HTML Structure

A menu is essentially a categorised list. Use the right HTML elements:

tsx
<section aria-labelledby="menu-heading">
  <h2 id="menu-heading">Our Menu</h2>

  {categories.map((category) => (
    <section key={category.id} aria-labelledby={`cat-${category.id}`}>
      <h3 id={`cat-${category.id}`}>{category.name}</h3>
      <ul>
        {category.items.map((item) => (
          <li key={item.id}>
            <article>
              <h4>{item.name}</h4>
              <p>{item.description}</p>
              <p aria-label={`Price: £${item.price}`}>£{item.price}</p>
              {item.allergens.length > 0 && (
                <AllergenList allergens={item.allergens} />
              )}
            </article>
          </li>
        ))}
      </ul>
    </section>
  ))}
</section>

Using <section> with aria-labelledby creates labelled regions that screen reader users can navigate between quickly using their virtual cursor. Using <ul> and <li> for menu items tells the browser — and assistive technology — that these items are members of a collection, not just visually grouped paragraphs.

Keyboard-Navigable Filters

Many restaurant menus include dietary filters: Vegan, Vegetarian, Gluten-Free, and so on. These filters are frequently implemented as styled <div> elements with click handlers — which means they're completely inaccessible to keyboard users.

The correct implementation uses <button> elements with toggle state:

tsx
<div role="group" aria-label="Filter by dietary requirement">
  {filters.map((filter) => (
    <button
      key={filter.id}
      onClick={() => toggleFilter(filter.id)}
      aria-pressed={activeFilters.includes(filter.id)}
      className={activeFilters.includes(filter.id) ? 'active' : ''}
    >
      {filter.label}
    </button>
  ))}
</div>

The aria-pressed attribute communicates toggle state to screen readers without any additional JavaScript. A screen reader will announce "Vegan, toggle button, pressed" or "unpressed" depending on state — exactly the right information for a user with food preferences or allergies.

Live Regions for Dynamic Updates

When a user applies a filter and the menu updates, a screen reader user needs to know something changed. Without a live region, the update is silent:

tsx
<div aria-live="polite" aria-atomic="true" className="sr-only">
  {filteredItems.length} dishes shown
</div>

aria-live="polite" announces the update after the current speech finishes. aria-atomic="true" ensures the whole message is read, not just the changed portion. The .sr-only class visually hides the element while keeping it in the DOM for assistive technology.

Allergen Accessibility: Natasha's Law and Beyond

The UK's Natasha's Law (in force since October 2021) requires all food businesses selling prepacked-for-direct-sale food to clearly label all 14 major allergens. For most restaurant websites, this extends to menu pages — customers with allergies have a legal right to clear, accessible allergen information.

The common mistake is marking allergens with icons only — a leaf for vegan, a wheat stalk for gluten, a nut icon for tree nuts. Icons without text labels fail WCAG Success Criterion 1.1.1 (Non-text Content).

The correct approach:

tsx
function AllergenList({ allergens }: { allergens: string[] }) {
  return (
    <ul aria-label="Contains allergens" className="allergen-list">
      {allergens.map((allergen) => (
        <li key={allergen}>
          <AllergenIcon allergen={allergen} aria-hidden="true" />
          <span>{allergen}</span>
        </li>
      ))}
    </ul>
  );
}

The icon is marked aria-hidden="true" — it's decorative. The visible text label carries the semantic meaning. A screen reader will announce "Contains allergens: Gluten, Nuts, Dairy" — exactly what a user with a food allergy needs to hear.

We helped a restaurant client in the South of England overhaul their allergen presentation after a diner with a nut allergy complained they couldn't reliably identify safe dishes on mobile. The fix took half a day. The result was clearer allergen information, better mobile usability, and a far lower legal risk exposure.

Colour Contrast: Where Restaurant Branding Often Fails

Restaurant branding tends towards the atmospheric: charcoal backgrounds, warm cream text, gold accents. These combinations often look beautiful and fail contrast requirements simultaneously.

The WCAG AA requirement of 4.5:1 contrast ratio for body text is non-negotiable if you want legal compliance — the Equality Act 2010 applies to websites in the UK. Use a tool like the WebAIM Contrast Checker to verify every text/background combination in your menu, especially any coloured allergen badges or price tags.

Common failures we see:

  • Light grey menu descriptions on white backgrounds (often 2:1 or worse)
  • Gold price text on ivory backgrounds
  • White text overlaid on a hero photograph with no sufficient darkening overlay

The fix isn't always to abandon your brand palette — it's to ensure sufficient contrast while respecting the visual identity. Often this means darkening a background by 10–15% or using a slightly bolder weight for body copy.

Key Takeaways

Building an accessible restaurant menu in React comes down to a set of consistent, testable decisions:

  • Replace PDF menus with HTML. Full stop. There is no accessible, SEO-friendly alternative.
  • Use semantic HTML: <section>, <h2><h4>, <ul>, <li>, <article>. The hierarchy matters.
  • **Filter buttons must be <button> elements** with aria-pressed to communicate toggle state.
  • Add a live region so screen reader users know when the menu updates after filtering.
  • Label allergens with visible text, not icons alone. Icons are decorative; labels carry meaning.
  • Verify colour contrast — 4.5:1 minimum for body text. Check every combination.
  • Test with a keyboard. Tab through every interactive element. If you can't reach it, neither can a keyboard-only user.
  • Test with a screen reader. VoiceOver on macOS is free and takes five minutes to learn the basics.

Making Your Menu Work for Everyone

An accessible menu isn't just about legal compliance. It's about making sure every potential diner — including the one in five with an accessibility need — can read your menu, filter it to their dietary requirements, and find the information they need without friction.

We've seen restaurants lose bookings not because of price or location, but because a diner with a serious allergy couldn't reliably identify safe dishes on the website. That's a fixable problem, and fixing it has the useful side effect of improving your SEO and your mobile experience at the same time.

At LogicLeap, every restaurant menu we build is accessible by default: semantic HTML, keyboard-navigable filters, WCAG-compliant contrast, and properly marked allergen information. If your current site has a PDF menu or low-contrast decorative text, get in touch — we'll audit it and tell you exactly what needs to change. Your whole audience deserves a menu they can actually read.

Need help implementing this?

We build high-performance websites and automate workflows for ambitious brands. Let's talk about how we can help your business grow.