Language Switcher Pill
Animated pill-style language switcher for React with keyboard navigation and RTL support.
Installation
npx shadcn@latest add @babelize/language-switcher-pillUsage
"use client";
import { useState } from "react";
import { LanguageSwitcherPill } from "@/components/ui/language-switcher-pill";
const locales = [
{ code: "en", label: "English" },
{ code: "fr", label: "Français" },
{ code: "ar", label: "العربية", rtl: true }, // rtl auto-detected, override optional
];
export function LocalePill() {
const [locale, setLocale] = useState("en");
return (
<LanguageSwitcherPill
locales={locales}
locale={locale} // omit for uncontrolled, use defaultLocale instead
onLocaleChange={setLocale}
/>
);
}Props
| Prop | Type | Required | Description |
|---|---|---|---|
locales | Locale[] | Yes | Array of available locales — only code is required |
locale | string | No | Selected locale code — pass this to control the component |
defaultLocale | string | No | Initial selected locale code when uncontrolled (default: first locale's code) |
onLocaleChange | (code: string) => void | No | Callback when the selected locale changes |
variant | "default" | "outline" | No | Muted segmented track or bordered track (default: "default") |
size | "sm" | "default" | "lg" | No | Pill size (default: "default") |
showFlags | boolean | No | Show each locale's flag emoji before its label (default: false) |
disabled | boolean | No | Disable every pill |
aria-label | string | No | Accessible label for the radio group (default: "Select language") |
classNames | LanguageSwitcherPillClassNames | No | Per-part class overrides — see below |
className | string | No | Additional CSS classes for the pill container |
Remaining div props forward to the root element.
Class names
Every part takes its own classes, merged after the defaults, so you can restyle one piece without forking the component:
| Slot | Element |
|---|---|
root | The track that holds every pill |
item | Each locale pill |
activeItem | The selected pill only |
indicator | The spring-animated indicator behind the pill |
flag | The flag shown when showFlags is set |
<LanguageSwitcherPill
locales={locales}
classNames={{
indicator: "bg-primary",
activeItem: "text-primary-foreground",
}}
/>The component is styled with shadcn/ui theme tokens (bg-muted, bg-background,
text-muted-foreground, ring-ring), so it picks up your theme automatically.
Locale Object
| Property | Type | Description |
|---|---|---|
code | string | ISO 639-1 / BCP 47 code (e.g. "en", "fr", "ar") — required |
label | string | Display name for the language pill |
flag | string | Flag emoji shown with showFlags (auto-resolved if omitted) |
rtl | boolean | Override RTL direction for text shaping (auto-detected if omitted) |
Examples
With Babelize SDK
Pair Language Switcher Pill with the Babelize SDK for automatic translations:
"use client";
import { useRouter } from "next/navigation";
import { LanguageSwitcherPill } from "@/components/ui/language-switcher-pill";
import { useBabelize } from "@babelize/sdk/react";
const locales = [
{ code: "en", label: "English" },
{ code: "fr", label: "Français" },
{ code: "es", label: "Español" },
{ code: "de", label: "Deutsch" },
{ code: "ar", label: "العربية" },
];
export function LocaleSwitcher() {
const router = useRouter();
const { locale, setLocale } = useBabelize();
return (
<LanguageSwitcherPill
locales={locales}
locale={locale}
onLocaleChange={(code) => {
setLocale(code);
router.push(`/${code}`);
}}
/>
);
}With next-intl
"use client";
import { useRouter } from "next/navigation";
import { LanguageSwitcherPill } from "@/components/ui/language-switcher-pill";
export function LocaleSwitcher() {
const router = useRouter();
return (
<LanguageSwitcherPill
locales={[
{ code: "en", label: "EN" },
{ code: "fr", label: "FR" },
{ code: "de", label: "DE" },
]}
defaultLocale="en"
onLocaleChange={(code) => router.push(`/${code}`)}
/>
);
}With react-i18next
"use client";
import { useTranslation } from "react-i18next";
import { LanguageSwitcherPill } from "@/components/ui/language-switcher-pill";
export function LocaleSwitcher() {
const { i18n } = useTranslation();
return (
<LanguageSwitcherPill
locales={[
{ code: "en", label: "English" },
{ code: "fr", label: "Français" },
{ code: "es", label: "Español" },
]}
locale={i18n.language}
onLocaleChange={(code) => i18n.changeLanguage(code)}
/>
);
}Variants and sizes
<LanguageSwitcherPill locales={locales} variant="outline" size="sm" showFlags />
<LanguageSwitcherPill locales={locales} size="lg" />Keyboard Navigation
- Arrow Right — Move to the next language (previous in RTL layouts).
- Arrow Left — Move to the previous language (next in RTL layouts).
- Home — Move to the first language.
- End — Move to the last language.
- Enter / Space — Select the focused language.
RTL Support
The component supports right-to-left languages such as Arabic, Hebrew, Persian, Urdu, Pashto, Sindhi, and Yiddish.
Per-locale direction is applied to individual pills for correct text shaping, while the overall layout and arrow navigation respect the document or container dir.
const locales = [
{ code: "en", label: "English" },
{ code: "ar", label: "العربية", rtl: true },
{ code: "he", label: "עברית", rtl: true },
];
<LanguageSwitcherPill locales={locales} defaultLocale="en" />;