Language switch
Language switch — the same page in another language.
Examples
<div class="lang-switch">
<button class="btn btn-sm lang-switch-toggle" type="button" id="lang-two"
command="toggle-popover" commandfor="lang-two-menu">
<svg class="icon" aria-hidden="true"><use href="#icon-languages"></use></svg>
<span class="lang-switch-code">EN</span>
<span class="sr-only">Language: English</span>
<svg class="icon icon-sm" aria-hidden="true"><use href="#icon-chevron-down"></use></svg>
</button>
<div class="popover popover-align-end lang-switch-panel" id="lang-two-menu" popover data-ui="popover">
<nav class="nav nav-vertical" aria-label="Language">
<ul class="nav-list">
<li><a class="nav-link lang-switch-option" lang="en" hreflang="en" href="#en" aria-current="true">English</a></li>
<li><a class="nav-link lang-switch-option" lang="it" hreflang="it" href="#it">Italiano</a></li>
</ul>
</nav>
</div>
</div>Two links in a footer would have done, and that is exactly why this exists: there is no width at which a row of links becomes a list, so with a row the markup has to change somewhere between two languages and ten.
Here it does not. Each option is a real link because each is a different URL — middle-click, open in a new tab and hreflang all come free, and none of them survive a select that navigates on change.
<div class="lang-switch">
<button class="btn btn-sm lang-switch-toggle" type="button" id="lang-ten"
command="toggle-popover" commandfor="lang-ten-menu">
<svg class="icon" aria-hidden="true"><use href="#icon-languages"></use></svg>
<span class="lang-switch-code">EN</span>
<span class="sr-only">Language: English</span>
<svg class="icon icon-sm" aria-hidden="true"><use href="#icon-chevron-down"></use></svg>
</button>
<div class="popover popover-align-end lang-switch-panel" id="lang-ten-menu" popover data-ui="popover">
<nav class="nav nav-vertical" aria-label="Language">
<ul class="nav-list">
<li><a class="nav-link lang-switch-option" lang="en" hreflang="en" href="#en" aria-current="true">English</a></li>
<li><a class="nav-link lang-switch-option" lang="it" hreflang="it" href="#it">Italiano</a></li>
<li><a class="nav-link lang-switch-option" lang="de" hreflang="de" href="#de">Deutsch</a></li>
<li><a class="nav-link lang-switch-option" lang="fr" hreflang="fr" href="#fr">Français</a></li>
<li><a class="nav-link lang-switch-option" lang="es" hreflang="es" href="#es">Español</a></li>
<li><a class="nav-link lang-switch-option" lang="pt" hreflang="pt" href="#pt">Português</a></li>
<li><a class="nav-link lang-switch-option" lang="nl" hreflang="nl" href="#nl">Nederlands</a></li>
<li><a class="nav-link lang-switch-option" lang="pl" hreflang="pl" href="#pl">Polski</a></li>
<li><a class="nav-link lang-switch-option" lang="el" hreflang="el" href="#el">Ελληνικά</a></li>
<li><a class="nav-link lang-switch-option" lang="ja" hreflang="ja" href="#ja">日本語</a></li>
</ul>
</nav>
</div>
</div>The same markup, five times the languages, and the only difference is that the panel now scrolls: --lang-switch-max-block-size caps it at about seven rows so it cannot run off the bottom of a phone, and overscroll-behavior: contain stops the page moving underneath when you reach the end.
Every name is written in its own language and carries lang, which is what makes a screen reader pronounce "Français" in French instead of spelling it out in the page's voice.
Tokens 3
Level 2, declared on .lang-switch itself. Set any of them on that selector to restyle this component without touching the skin.
| Token | Default |
|---|---|
| --lang-switch-code-font-size | var(--font-size-xs) |
| --lang-switch-max-block-size | 17rem |
| --lang-switch-min-inline-size | 11rem |
Variants and states
Variants
None. It is one shape, and the page does the rest.
Inside it
.lang-switch-code.lang-switch-option.lang-switch-panel.lang-switch-toggle
State it reads
None.
Before you ship it
What you have to do 6 requirements
- Write each language's name in that language. "Deutsch", not "German". A reader who cannot read the current language cannot read the word for theirs in it, which is the entire failure this control exists to avoid.
- Put
langandhreflangon every option.langmakes a screen reader pronounce "Français" in French instead of spelling it in the page's voice;hreflangtells a crawler what is on the other end. They are two different statements and both are one attribute. - Give the trigger a full name. The visible
ITis an abbreviation and the icon isaria-hidden, so the button's accessible name comes from the.sr-onlyspan: "Language: Italiano". Without it the button announces as "I T". - Mark the current language with
aria-current="true", and mark exactly one. It is the accessible signal as well as the visual one.pageis wrong: the option is the same page, in another language. - Add
data-ui="popover"to the panel. Without it the trigger has noaria-expandedin any engine. - Link the same page, not the home page. A switcher that drops the reader on the front page of the other language has lost their place, and that is the most common defect this control has.
src/css/components/lang-switch.css · npx mostlycss add lang-switch