Catalog · Navigation
Language switch
Lets readers go to the same page in another language, from a button in the header.
Fits best: any site — when the site comes in more than one language
Example
The language switch of a header, with English, Spanish and Arabic.
The links go to this site’s home page in each language. The list opens under the header, at the end of the row, as it does on the home page; this documentation is in English only.
Known issues
The list opens at a fixed place under the header, not under its button: placing it next to the button needs CSS anchor positioning, which is not yet available in every browser. Below 360 px wide the button shows the globe only, to keep the header on one row; the language’s name is still its accessible name, so a sighted reader has to recognise the globe.
It has not been tested with a screen reader yet.
Code
Copy the markup
import "@luz-ds/catalog/components/lang-switch.css";
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/catalog@0.3.0/dist/components/lang-switch.min.css" integrity="sha384-/CoO68kAJcVIGedf4xHY5JsMuUcSTkQs3MhJyc+jKvjJO1IN62ztsedeqNDvsVUj" crossorigin="anonymous">
One link per language, to the same page in that language, in the header of every translated page. On each page mark its own language with aria-current="page" and write the label in the page’s language.
<luz-lang-switch>
<button class="luz-lang-switch__button" type="button" popovertarget="lang-menu">
<span class="u-sr-only">Language: </span><span class="luz-lang-switch__name">English</span>
</button>
<nav class="luz-lang-switch__menu" id="lang-menu" popover aria-labelledby="lang-title">
<p class="luz-lang-switch__title" id="lang-title">Language</p>
<ul class="luz-lang-switch__list">
<li><a class="luz-lang-switch__link" href="/" hreflang="en" lang="en" aria-current="page">English</a></li>
<li><a class="luz-lang-switch__link" href="/es/" hreflang="es" lang="es">Español</a></li>
<li><a class="luz-lang-switch__link" href="/ar/" hreflang="ar" lang="ar">العربية</a></li>
</ul>
</nav>
</luz-lang-switch>
Variants and options
There are no variants. One custom property places the list.
| Tag, class or property | Effect |
|---|---|
<luz-lang-switch> | The block: holds the button and the list. A tag that no script defines, with no class and no role of its own |
.luz-lang-switch | A 44 px button with the current language’s name; filled while the list is open |
.luz-lang-switch | An optional globe before the name, drawn in the text colour. Decorative: aria-hidden="true" |
.luz-lang-switch | The current language’s name. Below 360 px it is clipped and the globe shows alone; it stays in the button’s name |
.luz-lang-switch | The popover: fixed under the header, at the end of the row (the left edge in right-to-left text) |
.luz-lang-switch | The visible name of the list |
.luz-lang-switch | The list of languages, without bullets |
.luz-lang-switch | One 44 px row per language. With aria-current: bold and underlined |
--luz-lang-switch-top | How far from the top of the screen the list opens. Defaults to --luz-bar-h, the height of a one-row bar of 44 px controls; set it on the tag if your header is taller. |
How it works
Changing language is going to another page, so the switch is a list of links. The button opens it with the native popovertarget, and @luz-ds/catalog only styles it: there is no script.
<luz-lang-switch> is a tag that no script ever defines; it only gives the block a name in the markup and in CSS, and adds no role: the button and the nav carry the meaning.
- Each language is named in that language, with
langso a screen reader pronounces it, andhreflangso the link says where it goes. - The list is a popover in the top layer, fixed under the header at the inline end; it mirrors in right-to-left text.
- The page itself declares its language and direction:
<html lang="ar" dir="rtl">. Add a<link rel="alternate" hreflang="…">per language in the<head>, so search engines find the others. - Core switches Arabic text to the system sans and removes letter spacing from it: the serif has no Arabic letters, and spacing breaks the joins of the script.
Without JavaScript
Nothing changes: the button opens the list with the popover API, and the links are links. In a browser without the popover API the button is hidden and the links show in a row.
Keyboard and ARIA
Tab to the button and press Enter or Space to open the list; Tab moves through the languages and Enter follows one. Escape, or a click outside, closes the list.
- The browser tells assistive technology whether the list is open, from
popovertarget. - The button’s name is “Language: English”: what it is, then the visible text (WCAG 2.5.3). Below 360 px only the globe shows, and the name is the same.
- The current language is a link with
aria-current="page".
Contrast and focus are in the Design tab.
Design
When to use
Use it on a site whose pages exist in two languages or more, in the header of every translated page, at the same place on each.
When not to use
Do not add it to pages that exist in one language only, even if the rest of the site is translated.
- Do not choose the language for the reader from the browser’s settings or their location: let them choose, and link each page to the others.
- Do not use it to translate the page in place with a script: each language is a page of its own, with its own address.
It replaces nothing in LUZ. It makes a <select> with a script that changes the address unnecessary.
Do and don't
Writing the content
Write each language’s name as its speakers write it: “English”, “Español”, “العربية”, never “EN” or a flag.
- Write the label in the page’s language: “Language”, “Idioma”, “اللغة”.
- List the languages in the same order on every page.
- If some pages are not translated, link to the closest page that is, and say in its language that the rest is in another one.
Contrast and focus
The button sits on the page; the list is a raised surface with a border and a shadow.
| Part | Colour on background | Light | Dark | High contrast | Needs |
|---|---|---|---|---|---|
Button text and globe | --luz-text-secondary on --luz-surface-page | 6.2:1 | 7.5:1 | 19.3:1 | 4.5:1 |
Button, hovered or open | --luz-text-primary on --luz-surface-muted | 15.8:1 | 15.1:1 | 20.1:1 | 4.5:1 |
List title | --luz-text-secondary on --luz-surface | 6.4:1 | 6.9:1 | 19.3:1 | 4.5:1 |
Language link | --luz-text-primary on --luz-surface | 16.5:1 | 14.0:1 | 21.0:1 | 4.5:1 |
Language link, hovered | --luz-text-primary on --luz-surface-muted | 15.8:1 | 15.1:1 | 20.1:1 | 4.5:1 |
Focus ring on the button | --luz-focus-ring on --luz-surface-page | 5.6:1 | 7.7:1 | 21.0:1 | 3:1 |
Focus ring on a link | --luz-focus-ring on --luz-surface | 5.9:1 | 7.2:1 | 21.0:1 | 3:1 |
Measured from the token values of each mode, rounded down. Needs is the WCAG 2.2 AA minimum: 4.5:1 for text, 3:1 for focus rings, borders and icons.
- Focus: a 2 px
--luz-focus-ringoutline, 2 px outside the button and just inside each link, so the list’s rounded corners never cut it. - The current language is bold and underlined, not only a colour.
Tokens it uses
The 20 tokens that the rules of catalog’s components/lang-switch.css read, by family. Each family links to its page, with the values in every mode. In Figma they are variables with the same names: see Tokens in Figma.
- Color
--luz-brand-primary,--luz-divider,--luz-focus-ring,--luz-surface,--luz-surface-hover,--luz-surface-muted,--luz-text-muted,--luz-text-primary,--luz-text-secondary- Spacing and sizing
--luz-control-lg,--luz-space-2xs,--luz-space-xs,--luz-space-sm,--luz-space-md- Typography
--luz-font-sans,--luz-text-sm,--luz-text-md- Radius
--luz-radius-md- Shadows
--luz-shadow-md- Layout and breakpoints
--luz-bar-h
Keyboard and ARIA are in the Code tab.