Skip to main content

Design system components

LanguageSwitcher

Compact language selector: a current-language trigger that fans out the other locales as a button group.

Status: stable. Group: navigation. Markdown version: /design-system/components/language-switcher.md

When to use

LanguageSwitcher is the site-chrome locale control: the current language shows as a compact button that fans out a tray of the other locales on demand. It is controlled; update your i18n instance in onLanguageChange and pass the new currentLang back. Each option's ariaLabel is the language's own name ("Suomi"), not an action phrase: the trigger and every option are named "<label>, <name>", and options carry lang so screen readers pronounce the name in that language. The closed tray is inert so hidden options can never take focus. Escape or an outside click closes it; tabbing out of the open tray does not.

When not to use

  • Do not use flags as the only label; flags name countries, not languages.

Look-alikes

The contract names no look-alike with a deciding condition.

Props

LanguageSwitcher props
PropTypeDefaultDescriptionRequired
languagesLanguageSwitcherOption[]noneAvailable languages: code (also set as lang), visible label and ariaLabel, the language's own name used in the "<label>, <name>" accessible name.Yes
currentLangstringnoneCurrently selected language code.Yes
onLanguageChange(code: string) => voidnoneCalled with the selected language code.Yes
classNamestringnoneOptional classes on the group wrapper.No
buttonClassNamestringshrink-0 whitespace-nowrap px-2.5 py-1.5 text-sm font-heading font-semibold uppercase tracking-widest transition-colors motion-reduce:transition-none cursor-pointer rounded-sm border border-transparent bg-transparent text-muted-foreground hover:text-foreground hover:bg-foreground/5Base class override applied to every language button.No
activeButtonClassNamestringtext-foreground hover:text-foregroundClass override for the current-language trigger.No
openTriggerClassNamestringbg-foreground/5 border-foreground text-foreground hover:border-foregroundClass override for the trigger while the tray is open.No
floatedButtonClassNamestringfont-semibold text-foreground/80 hover:text-foregroundClass override for the fanned-out tray options.No

Variants

No variant dimensions and no props with a fixed set of values.

Invalid combinations

The contract records no invalid combinations.

Keyboard

LanguageSwitcher keyboard behaviour
KeyBehaviour
TabMoves focus to the control and on to the next focusable element. (standard)
EnterActivates the focused control. (standard)
SpaceActivates or toggles the focused control. (standard)
EscapeCloses or dismisses the open layer. (standard)

Rows marked (standard) name a key the contract lists without a description; the behaviour shown is the usual one for that key.

Rules

Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.

LanguageSwitcher rules
LevelRuleHow it is checked
MUSTARIA: Role=group with aria-label.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Trigger aria-expanded + aria-controls.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTARIA: Each option is named '<label>, <language name>' (visible label first, WCAG 2.5.3) and carries lang.Not yet verified: no evidence record. WCAG 4.1.2.
MUSTRequired stories produce no axe violations at the configured severity.Automated by npm run test:stories:matrix:ci. WCAG 2.5.8.
MUSTThe accessibility tree (role, name, state) matches the committed snapshot for every required story.Automated by npm run test:stories:matrix:ci. WCAG 1.3.1, 4.1.2.
MUSTThe component remains legible and operable under a real browser forced-colors: active pass.Automated by npm run test:stories:hc.
MUSTEvery documented keyboard interaction works as specified.Not yet verified: no evidence record. WCAG 2.1.1, 2.1.2.
MUSTA human completed an end-to-end accessibility review of this component.Manual review, last on 2026-11-04.
SHOULDWrite each option's ariaLabel as the language's own name ("Suomi", "Svenska"), not an action phrase; the component builds the "<label>, <name>" accessible name from it.Not stated in the contract.
SHOULDKeep it controlled: change the i18n language inside onLanguageChange and feed the new currentLang back down.Not stated in the contract.
SHOULDUse short locale labels (EN/FI/SV); the fan tray is chrome, not a settings page.Not stated in the contract.
SHOULDTrust the built-in Escape, outside-click close, and inert tray; do not wrap it in another disclosure.Not stated in the contract.
SHOULD NOTDo not bury language choice in a settings modal on public pages; it is a first-visit need and belongs in the header.Not stated in the contract.

Install and import

  • Install: npm install @digitaltableteur/react (version 0.9.1, restricted access on npm).
  • Import: import { LanguageSwitcher } from "@digitaltableteur/react";
  • Entry point: @digitaltableteur/react/navigation exports it too.
  • Styles: import @digitaltableteur/react/style.css once at the app boundary.
  • Also available as the custom element <dt-language-switcher>; see the custom elements manifest.