Babelize Elements
ComponentsLanguage Switchers

Language Switcher Pill

Animated pill-style language switcher for React with keyboard navigation and RTL support.

Installation

npx shadcn@latest add @babelize/language-switcher-pill

Usage

"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

PropTypeRequiredDescription
localesLocale[]YesArray of available locales — only code is required
localestringNoSelected locale code — pass this to control the component
defaultLocalestringNoInitial selected locale code when uncontrolled (default: first locale's code)
onLocaleChange(code: string) => voidNoCallback when the selected locale changes
variant"default" | "outline"NoMuted segmented track or bordered track (default: "default")
size"sm" | "default" | "lg"NoPill size (default: "default")
showFlagsbooleanNoShow each locale's flag emoji before its label (default: false)
disabledbooleanNoDisable every pill
aria-labelstringNoAccessible label for the radio group (default: "Select language")
classNamesLanguageSwitcherPillClassNamesNoPer-part class overrides — see below
classNamestringNoAdditional 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:

SlotElement
rootThe track that holds every pill
itemEach locale pill
activeItemThe selected pill only
indicatorThe spring-animated indicator behind the pill
flagThe 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

PropertyTypeDescription
codestringISO 639-1 / BCP 47 code (e.g. "en", "fr", "ar") — required
labelstringDisplay name for the language pill
flagstringFlag emoji shown with showFlags (auto-resolved if omitted)
rtlbooleanOverride 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" />;

On this page