LanguageSwitcher
ImplementedEN/FR bilingual toggle with instant language switch — inline EN | FR pair (header default) or compact single button showing the target locale (ADR-038: fab retired). Controlled and stateless: the consumer owns URL-segment navigation and the document language.
components/molecules/languageswitcher/LanguageSwitcher.tsxEN/FR bilingual toggle with instant language switch. Two variants: inline (header, default) and compact (single-button); the fab variant was retired in ADR-038 (2026-09-17). D-HOME-P4b 2026-08-11 (test updated for new API).
LanguageSwitcher
Family 7 — Commerce Molecules
Bilingual switch with two visual variants. Inline shows EN | FR pair (default for header). Compact shows single button with the OTHER locale. All variants are WCAG 2.5.5 compliant (44x44px tap target). Controlled and stateless — the consumer owns URL-segment navigation and document.documentElement.lang. The fab variant was retired in ADR-038 (2026-09-17); mobile language switching is owned by the MobileNav drawer.
Inline (default, EN active)
Inline (FR active — aria-current flips)
Compact
Controlled (onLocaleChange fires)
Controlled — click a button: onLocaleChange fires, aria-current moves (active: en).
Usage
import { LanguageSwitcher } from '@epasslive/design-system'
// Header (desktop)
<LanguageSwitcher
currentLocale={locale}
onLocaleChange={switchLocale}
groupLabel={t('switchLanguage')}
/>
// Compact (single button showing the other locale)
<LanguageSwitcher
currentLocale={locale}
onLocaleChange={switchLocale}
variant="compact"
/>Integration — what the consumer owns
The molecule is display/interaction only. A correct integration (1) navigates the URL locale segment on onLocaleChange (e.g. router.replace(pathname, { locale: next })) and (2) sets document.documentElement.lang. Note: the document-language half is currently broken app-wide (every /fr route serves <html lang="en">) — the app-level fix is tracked separately. Persistence (URL/cookie) is also consumer-side; the molecule keeps no state. The built-in per-button strings ("switch to", ", current") are English-only — localize groupLabel per page locale.
Atom vs molecule
Prefer this molecule for header/nav locale switching: aria-current group semantics and BCP 47 casing ("en" | "fr"). The LanguageToggle atom is a minimal single-token toggle (aria-pressed, uppercase 'FR' | 'EN') — its compact overlap with this molecule is a pending consolidation ADR. New integrations should use the molecule.
D-HOME-P4b changelog (2026-08-11)
- Updated test name: "DS-CORR-07-03: LanguageSwitcher active locale uses bg-ep-btn-primary (AA contrast)"
- Variant contract history: D-HOME-P4b set "inline" | "compact" | "fab"; ADR-038 (2026-09-17) retired "fab" — the union is now "inline" | "compact"
Accessibility
Native buttons with aria-label describing the language switch action. Active button has aria-current="true". 44x44px min tap target via min-h-[var(--ep-tap-min)] + min-w-[var(--ep-tap-min)]. Each button carries lang={opt.locale} (WCAG 3.1.2).
Props
currentLocale*'en' | 'fr'Currently active locale. The matching button gets aria-current="true".
onLocaleChange(locale: LanguageSwitcherLocale) => voidCalled with the next locale when the user activates a button. Omit for display-only rendering.
optionsLanguageSwitcherOption[]Override the default EN/FR options.
variant'inline' | 'compact'inlineVisual variant — inline (default), compact (single button); the fab variant was retired in ADR-038.
groupLabelstringAccessible label for the outer group (rendered when variant is inline).
classNamestringAdditional classes appended to the outermost element.
data-testidstringForwarded to the outer element.
Used By
storefront/src/components/layout/SiteHeader.tsx— inline (desktop, hidden below md)storefront/src/components/layout/MobileNavDrawer.tsx— inlinestorefront/src/components/docs/LanguageSwitcher.tsx— docs-site topbar wrapper (inline)
Props
LanguageSwitcher
currentLocale'en' | 'fr'—Currently active locale. The matching button gets aria-current="true".
onLocaleChange(locale: 'en' | 'fr') => void—Called with the next locale when the user activates a button. Omit for a display-only rendering.
optionsLanguageSwitcherOption[]EN/FR defaultsOverride the default EN/FR options ({ locale, shortLabel, nativeName, englishName } per entry; both locales must be present).
variant"inline" | "compact"'inline'Visual variant: inline — text-only EN | FR pair (header use); compact — single button showing the other locale. ADR-038 retired the former fab value (mobile floating action button).
groupLabelstring'Language switcher'Accessible label for the outer group (rendered when variant is inline). Localize per page locale.
classNamestring''Extra classes appended to the outermost element.
data-testidstring—Forwarded to the outer element (data-* / id).
<LanguageSwitcher />Accessibility
ARIA
1 ruleType
1 rulevalidation
1 ruleUsage
Import
import { LanguageSwitcher } from "@epasslive/design-system"Example 1
<LanguageSwitcher currentLocale="en" />Example 2
<LanguageSwitcher currentLocale={locale} variant="compact" onLocaleChange={switchLocale} />Anti-patterns
— Do not hardcode data in molecules
— Avoid complex business logic in molecules