ComponentsLanguage Switchers
Language Switcher
Copy-paste language switcher component for React with dropdown, search, RTL support, and full accessibility. Works with next-intl, react-i18next, and any i18n setup.
Hello, world!
Installation
bash npx shadcn@latest add @elements/language-switcher Usage
import { LanguageSwitcher } from "@/components/ui/language-switcher";<LanguageSwitcher
locales={[
{ code: "en" },
{ code: "fr" },
{ code: "es" },
{ code: "de" },
{ code: "ja" },
{ code: "ar" },
]}
defaultValue="en"
onValueChange={(code) => console.log(code)}
/>Props
| Prop | Type | Required | Description |
|---|---|---|---|
locales | Locale[] | Yes | Array of available locales — only code is required |
value | string | No | Selected locale code — pass this to control the component |
defaultValue | string | No | Initial selected locale code (default: first locale's code) |
onValueChange | (code: string) => void | No | Callback when locale changes |
label | "native" | "english" | No | Display labels in native or English (default: "english") |
showFlags | boolean | No | Show flag emojis (default: false) |
className | string | No | Additional CSS classes |
Locale Object
| Property | Type | Description |
|---|---|---|
code | string | ISO 639-1 / BCP 47 code (e.g. "en", "fr-FR", "ar-SA") — required |
label | string | Override display name (auto-resolved from built-in mapping if omitted) |
flag | string | Override flag emoji (auto-detected from code if omitted) |
rtl | boolean | Override RTL (auto-detected from code if omitted) |
Examples
With Babelize SDK
Pair Language Switcher with the Babelize SDK for automatic translations:
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { LanguageSwitcher } from "@/components/ui/language-switcher";
import { useBabelize } from "@babelize/sdk/react";
const locales = [
{ code: "en", label: "English", flag: "🇺🇸" },
{ code: "fr", label: "Français", flag: "🇫🇷" },
{ code: "es", label: "Español", flag: "🇪🇸" },
{ code: "de", label: "Deutsch", flag: "🇩🇪" },
{ code: "ja", label: "日本語", flag: "🇯🇵" },
{ code: "ar", label: "العربية", flag: "🇸🇦" },
];
export function LocaleSwitcher() {
const router = useRouter();
const { locale, setLocale } = useBabelize();
return (
<LanguageSwitcher
locales={locales}
value={locale}
onValueChange={(code) => {
setLocale(code);
router.push(`/${code}`);
}}
/>
);
}With next-intl
"use client";
import { useRouter } from "next/navigation";
import { LanguageSwitcher } from "@/components/ui/language-switcher";
export function LocaleSwitcher() {
const router = useRouter();
return (
<LanguageSwitcher
locales={[{ code: "en" }, { code: "fr" }]}
defaultValue="en"
onValueChange={(code) => router.push(`/${code}`)}
/>
);
}With react-i18next
"use client";
import { useTranslation } from "react-i18next";
import { LanguageSwitcher } from "@/components/ui/language-switcher";
export function LocaleSwitcher() {
const { i18n } = useTranslation();
return (
<LanguageSwitcher
locales={[{ code: "en" }, { code: "fr" }]}
value={i18n.language}
onValueChange={(code) => i18n.changeLanguage(code)}
/>
);
}Native Labels + Flags
"use client";
import { LanguageSwitcher } from "@/components/ui/language-switcher";
export default function App() {
return (
<LanguageSwitcher
locales={[{ code: "en" }, { code: "fr" }, { code: "es" }]}
defaultValue="en"
label="native"
showFlags
/>
);
}Features
- Zero-config — just pass codes, labels and flags are auto-resolved
- Dropdown with search — filter locales by name or code
- RTL-aware — auto-detects RTL locales (ar, he, fa, ur) and adjusts layout
- Keyboard accessible — arrow keys, Enter, Escape, Tab navigation
- ARIA labels — proper
aria-expanded,aria-haspopup,aria-selected - Dark mode — built with Tailwind CSS, dark-mode compatible
- Library agnostic — works with any i18n setup via callback
Supported Languages
Labels and flags are auto-detected from the language code. Pass just the code:
| Code | Language | Flag | RTL |
|---|---|---|---|
en | English | 🇺🇸 | |
fr | Français | 🇫🇷 | |
es | Español | 🇪🇸 | |
de | Deutsch | 🇩🇪 | |
ja | 日本語 | 🇯🇵 | |
ko | 한국어 | 🇰🇷 | |
zh | 中文 | 🇨🇳 | |
ar | العربية | 🇸🇦 | ✓ |
pt | Português | 🇧🇷 | |
it | Italiano | 🇮🇹 | |
nl | Nederlands | 🇳🇱 | |
ru | Русский | 🇷🇺 | |
pl | Polski | 🇵🇱 | |
tr | Türkçe | 🇹🇷 | |
vi | Tiếng Việt | 🇻🇳 | |
th | ไทย | 🇹🇭 | |
id | Bahasa Indonesia | 🇮🇩 | |
hi | हिन्दी | 🇮🇳 | |
bn | বাংলা | 🇧🇩 | |
uk | Українська | 🇺🇦 | |
cs | Čeština | 🇨🇿 | |
sv | Svenska | 🇸🇪 | |
da | Dansk | 🇩🇰 | |
fi | Suomi | 🇫🇮 | |
no | Norsk | 🇳🇴 | |
el | Ελληνικά | 🇬🇷 | |
he | עברית | 🇮🇱 | ✓ |
fa | فارسی | 🇮🇷 | ✓ |
ro | Română | 🇷🇴 | |
hu | Magyar | 🇭🇺 | |
sk | Slovenčina | 🇸🇰 | |
bg | Български | 🇧🇬 | |
hr | Hrvatski | 🇭🇷 | |
sr | Српски | 🇷🇸 | |
sl | Slovenščina | 🇸🇮 | |
lt | Lietuvių | 🇱🇹 | |
lv | Latviešu | 🇱🇻 | |
et | Eesti | 🇪🇪 | |
ga | Gaeilge | 🇮🇪 | |
ca | Català | 🇦🇩 | |
is | Íslenska | 🇮🇸 | |
sw | Kiswahili | 🇹🇿 | |
am | አማርኛ | 🇪🇹 | |
ne | नेपाली | 🇳🇵 | |
si | සිංහල | 🇱🇰 | |
my | မြန်မာ | 🇲🇲 | |
km | ខ្មែរ | 🇰🇭 | |
lo | ລາວ | 🇱🇦 | |
ka | ქართული | 🇬🇪 | |
hy | Հայերեն | 🇦🇲 | |
kk | Қазақ | 🇰🇿 | |
uz | Oʻzbek | 🇺🇿 | |
mn | Монгол | 🇲🇳 | |
ur | اردو | 🇵🇰 | ✓ |
ml | മലയാളം | 🇮🇳 | |
ta | தமிழ் | 🇮🇳 | |
te | తెలుగు | 🇮🇳 | |
kn | ಕನ್ನಡ | 🇮🇳 | |
mr | मराठी | 🇮🇳 | |
gu | ગુજરાતી | 🇮🇳 | |
pa | ਪੰਜਾਬੀ | 🇮🇳 | |
yo | Yorùbá | 🇳🇬 | |
ig | Igbo | 🇳🇬 | |
zu | isiZulu | 🇿🇦 | |
af | Afrikaans | 🇿🇦 | |
ha | Hausa | 🇳🇬 | |
tl | Filipino | 🇵🇭 | |
mi | Te Reo Māori | 🇳🇿 | |
sm | Gagana Samoa | 🇼🇸 | |
to | Lea Faka-Tonga | 🇹🇴 | |
eo | Esperanto | 🌐 |
Need a language not listed? Pass a custom label and flag to override:
<LanguageSwitcher
locales={[{ code: "xyz", label: "My Language", flag: "🏴" }]}
defaultValue="xyz"
/>Dependencies
This component uses:
clsxandtailwind-merge(bundled as dependencies)
Peer dependencies:
react>= 18react-dom>= 18tailwindcss>= 3
Credits
Created by sapatmohit.