Components · Navigation
Language switcher
The page in other languages as links with lang and hreflang, behind one button; the current one is marked.
1.5% of the 2,062 sites in The UI Index have a dropdown (30 sites).
© 2026 Caseroom
© 2026 Caseroom
Both previews render the same markup; the HTML tab below is that markup without a framework.
Install
- React
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ language-switcher.json - Astro
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ astro/ language-switcher.json
Or copy the files below into your project. They need clsx, tailwind-merge, and the Caseroom theme. The HTML needs only the compiled CSS and the script.
// Language switcher behaviour for Astro, HTML and React. The trigger has popovertarget="<list id>" and
// data-lang-trigger; the list is [popover][data-lang-list] with links. Opening focuses the current language;
// Up/Down/Home/End move between the links, Tab moves on as usual and closes the list once focus leaves it, Escape
// and outside clicks close it (native popover) and focus returns to the trigger. aria-expanded follows the list.
declare global {
interface Window {
__caseroomLang?: boolean;
}
}
const linksOf = (list: HTMLElement) => [...list.querySelectorAll<HTMLAnchorElement>('a[href]')];
const triggerOf = (list: HTMLElement) => document.querySelector<HTMLElement>(`[popovertarget="${CSS.escape(list.id)}"]`);
const anchored = typeof CSS !== 'undefined' && CSS.supports('anchor-name: --a');
/** Without CSS anchor positioning: under the trigger, flipped up when there is no room below. */
function place(list: HTMLElement, trigger: HTMLElement) {
if (anchored) return;
const t = trigger.getBoundingClientRect();
const m = list.getBoundingClientRect();
const end = list.dataset.align === 'end';
const below = t.bottom + 4 + m.height <= innerHeight || t.top < m.height;
list.style.position = 'fixed';
list.style.top = `${below ? t.bottom + 4 : t.top - 4 - m.height}px`;
list.style.left = `${Math.max(8, Math.min(end ? t.right - m.width : t.left, innerWidth - m.width - 8))}px`;
}
if (typeof window !== 'undefined' && !window.__caseroomLang) {
window.__caseroomLang = true;
document.addEventListener(
'toggle',
(e) => {
const list = e.target as HTMLElement;
if (!(list instanceof HTMLElement) || !list.hasAttribute('data-lang-list')) return;
const open = (e as ToggleEvent).newState === 'open';
const trigger = triggerOf(list);
trigger?.setAttribute('aria-expanded', String(open));
if (open) {
if (trigger) place(list, trigger);
const links = linksOf(list);
(links.find((a) => a.getAttribute('aria-current') === 'true') ?? links[0])?.focus();
} else if (list.contains(document.activeElement) || document.activeElement === document.body) trigger?.focus();
},
true,
);
document.addEventListener('keydown', (e) => {
const list = (e.target as HTMLElement).closest?.<HTMLElement>('[data-lang-list]');
if (!list) return;
const links = linksOf(list);
const i = links.indexOf(e.target as HTMLAnchorElement);
let next: HTMLElement | undefined;
if (e.key === 'ArrowDown') next = links[(i + 1) % links.length];
else if (e.key === 'ArrowUp') next = links[(i - 1 + links.length) % links.length];
else if (e.key === 'Home') next = links[0];
else if (e.key === 'End') next = links.at(-1);
if (next) {
e.preventDefault();
next.focus();
}
});
// Tab past the first or last link: the list closes, focus goes where Tab took it.
document.addEventListener('focusout', (e) => {
const list = (e.target as HTMLElement).closest?.<HTMLElement>('[data-lang-list]');
const to = (e as FocusEvent).relatedTarget as Node | null;
if (!list || !to || list.contains(to) || to === triggerOf(list)) return;
if (list.matches(':popover-open')) list.hidePopover();
});
}
export {};import { cn } from '@/lib/utils';
import { buttonVariants, type ButtonVariants } from '../button/button.variants';
import './language-switcher.client';
import { CHECK_PATH, CHEVRON_PATH, GLOBE_PATH, languageSwitcher as c, type Language } from './language-switcher.variants';
export type { Language };
export interface LanguageSwitcherProps {
/** Unique on the page: the list's id. */
id: string;
/** What is switched, read before the current value: "Language" or "Region". */
label?: string;
languages: Language[];
/** The code of the page's language. */
current: string;
/** Line the list up with the trigger's start or end. */
align?: 'start' | 'end';
variant?: ButtonVariants['variant'];
size?: ButtonVariants['size'];
className?: string;
}
/** A disclosure button with the page's other languages as links (lang, hreflang, the current one marked).
* Opening focuses the current language; arrows and Tab move; Escape closes and returns focus. */
export function LanguageSwitcher({ id, label = 'Language', languages, current, align = 'start', variant = 'outline', size, className }: LanguageSwitcherProps) {
const now = languages.find((l) => l.code === current) ?? languages[0];
const svg = { viewBox: '0 0 16 16', fill: 'none', stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true } as const;
return (
<div className={cn(c.root, className)}>
<button type="button" popoverTarget={id} aria-expanded="false" data-lang-trigger="" className={cn(buttonVariants({ variant, size }), 'group')} style={{ anchorName: `--${id}` } as React.CSSProperties}>
<svg {...svg} className={c.globe}><path d={GLOBE_PATH} /></svg>
<span className="sr-only">{label}: </span><span lang={now.code}>{now.name}</span>
<svg {...svg} className={c.chevron}><path d={CHEVRON_PATH} /></svg>
</button>
<nav id={id} popover="auto" aria-label={label} data-lang-list="" data-align={align} className={c.content} style={{ positionAnchor: `--${id}` } as React.CSSProperties}>
<ul role="list" className={c.list}>
{languages.map((l) => (
<li key={l.code}>
<a href={l.href} lang={l.code} hrefLang={l.code} aria-current={l.code === now.code ? 'true' : undefined} className={c.link}>
<svg {...svg} className={c.check}><path d={CHECK_PATH} /></svg>
{l.name}
<span aria-hidden="true" className={c.code}>{l.code}</span>
</a>
</li>
))}
</ul>
</nav>
</div>
);
}/** A language (or region) switcher: a disclosure button that opens a list of links on the native popover, each
* link in its own language with lang and hreflang, the current one marked. Links, not a menu: Tab moves through
* them as through any navigation, and the arrows move too. */
export const languageSwitcher = {
root: 'inline-block',
// Under the trigger, flipped when there is no room (CSS anchor positioning; the script places it where that is missing).
content:
'm-0 [inset:auto] supports-[anchor-name:--a]:mt-1 supports-[anchor-name:--a]:[top:anchor(bottom)] supports-[anchor-name:--a]:[left:anchor(left)] supports-[anchor-name:--a]:[position-try-fallbacks:flip-block,flip-inline] supports-[anchor-name:--a]:data-[align=end]:[left:auto] supports-[anchor-name:--a]:data-[align=end]:[right:anchor(right)] min-w-52 rounded-lg border bg-popover p-1 text-popover-foreground shadow-lg dark:shadow-[0_12px_32px_rgb(0_0_0/0.65)] opacity-0 transition-[opacity,display,overlay] transition-discrete duration-100 open:opacity-100 starting:open:opacity-0',
list: 'grid',
link: 'group flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm whitespace-nowrap outline-none transition-colors select-none hover:bg-accent focus-visible:bg-accent focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring active:bg-pressed aria-[current=true]:font-medium [&_svg]:size-4 [&_svg]:shrink-0',
check: 'invisible group-aria-[current=true]:visible',
code: 'ml-auto pl-4 text-xs font-normal text-muted-foreground uppercase tabular-nums',
globe: 'text-muted-foreground',
chevron: 'size-3.5 text-muted-foreground transition-transform duration-150 group-aria-expanded:rotate-180',
};
export type Language = {
/** BCP 47 code, e.g. "nl" or "pt-BR": the link's lang and hreflang. */
code: string;
/** The language's name in that language, e.g. "Nederlands". */
name: string;
/** The same page in that language. */
href: string;
};
export const GLOBE_PATH = 'M8 14.5a6.5 6.5 0 1 0 0-13 6.5 6.5 0 0 0 0 13zM1.5 8h13M8 1.5c1.7 1.8 2.5 4 2.5 6.5S9.7 12.7 8 14.5C6.3 12.7 5.5 10.5 5.5 8S6.3 3.3 8 1.5z';
export const CHECK_PATH = 'M3.5 8.5l3 3 6-7';
export const CHEVRON_PATH = 'M4 6l4 4 4-4';---
import { cn } from '@/lib/utils';
import { buttonVariants, type ButtonVariants } from '../button/button.variants';
import { CHECK_PATH, CHEVRON_PATH, GLOBE_PATH, languageSwitcher as c, type Language } from './language-switcher.variants';
interface Props {
/** Unique on the page: the list's id. */
id: string;
/** What is switched, read before the current value: "Language" or "Region". */
label?: string;
languages: Language[];
/** The code of the page's language. */
current: string;
/** Line the list up with the trigger's start or end. */
align?: 'start' | 'end';
variant?: ButtonVariants['variant'];
size?: ButtonVariants['size'];
class?: string;
}
const { id, label = 'Language', languages, current, align = 'start', variant = 'outline', size, class: className } = Astro.props;
const now = languages.find((l) => l.code === current) ?? languages[0];
const svg = { viewBox: '0 0 16 16', fill: 'none', stroke: 'currentColor', 'stroke-width': '1.5', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', 'aria-hidden': 'true' };
---
<div class={cn(c.root, className)}>
<button type="button" popovertarget={id} aria-expanded="false" data-lang-trigger class={cn(buttonVariants({ variant, size }), 'group')} style={`anchor-name: --${id}`}>
<svg {...svg} class={c.globe}><path d={GLOBE_PATH} /></svg>
<span class="sr-only">{label}: </span><span lang={now.code}>{now.name}</span>
<svg {...svg} class={c.chevron}><path d={CHEVRON_PATH} /></svg>
</button>
<nav id={id} popover="auto" aria-label={label} data-lang-list data-align={align} class={c.content} style={`position-anchor: --${id}`}>
<ul role="list" class={c.list}>
{
languages.map((l) => (
<li>
<a href={l.href} lang={l.code} hreflang={l.code} aria-current={l.code === now.code ? 'true' : undefined} class={c.link}>
<svg {...svg} class={c.check}><path d={CHECK_PATH} /></svg>
{l.name}
<span aria-hidden="true" class={c.code}>{l.code}</span>
</a>
</li>
))
}
</ul>
</nav>
</div>
<script>
import './language-switcher.client';
</script>// Language switcher behaviour for Astro, HTML and React. The trigger has popovertarget="<list id>" and
// data-lang-trigger; the list is [popover][data-lang-list] with links. Opening focuses the current language;
// Up/Down/Home/End move between the links, Tab moves on as usual and closes the list once focus leaves it, Escape
// and outside clicks close it (native popover) and focus returns to the trigger. aria-expanded follows the list.
declare global {
interface Window {
__caseroomLang?: boolean;
}
}
const linksOf = (list: HTMLElement) => [...list.querySelectorAll<HTMLAnchorElement>('a[href]')];
const triggerOf = (list: HTMLElement) => document.querySelector<HTMLElement>(`[popovertarget="${CSS.escape(list.id)}"]`);
const anchored = typeof CSS !== 'undefined' && CSS.supports('anchor-name: --a');
/** Without CSS anchor positioning: under the trigger, flipped up when there is no room below. */
function place(list: HTMLElement, trigger: HTMLElement) {
if (anchored) return;
const t = trigger.getBoundingClientRect();
const m = list.getBoundingClientRect();
const end = list.dataset.align === 'end';
const below = t.bottom + 4 + m.height <= innerHeight || t.top < m.height;
list.style.position = 'fixed';
list.style.top = `${below ? t.bottom + 4 : t.top - 4 - m.height}px`;
list.style.left = `${Math.max(8, Math.min(end ? t.right - m.width : t.left, innerWidth - m.width - 8))}px`;
}
if (typeof window !== 'undefined' && !window.__caseroomLang) {
window.__caseroomLang = true;
document.addEventListener(
'toggle',
(e) => {
const list = e.target as HTMLElement;
if (!(list instanceof HTMLElement) || !list.hasAttribute('data-lang-list')) return;
const open = (e as ToggleEvent).newState === 'open';
const trigger = triggerOf(list);
trigger?.setAttribute('aria-expanded', String(open));
if (open) {
if (trigger) place(list, trigger);
const links = linksOf(list);
(links.find((a) => a.getAttribute('aria-current') === 'true') ?? links[0])?.focus();
} else if (list.contains(document.activeElement) || document.activeElement === document.body) trigger?.focus();
},
true,
);
document.addEventListener('keydown', (e) => {
const list = (e.target as HTMLElement).closest?.<HTMLElement>('[data-lang-list]');
if (!list) return;
const links = linksOf(list);
const i = links.indexOf(e.target as HTMLAnchorElement);
let next: HTMLElement | undefined;
if (e.key === 'ArrowDown') next = links[(i + 1) % links.length];
else if (e.key === 'ArrowUp') next = links[(i - 1 + links.length) % links.length];
else if (e.key === 'Home') next = links[0];
else if (e.key === 'End') next = links.at(-1);
if (next) {
e.preventDefault();
next.focus();
}
});
// Tab past the first or last link: the list closes, focus goes where Tab took it.
document.addEventListener('focusout', (e) => {
const list = (e.target as HTMLElement).closest?.<HTMLElement>('[data-lang-list]');
const to = (e as FocusEvent).relatedTarget as Node | null;
if (!list || !to || list.contains(to) || to === triggerOf(list)) return;
if (list.matches(':popover-open')) list.hidePopover();
});
}
export {};/** A language (or region) switcher: a disclosure button that opens a list of links on the native popover, each
* link in its own language with lang and hreflang, the current one marked. Links, not a menu: Tab moves through
* them as through any navigation, and the arrows move too. */
export const languageSwitcher = {
root: 'inline-block',
// Under the trigger, flipped when there is no room (CSS anchor positioning; the script places it where that is missing).
content:
'm-0 [inset:auto] supports-[anchor-name:--a]:mt-1 supports-[anchor-name:--a]:[top:anchor(bottom)] supports-[anchor-name:--a]:[left:anchor(left)] supports-[anchor-name:--a]:[position-try-fallbacks:flip-block,flip-inline] supports-[anchor-name:--a]:data-[align=end]:[left:auto] supports-[anchor-name:--a]:data-[align=end]:[right:anchor(right)] min-w-52 rounded-lg border bg-popover p-1 text-popover-foreground shadow-lg dark:shadow-[0_12px_32px_rgb(0_0_0/0.65)] opacity-0 transition-[opacity,display,overlay] transition-discrete duration-100 open:opacity-100 starting:open:opacity-0',
list: 'grid',
link: 'group flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm whitespace-nowrap outline-none transition-colors select-none hover:bg-accent focus-visible:bg-accent focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring active:bg-pressed aria-[current=true]:font-medium [&_svg]:size-4 [&_svg]:shrink-0',
check: 'invisible group-aria-[current=true]:visible',
code: 'ml-auto pl-4 text-xs font-normal text-muted-foreground uppercase tabular-nums',
globe: 'text-muted-foreground',
chevron: 'size-3.5 text-muted-foreground transition-transform duration-150 group-aria-expanded:rotate-180',
};
export type Language = {
/** BCP 47 code, e.g. "nl" or "pt-BR": the link's lang and hreflang. */
code: string;
/** The language's name in that language, e.g. "Nederlands". */
name: string;
/** The same page in that language. */
href: string;
};
export const GLOBE_PATH = 'M8 14.5a6.5 6.5 0 1 0 0-13 6.5 6.5 0 0 0 0 13zM1.5 8h13M8 1.5c1.7 1.8 2.5 4 2.5 6.5S9.7 12.7 8 14.5C6.3 12.7 5.5 10.5 5.5 8S6.3 3.3 8 1.5z';
export const CHECK_PATH = 'M3.5 8.5l3 3 6-7';
export const CHEVRON_PATH = 'M4 6l4 4 4-4';<div class="grid w-full max-w-md gap-6">
<div class="inline-block">
<button type="button" popovertarget="ex-lang" aria-expanded="false" data-lang-trigger class="inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,background-color,border-color,transform] select-none active:scale-[0.97] aria-busy:cursor-progress disabled:pointer-events-none disabled:opacity-50 aria-busy:disabled:opacity-80 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border border-input bg-background hover:bg-accent hover:text-accent-foreground active:bg-pressed h-9 px-4 group" style="anchor-name: --ex-lang">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="text-muted-foreground"><path d="M8 14.5a6.5 6.5 0 1 0 0-13 6.5 6.5 0 0 0 0 13zM1.5 8h13M8 1.5c1.7 1.8 2.5 4 2.5 6.5S9.7 12.7 8 14.5C6.3 12.7 5.5 10.5 5.5 8S6.3 3.3 8 1.5z"></path></svg>
<span class="sr-only">Language: </span>
<span lang="en">English</span>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="size-3.5 text-muted-foreground transition-transform duration-150 group-aria-expanded:rotate-180"><path d="M4 6l4 4 4-4"></path></svg>
</button>
<nav id="ex-lang" popover="auto" aria-label="Language" data-lang-list data-align="start" class="m-0 [inset:auto] supports-[anchor-name:--a]:mt-1 supports-[anchor-name:--a]:[top:anchor(bottom)] supports-[anchor-name:--a]:[left:anchor(left)] supports-[anchor-name:--a]:[position-try-fallbacks:flip-block,flip-inline] supports-[anchor-name:--a]:data-[align=end]:[left:auto] supports-[anchor-name:--a]:data-[align=end]:[right:anchor(right)] min-w-52 rounded-lg border bg-popover p-1 text-popover-foreground shadow-lg dark:shadow-[0_12px_32px_rgb(0_0_0/0.65)] opacity-0 transition-[opacity,display,overlay] transition-discrete duration-100 open:opacity-100 starting:open:opacity-0" style="position-anchor: --ex-lang">
<ul role="list" class="grid">
<li>
<a href="#en" lang="en" hreflang="en" aria-current="true" class="group flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm whitespace-nowrap outline-none transition-colors select-none hover:bg-accent focus-visible:bg-accent focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring active:bg-pressed aria-[current=true]:font-medium [&_svg]:size-4 [&_svg]:shrink-0">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="invisible group-aria-[current=true]:visible"><path d="M3.5 8.5l3 3 6-7"></path></svg>
English
<span aria-hidden="true" class="ml-auto pl-4 text-xs font-normal text-muted-foreground uppercase tabular-nums">en</span>
</a>
</li>
<li>
<a href="#nl" lang="nl" hreflang="nl" class="group flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm whitespace-nowrap outline-none transition-colors select-none hover:bg-accent focus-visible:bg-accent focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring active:bg-pressed aria-[current=true]:font-medium [&_svg]:size-4 [&_svg]:shrink-0">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="invisible group-aria-[current=true]:visible"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Nederlands
<span aria-hidden="true" class="ml-auto pl-4 text-xs font-normal text-muted-foreground uppercase tabular-nums">nl</span>
</a>
</li>
<li>
<a href="#de" lang="de" hreflang="de" class="group flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm whitespace-nowrap outline-none transition-colors select-none hover:bg-accent focus-visible:bg-accent focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring active:bg-pressed aria-[current=true]:font-medium [&_svg]:size-4 [&_svg]:shrink-0">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="invisible group-aria-[current=true]:visible"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Deutsch
<span aria-hidden="true" class="ml-auto pl-4 text-xs font-normal text-muted-foreground uppercase tabular-nums">de</span>
</a>
</li>
<li>
<a href="#fr" lang="fr" hreflang="fr" class="group flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm whitespace-nowrap outline-none transition-colors select-none hover:bg-accent focus-visible:bg-accent focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring active:bg-pressed aria-[current=true]:font-medium [&_svg]:size-4 [&_svg]:shrink-0">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="invisible group-aria-[current=true]:visible"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Français
<span aria-hidden="true" class="ml-auto pl-4 text-xs font-normal text-muted-foreground uppercase tabular-nums">fr</span>
</a>
</li>
<li>
<a href="#es" lang="es" hreflang="es" class="group flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm whitespace-nowrap outline-none transition-colors select-none hover:bg-accent focus-visible:bg-accent focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring active:bg-pressed aria-[current=true]:font-medium [&_svg]:size-4 [&_svg]:shrink-0">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="invisible group-aria-[current=true]:visible"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Español
<span aria-hidden="true" class="ml-auto pl-4 text-xs font-normal text-muted-foreground uppercase tabular-nums">es</span>
</a>
</li>
</ul>
</nav>
</div>
<div class="flex flex-wrap items-center justify-between gap-2 border-t pt-4 text-xs text-muted-foreground">
<span>© 2026 Caseroom</span>
<div class="inline-block">
<button type="button" popovertarget="ex-lang-footer" aria-expanded="false" data-lang-trigger class="inline-flex shrink-0 cursor-pointer items-center justify-center gap-2 rounded-md font-medium whitespace-nowrap transition-[color,background-color,border-color,transform] select-none active:scale-[0.97] aria-busy:cursor-progress disabled:pointer-events-none disabled:opacity-50 aria-busy:disabled:opacity-80 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 hover:bg-accent hover:text-accent-foreground active:bg-pressed h-8 px-3 text-[13px] group" style="anchor-name: --ex-lang-footer">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="text-muted-foreground"><path d="M8 14.5a6.5 6.5 0 1 0 0-13 6.5 6.5 0 0 0 0 13zM1.5 8h13M8 1.5c1.7 1.8 2.5 4 2.5 6.5S9.7 12.7 8 14.5C6.3 12.7 5.5 10.5 5.5 8S6.3 3.3 8 1.5z"></path></svg>
<span class="sr-only">Language: </span>
<span lang="nl">Nederlands</span>
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="size-3.5 text-muted-foreground transition-transform duration-150 group-aria-expanded:rotate-180"><path d="M4 6l4 4 4-4"></path></svg>
</button>
<nav id="ex-lang-footer" popover="auto" aria-label="Language" data-lang-list data-align="end" class="m-0 [inset:auto] supports-[anchor-name:--a]:mt-1 supports-[anchor-name:--a]:[top:anchor(bottom)] supports-[anchor-name:--a]:[left:anchor(left)] supports-[anchor-name:--a]:[position-try-fallbacks:flip-block,flip-inline] supports-[anchor-name:--a]:data-[align=end]:[left:auto] supports-[anchor-name:--a]:data-[align=end]:[right:anchor(right)] min-w-52 rounded-lg border bg-popover p-1 text-popover-foreground shadow-lg dark:shadow-[0_12px_32px_rgb(0_0_0/0.65)] opacity-0 transition-[opacity,display,overlay] transition-discrete duration-100 open:opacity-100 starting:open:opacity-0" style="position-anchor: --ex-lang-footer">
<ul role="list" class="grid">
<li>
<a href="#en" lang="en" hreflang="en" class="group flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm whitespace-nowrap outline-none transition-colors select-none hover:bg-accent focus-visible:bg-accent focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring active:bg-pressed aria-[current=true]:font-medium [&_svg]:size-4 [&_svg]:shrink-0">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="invisible group-aria-[current=true]:visible"><path d="M3.5 8.5l3 3 6-7"></path></svg>
English
<span aria-hidden="true" class="ml-auto pl-4 text-xs font-normal text-muted-foreground uppercase tabular-nums">en</span>
</a>
</li>
<li>
<a href="#nl" lang="nl" hreflang="nl" aria-current="true" class="group flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm whitespace-nowrap outline-none transition-colors select-none hover:bg-accent focus-visible:bg-accent focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring active:bg-pressed aria-[current=true]:font-medium [&_svg]:size-4 [&_svg]:shrink-0">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="invisible group-aria-[current=true]:visible"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Nederlands
<span aria-hidden="true" class="ml-auto pl-4 text-xs font-normal text-muted-foreground uppercase tabular-nums">nl</span>
</a>
</li>
<li>
<a href="#de" lang="de" hreflang="de" class="group flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm whitespace-nowrap outline-none transition-colors select-none hover:bg-accent focus-visible:bg-accent focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring active:bg-pressed aria-[current=true]:font-medium [&_svg]:size-4 [&_svg]:shrink-0">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="invisible group-aria-[current=true]:visible"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Deutsch
<span aria-hidden="true" class="ml-auto pl-4 text-xs font-normal text-muted-foreground uppercase tabular-nums">de</span>
</a>
</li>
<li>
<a href="#fr" lang="fr" hreflang="fr" class="group flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm whitespace-nowrap outline-none transition-colors select-none hover:bg-accent focus-visible:bg-accent focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring active:bg-pressed aria-[current=true]:font-medium [&_svg]:size-4 [&_svg]:shrink-0">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="invisible group-aria-[current=true]:visible"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Français
<span aria-hidden="true" class="ml-auto pl-4 text-xs font-normal text-muted-foreground uppercase tabular-nums">fr</span>
</a>
</li>
<li>
<a href="#es" lang="es" hreflang="es" class="group flex h-8 w-full items-center gap-2 rounded-md px-2 text-sm whitespace-nowrap outline-none transition-colors select-none hover:bg-accent focus-visible:bg-accent focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring active:bg-pressed aria-[current=true]:font-medium [&_svg]:size-4 [&_svg]:shrink-0">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" class="invisible group-aria-[current=true]:visible"><path d="M3.5 8.5l3 3 6-7"></path></svg>
Español
<span aria-hidden="true" class="ml-auto pl-4 text-xs font-normal text-muted-foreground uppercase tabular-nums">es</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div><script type="module" src="/caseroom/r/html/language-switcher.js"></script>