# LocaleSelector

> Locale picker with native language names.

- **Level**: molecule · **Status**: stable · **Role**: input
- **Import**: `import { LocaleSelector } from "@rrios-forge/ui/molecules/locale-selector";`
- **Source**: `packages/ui/src/molecules/locale-selector`

**Purpose.** Single-choice locale picker. Shows the selected locale's flag and native name in a trigger and exposes the full list — searchable when long — in a `Popover`. The list is content-controlled by the integrator so the molecule never ships a hardcoded language catalogue.

**When to use.**
- A header / top-bar locale switch on a public marketing page.
- A settings field where the user picks the language of the product UI.
- A document or workspace surface where the active locale is part of the metadata.

**When NOT to use.**
- Choosing a country that isn't tied to a language — use a plain `Select`.
- Multi-select language preferences — compose `Combobox` instead.
- A free-text search across many unrelated entities — use a `Combobox` or a `CommandMenu`.

**Composes.**
- `Popover` — the floating panel and ARIA dialog surface.
- `cmdk` `Command` — the searchable list with type-ahead, arrow-key navigation and `aria-activedescendant`.

**Display modes.**
- `flag-and-text` (default) — flag + native name, e.g. `🇪🇸 Español`.
- `flag-only` — flag only; ideal for top-bars. Requires an `aria-label` (a sensible default is computed from the selected locale's native name).
- `text-only` — native name only; useful when the brand cannot rely on emoji flag rendering across all platforms (notably Windows).

**Search.**
- The list grows a search input when `locales.length >= searchThreshold` (default `5`).
- The filter scores against the native name, the optional English alias (`searchAliases`) and the locale code, so users discover their language even when they do not yet read it.

**Accessibility.**
- The trigger is `role="combobox"`, `aria-haspopup="listbox"`, `aria-expanded` reflects the open state.
- Keyboard contract inherits from `Popover` + `cmdk`: Enter / Space opens, ↑↓ navigate, Enter selects, Escape closes and returns focus to the trigger.
- `flag-only` requires an explicit or computed `aria-label` so screen-reader users hear the active locale.

**Related patterns.**
- `Patterns/Decision Matrix · NavigationChoice` — picks a value from a long set.
- `Patterns/Surfaces/Overlays · OverlayDecisionTable` — Popover (not Dropdown, not Dialog) is the right primitive.
- `Patterns/Surfaces/Forms · SearchAndSelect` — the search-within-a-list contract.
- `Patterns/Flows/Navigation · PopoverForContextualWork` — anatomy of the panel.
- `Patterns/Flows/Search & Command · SearchFieldVsCombobox` — when to expose the search input.
- `Patterns/Foundations/Visual Priority · QuietSecondaryActions` — the selected-row tint and tick are quiet, not loud.
- `Patterns/Foundations/Density & Growth · ContentGrowth` — search appears as the dataset grows.

## Axes

| Axis | Part | Values | Default | Meaning |
|---|---|---|---|---|
| `size` | — | `sm` `md` `lg` | `md` | Ergonomic scale, in three families (control, text, container). |
| `display` | — | `flag-only` `flag-and-text` `text-only` | `flag-and-text` | What a selector shows. |
| `selected` | — | `true` `false` | `false` | Chosen in a set: the selected wash plus a non-colour cue (weight, check). |

## Stories

- [Playground](/?path=/story/components-molecules-localeselector--playground) · frame: `/iframe.html?id=components-molecules-localeselector--playground&viewMode=story`
- [Display Modes](/?path=/story/components-molecules-localeselector--display-modes) · frame: `/iframe.html?id=components-molecules-localeselector--display-modes&viewMode=story`
- [Sizes](/?path=/story/components-molecules-localeselector--sizes) · frame: `/iframe.html?id=components-molecules-localeselector--sizes&viewMode=story`
- [With Search](/?path=/story/components-molecules-localeselector--with-search) · frame: `/iframe.html?id=components-molecules-localeselector--with-search&viewMode=story`
- [Without Search](/?path=/story/components-molecules-localeselector--without-search) · frame: `/iframe.html?id=components-molecules-localeselector--without-search&viewMode=story`
- [States](/?path=/story/components-molecules-localeselector--states) · frame: `/iframe.html?id=components-molecules-localeselector--states&viewMode=story`
- [In Context](/?path=/story/components-molecules-localeselector--in-context) · frame: `/iframe.html?id=components-molecules-localeselector--in-context&viewMode=story`

## Patterns consulted

- Patterns/Decision Matrix · OverlayChoice
- Patterns/Decision Matrix · NavigationChoice
- Patterns/Surfaces/Overlays · OverlayDecisionTable
- Patterns/Surfaces/Overlays · EscapeAndReturnPath
- Patterns/Surfaces/Forms · SearchAndSelect
- Patterns/Flows/Navigation · NavigationLayers
- Patterns/Flows/Navigation · PopoverForContextualWork
- Patterns/Flows/Search & Command · SearchFieldVsCombobox
- Patterns/Foundations/Visual Priority · QuietSecondaryActions
- Patterns/Foundations/Visual Priority · StatusHierarchy
- Patterns/Foundations/Density & Growth · DensityLevels
- Patterns/Foundations/Density & Growth · ContentGrowth
- Patterns/Foundations/Composition · CompositionChecklist
- Patterns/Foundations/Composition · ReuseBeforeCreate

## Decisions that name it

- ADR-024 — `LocaleSelector` is a new molecule, not an extension of `Combobox`

## Always

- Tokens only (R04); variants are semantic axes, never appearance (R02).
- Typography and actions are Poesía atoms, never raw HTML with utilities.
- Vibrant panes need a ground and never nest (ADR-027).
