---
title: Client-Side Features
description: Components and hooks for locale switching in Vite + React applications
---

Client-side features in `@better-i18n/use-intl` are built for CSR apps. Locale switching happens entirely in React state — no server round-trips, no page navigation required.

## useLocale

Access and switch the current locale from anywhere inside `BetterI18nProvider`:

```tsx
import { useLocale } from '@better-i18n/use-intl'

function LocaleToggle() {
  const { locale, setLocale, isLoading } = useLocale()

  return (
    <button
      onClick={() => setLocale(locale === 'en' ? 'tr' : 'en')}
      disabled={isLoading}
    >
      {isLoading ? 'Switching...' : `Current: ${locale}`}
    </button>
  )
}
```

### Return Value

| Property | Type | Description |
|----------|------|-------------|
| `locale` | `string` | Current active locale (e.g., `"en"`, `"tr"`) |
| `setLocale` | `(locale: string) => void` | Switch locale — triggers CDN fetch + re-render |
| `isLoading` | `boolean` | `true` while new messages are loading |

### Locale Switching Flow

Calling `setLocale()` triggers:

1. CDN fetch for the new locale's messages
2. React context update with new translations
3. Full re-render of the component tree
4. `onLocaleChange` callback (from `BetterI18nProvider`) fired with the new locale

---

## LanguageSwitcher

A drop-in `<select>`-based language switcher. Handles loading state and locale changes automatically.

```tsx
import { LanguageSwitcher } from '@better-i18n/use-intl'

function Header() {
  return (
    <header className="flex justify-between items-center p-4">
      <Logo />
      <LanguageSwitcher className="border rounded px-3 py-2" />
    </header>
  )
}
```

### Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `className` | `string` | — | CSS class for the `<select>` element |
| `loadingLabel` | `string` | `"Loading..."` | Label shown while fetching languages |
| `renderOption` | `(lang) => ReactNode` | — | Custom option renderer |

---

## LocaleDropdown

A fully-featured accessible dropdown with flag icons, keyboard navigation, and CSS theming.

```tsx
import { LocaleDropdown } from '@better-i18n/use-intl'

function Header() {
  return (
    <header>
      <LocaleDropdown />
    </header>
  )
}
```

Selecting a locale calls `setLocale()` internally — no props needed inside `BetterI18nProvider`.

### Quick Theming

Override CSS custom properties on any parent element:

```css
.my-header {
  --better-locale-text: #374151;
  --better-locale-trigger-bg: transparent;
  --better-locale-border: #e5e7eb;
  --better-locale-menu-bg: #ffffff;
  --better-locale-hover-bg: #f3f4f6;
  --better-locale-active-bg: #f9fafb;
  --better-locale-code-text: #9ca3af;
}
```

### Unstyled Mode

Use `variant="unstyled"` with Tailwind or any CSS framework:

```tsx
<LocaleDropdown
  variant="unstyled"
  className="relative"
  triggerClassName="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-gray-100"
  menuClassName="absolute right-0 mt-1 w-52 bg-white border rounded-xl shadow-lg z-50"
/>
```

<Callout type="info">
  For the full API — props, CSS custom properties, data attributes, `renderTrigger`, `renderItem`, and keyboard shortcuts — see [Locale Management](/frameworks/locale#localedropdown).
</Callout>

---

## Persisting the Locale

### With `localeCookie` prop (recommended)

The `localeCookie` prop handles both reading and writing the locale cookie automatically:

```tsx title="src/App.tsx"
import { BetterI18nProvider, LocaleDropdown } from '@better-i18n/use-intl'

function App() {
  return (
    <BetterI18nProvider localeCookie> {/* [!code highlight] */}
      <LocaleDropdown />
      <Router />
    </BetterI18nProvider>
  )
}
```

This writes a `locale` cookie on every locale change, and reads it back on page refresh. No manual `document.cookie` or `localStorage` needed.

### With Vite Plugin (SSR)

When using `@better-i18n/vite`, set the same cookie name on both the plugin and provider:

```ts title="vite.config.ts"
betterI18n({
  projectId: "your-org/your-project",
  localeCookie: "locale", // [!code highlight]
})
```

```tsx title="src/App.tsx"
<BetterI18nProvider localeCookie> {/* matches "locale" by default */}
  <App />
</BetterI18nProvider>
```

The Vite plugin reads the cookie server-side during SSR, and the provider reads it client-side for SPA navigation.

### Custom cookie name

Use a string value to set a custom cookie name:

```tsx
<BetterI18nProvider localeCookie="preferred-locale">
  <App />
</BetterI18nProvider>
```

<Callout type="tip">
  The cookie name must match across client, server, and Vite plugin. See [Provider docs](/frameworks/provider#locale-persistence) for the full server-side pairing pattern.
</Callout>

## Next Steps

<Cards>
  <Card title="React Router" icon="Route" href="/frameworks/vite/react-router">
    URL-based locale routing with `/:locale/*`.
  </Card>
  <Card title="Locale Management" icon="Globe" href="/frameworks/locale">
    Full API reference for hooks and components.
  </Card>
</Cards>
