Babelize Elements
Locale Pickers

Locale Picker

Region picker with locale-derived defaults — currency, date and number formats, and first day of week. Searchable, keyboard accessible, and RTL-aware.

Currency
INR₹12,34,567.89
Date format
15 Jan 2026
Number format
12,34,567.89
Week starts on
Sunday

Installation

npx shadcn@latest add @babelize/locale-picker

Usage

import { LocalePicker } from "@/components/ui/locale-picker";
<LocalePicker
  regions={["IN", "US", "GB", "DE", "JP"]} // your ISO 3166-1 alpha-2 regions
  defaultValue="en-IN"
  onValueChange={(locale) => console.log("Selected locale:", locale)}
  label="english" // native, english
  variant="outline" // outline, ghost, secondary
  size="default" // sm, default, lg
/>

Props

PropTypeRequiredDefaultDescription
regionsstring[]Yes—Regions to choose from, ISO 3166-1 alpha-2 codes
valuestringNo—Selected locale (BCP 47) — pass this to control the component
defaultValuestringNoen-<first region>Initial selected locale when uncontrolled
onValueChange(locale: string) => voidNo—Callback when the region changes — receives the new BCP 47 tag
label"native" | "english"No"english"Region names in English, or localized to the selected locale
showFlagsbooleanNotrueShow the region's flag emoji next to its name
maxHeightnumber | stringNo"15rem"Max height of the scrolling option list, before it scrolls
variant"outline" | "ghost" | "secondary"No"outline"Trigger style
size"sm" | "default" | "lg"No"default"Trigger size
align"start" | "end"No"start"Edge of the trigger the popup lines up with
side"top" | "bottom"No"bottom"Preferred popup side — flips when the viewport has no room
searchablebooleanNotrueShow the search field
searchPlaceholderstringNo"Search regions..."Placeholder for the search field
emptyMessageReact.ReactNodeNo"No results"Content shown when the search matches nothing
disabledbooleanNofalseDisable the trigger
showSummarybooleanNotrueShow the summary card of derived locale defaults
labelsPartial<LocalePickerLabels>No—Override the built-in English UI text (see below)
classNamesLocalePickerClassNamesNo—Per-part classes: trigger, content, search, list, item, empty, summary
classNamestringNo—Additional CSS classes

The selected value is a full BCP 47 tag: its region subtag is the picked region, and its language subtag survives region changes. Picking Germany from en-IN yields en-DE; picking it from fr-JP yields fr-DE.

Derived defaults

The summary card derives everything from the selected locale through Intl — no data bundled beyond an ISO 4217 fallback table for the currency code:

RowSource
CurrencyRegion → ISO 4217 code, previewed with Intl.NumberFormat (style: "currency")
Date formatIntl.DateTimeFormat (dateStyle: "medium") on a fixed sample date
Number formatIntl.NumberFormat on a fixed sample amount
Week starts onIntl.Locale#getWeekInfo() where available, with a regional fallback

Regions without a currency mapping show an em dash rather than a guess.

Keyboard support

The popup follows the WAI-ARIA combobox pattern: focus stays in the search field, which is exposed as a role="combobox", and the highlighted option is announced through aria-activedescendant.

KeyAction
Enter, Space, or clickOpen the popup from the trigger
ArrowDown / ArrowUp (closed)Open with the first / last region highlighted
ArrowDown / ArrowUpMove the highlight, wrapping at both ends
Home / EndHighlight the first / last region
EnterSelect the highlighted region and return focus to the trigger
EscapeClose the popup and return focus to the trigger
TypingFilter by name or code; the highlight restarts at the top

Features

  • Derived defaults — currency, date format, number format, and first day of week, resolved live through Intl
  • Dropdown with search — filter regions by English name, localized name, or region code, with a clear button
  • Motion — the popup fades and slides via motion/react, honouring prefers-reduced-motion, and leaves the accessibility tree as it closes
  • RTL-aware — flips direction for RTL locales (ar, he, fa, ur) via dir on the root
  • Keyboard accessible — arrow keys, Home / End, Enter, and Escape navigation
  • ARIA labels — role="combobox" search field with aria-activedescendant, plus aria-expanded, aria-haspopup, and aria-selected
  • Themed with shadcn tokens — bg-popover, border-input, bg-accent and friends, so it inherits your theme; dark mode comes from the tokens
  • Customisable — variant / size triggers plus per-part classNames (LocalePickerClassNames is exported from the barrel), with lucide-react icons throughout
  • Controlled or uncontrolled — value / defaultValue / onValueChange

Examples

Controlled

<LocalePicker regions={["IN", "US", "GB", "DE", "JP"]} value={locale} onValueChange={setLocale} />

Native region names

<LocalePicker regions={["IN", "US", "GB", "DE", "JP"]} defaultValue="de-DE" label="native" />

With de-DE selected, the trigger reads "Deutschland" and the options read "Vereinigte Staaten", "Vereinigtes Königreich", and so on.

Translated UI text

The search placeholder, empty state, accessible labels and summary row labels default to English. Pass labels to translate them — any key you leave out keeps its English default. The searchPlaceholder and emptyMessage props still work, and win over labels when both are set.

"use client";

import { LocalePicker } from "@/components/ui/locale-picker";

export default function App() {
  return (
    <LocalePicker
      regions={["IN", "US", "GB", "DE", "JP"]}
      defaultValue="en-IN"
      labels={{
        searchPlaceholder: "Rechercher une région…",
        searchLabel: "Rechercher une région",
        clearSearch: "Effacer la recherche",
        emptyMessage: "Aucune région trouvée.",
        listbox: "Choisir la région",
        current: (name) => `Région actuelle : ${name}`,
        currency: "Devise",
        dateFormat: "Format de date",
        numberFormat: "Format de nombre",
        weekStart: "La semaine commence le",
      }}
    />
  );
}

Dependencies

This component uses:

  • clsx and tailwind-merge (bundled as dependencies)
  • lucide-react (icons)
  • motion (popup animation)

Peer dependencies:

  • react >= 18
  • react-dom >= 18
  • tailwindcss >= 3

Credits

Created by ujwalbajaj.

On this page