Accessibility pattern · Navigation

Language switcher

Every language is written in itself, in its own script, so people can find their own, and each name carries lang so a screen reader says it in the right voice. It is a disclosure, not a menu: a button with aria-expanded opens an ordinary list of links, Tab moves through them, and Escape closes it.

WCAG criteria
6
Keyboard rules
5
Checked with
axe, keyboard and the inspector

Live demo

Try it

Use it with a mouse, a keyboard or a screen reader. The inspector beside it shows what the browser tells assistive technology as you go: focus, state changes and announcements.

Hello

Weekend treks in the Sahyadris.

Page language lang="en"

The demo works without JavaScript only as far as its HTML does; the inspector needs JavaScript.

Keyboard

Keys it answers to

Every action works without a pointer.

KeyWhat it does
Enter or SpaceOn the language button, opens or closes the list; focus stays on the button.
Tab or ShiftTabMoves through the languages; leaving the list closes it.
Arrow Down or Arrow UpWhile the list is open, moves to the next or previous language, as an extra to Tab.
EscapeCloses the list and returns focus to the button.
EnterOn a language, switches to it (in the demo, the page below changes) and returns focus to the button.

Screen readers

What it announces

Written from the roles, names and states in the markup.

WhenExpected announcement
Tab reaches the buttonLanguage: English, button, collapsed
Enter opens the listExpanded
Tab reaches the current languageEnglish, current, link
Tab reaches HindiThe Hindi name, in a Hindi voice where one is installed, link
Tamil is chosenLanguage: the Tamil name, in a Tamil voice, button, collapsed

These are expected announcements, not recordings. Wording and order differ between screen readers and browsers.

Code

Copy the code

The exact files this demo runs on. The styles are served with a prefix that keeps this site's own styles out of the demo; what you copy is the original.

The markup

<div class="ap-lang" data-ap-lang>
  <div class="ap-lang__bar">
    <span class="ap-lang__brand">
      <span class="ap-lang__logo" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="m3 19 6.5-12 4 7 2.5-4L21 19Z"/></svg></span>
      <span translate="no">Sahyadri Treks</span>
    </span>
    <div class="ap-lang__switch">
      <button type="button" class="ap-lang__button" aria-expanded="false" aria-controls="lang-list">
        <svg class="ap-lang__globe" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c2.5 2.6 3.8 5.6 3.8 9s-1.3 6.4-3.8 9c-2.5-2.6-3.8-5.6-3.8-9S9.5 5.6 12 3Z"/></svg>
        <span class="ap-lang__vh">Language:</span>
        <span class="ap-lang__current" lang="en" translate="no">English</span>
        <svg class="ap-lang__chevron" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m6 9 6 6 6-6"/></svg>
      </button>
      <ul class="ap-lang__list" id="lang-list" translate="no" hidden>
          <li><a class="ap-lang__link" href="/en/" hreflang="en" lang="en" aria-current="true"><span>English</span><svg class="ap-lang__check" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></a></li>
          <li><a class="ap-lang__link" href="/hi/" hreflang="hi" lang="hi"><span>हिन्दी</span><svg class="ap-lang__check" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></a></li>
          <li><a class="ap-lang__link" href="/ta/" hreflang="ta" lang="ta"><span>தமிழ்</span><svg class="ap-lang__check" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></a></li>
          <li><a class="ap-lang__link" href="/bn/" hreflang="bn" lang="bn"><span>বাংলা</span><svg class="ap-lang__check" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></a></li>
          <li><a class="ap-lang__link" href="/es/" hreflang="es" lang="es"><span>Español</span><svg class="ap-lang__check" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></a></li>
      </ul>
    </div>
  </div>
  <div class="ap-lang__page" lang="en" translate="no">
    <div class="ap-lang__copy">
      <p class="ap-lang__hello">Hello</p>
      <p class="ap-lang__line">Weekend treks in the Sahyadris.</p>
    </div>
    <svg class="ap-lang__art" viewBox="0 0 160 110" aria-hidden="true" focusable="false"><circle class="ap-lang__sun" cx="118" cy="30" r="14"/><path class="ap-lang__far" d="M0 110 38 52l22 26 34-46 66 78Z"/><path class="ap-lang__near" d="M0 110 50 70l26 18 26-24 58 46Z"/></svg>
  </div>
  <p class="ap-lang__attr"><span>Page language</span> <code class="ap-lang__code">lang="en"</code></p>
</div>

WCAG 2.2

What it meets

The success criteria this pattern takes care of, and how.

  • 1.3.1 Info and Relationships Level A

    The languages are a list of links straight after the button that controls them, and the current one is marked in the markup, not just drawn.

  • 2.1.1 Keyboard Level A

    The button, every language and closing the list all work from the keyboard.

  • 2.4.3 Focus Order Level A

    Escape and choosing a language both return focus to the button, so nobody is left in a list that has closed.

  • 3.1.1 Language of Page Level A

    Each language's page sets its own lang; the demo changes the lang of the page area when you choose.

  • 3.1.2 Language of Parts Level AA

    Every language name carries lang, so screen readers switch pronunciation for the Hindi, Tamil and Bengali names.

  • 4.1.2 Name, Role, Value Level A

    The button reports expanded or collapsed, and the current language reports current.

Usage

When to use it

Use it

  • Sites published in several languages, where every page has versions to move between.
  • A header or footer control that should stay small until it is needed.

Use something else

  • Two languages only: a single link to the other one, written in that language, is simpler.
  • Choosing the language of content people are writing: use a select in the form.
  • Changing the language by country or flag: a country is not a language.

Common failures

How it usually goes wrong

  • Names in the current page's language

    Someone who reads only Tamil may not recognise "Tamil" in English. Each language is written in its own script here.

  • No lang on the names

    Without lang, a screen reader reads the Hindi name with English rules, or skips it. lang="hi" switches the voice.

  • Flags for languages

    Flags stand for countries: Spanish is spoken in many, India has many languages. Flags also mean nothing to a screen reader.

  • A menu role on a list of links

    role=menu promises an application menu with arrow keys only. Language links are navigation: a button and a list of links.

  • A select that jumps on change

    Loading a new page as soon as a select changes surprises keyboard users who are only looking through the options. Links go only when chosen.

  • A list that will not close

    If Escape does nothing and focus can wander off with the list still open, it covers the page. Escape, leaving it, or clicking away closes it.

Notes

Building it

  • hreflang tells browsers and search engines the language of the page a link leads to; lang tells screen readers how to say the link text. Use both.
  • The names, and the current language on the button, carry translate="no" so translation tools do not turn "Español" into "Spanish".
  • In a real site each link opens the same page in that language, and that page sets lang on html. The demo stands in for it by changing the area below.
  • The visually hidden "Language:" in the button names what it is for; the visible language name is part of that name, so voice control still works.

Sources: W3C Internationalization: Declaring language in HTML · WAI-ARIA Authoring Practices: Disclosure navigation menu example

Checked with axe in light and dark themes, at desktop and phone widths, and by keyboard. Not yet tested with every screen reader and browser pair. Report a correction