Source: https://monarchui.dev/components/timezone-select/

Components · Date, time and scheduling

# TimezoneSelect

Searchable IANA time zone picker labelled by UTC offset and city, with detected and pinned zones.

@rookcom/monarch-react/scheduling

Component

## Detected and pinned zones

## Usage examples

### Curated list, small

## Purpose

Chooses an IANA time zone from every zone the runtime knows (about 420), labelled "(UTC+05:30) Kolkata" and sorted by offset, with search by city, country, offset or abbreviation. Replaces native selects and hard-coded lists of 15 zones. Import from `@rookcom/monarch-react/scheduling`.

### Use when

- A person, workspace, schedule or booking page needs a time zone.
- A short curated list is enough: pass `zones`.

### Avoid when

- Showing a time in a zone: format it with Intl instead.
- Picking a UTC offset rather than a place: offsets change with daylight saving time.

## Design principles

- Zones are places, not offsets: the value is the IANA id, and the label shows the offset for `now` (DST-aware).
- The likely answer is first: the device’s zone under "Detected", then the product’s "Pinned" and "Recent" zones.
- Same render on server and client: detection runs after mount, and `now` can be passed.
- Legacy ids that browsers still report (Asia/Calcutta) select their current name (Asia/Kolkata).

## API reference

```tsx
import { TimezoneSelect, canonicalTimezone, formatUtcOffset, getTimezoneInfo, listTimezones } from '@rookcom/monarch-react/scheduling';
```

| Export | Kind | Rendering | Root class |
| --- | --- | --- | --- |
| `TimezoneSelect` | component | client | `.mn-timezone-select` |
| `canonicalTimezone` | utility | client | – |
| `formatUtcOffset` | utility | client | – |
| `getTimezoneInfo` | utility | client | – |
| `listTimezones` | utility | client | – |

Built on Combobox and accepts its input props (size, placeholder, invalid, disabled, limit, …). Forwards its ref to the input.

Helpers: `getTimezoneInfo(zone, { now, locale })`, `listTimezones()`, `formatUtcOffset(minutes)`, `canonicalTimezone(zone)`.

Generated from the TypeScript declarations of `packages/react/src/components/scheduling/timezone-select`.

## Properties

Without `Intl.supportedValuesOf` (older browsers and Node) the list falls back to about 80 common zones; a value outside the list is still shown.

### `TimezoneSelect`

Searchable IANA time zone picker, labelled by UTC offset and city and sorted by offset, with "Detected", "Pinned" and "Recent" sections. Search matches city, zone id, offset (`+5:30`, `utc-8`), long name and common country names. Offsets are DST-aware for `now`. Built on Combobox.

| Prop | Type | Description |
| --- | --- | --- |
| `children` | `ReactNode` |  |
| `className` | `string` |  |
| `clearable` | `boolean` | Allow clearing the selection with the clear button / Backspace on empty input. |
| `clearOption` | `ComboboxClearOption` | The list starts with an option that clears the selection, such as "Unassigned" or "No country". |
| `closeOnSelect` | `boolean` | Close the list after a pick. Default: true, or false with `multiple` or `inline`. |
| `defaultOpen` | `boolean` |  |
| `defaultValue` | `string \| null` |  |
| `detectedZone` | `string \| null` | The zone shown under "Detected". Default: the device's zone, read after mount so server and client render the same. `null` hides the section. |
| `disabled` | `boolean` |  |
| `emptyMessage` | `ReactNode` | Shown when no option matches. |
| `formatLabel` | `((zone: TimezoneInfo) => string)` | Option label. Default `(UTC+05:30) Kolkata`. |
| `id` | `string` |  |
| `inputValue` | `string` | Text typed in the input (controlled). Use for server-side filtering. |
| `invalid` | `boolean` |  |
| `limit` | `number` | Render at most this many matching options, with a message to keep typing. Default: all. |
| `listLabel` | `string` | Accessible name of the listbox. Default: the Field label, `aria-label`, or "Options". |
| `loading` | `boolean` | Shown while options load. |
| `locale` | `string` | Locale for long zone names ("India Standard Time"). Default `en-US`. |
| `messages` | `(Partial<TimezoneSelectMessages> & Partial<ComboboxMessages>)` | Interface text, for translation. |
| `minSearchLength` | `number` | Shortest query `onSearch` runs for; shorter queries show `emptyMessage`. Default 0. |
| `now` | `number \| Date` | The instant offsets are computed for. Default: the time of first render. Pass it to render the same on server and client. |
| `onInputValueChange` | `((text: string) => void)` |  |
| `onOpenChange` | `((open: boolean) => void)` |  |
| `onValueChange` | `((zone: string \| null) => void)` |  |
| `open` | `boolean` | Whether the list is open (controlled). Ignored with `inline`. |
| `pinned` | `string[]` | Zones listed first under "Pinned", such as the workspace's offices. |
| `recent` | `string[]` | Zones listed under "Recent", such as the person's last choices. |
| `renderOption` | `((option: ComboboxOption, state: ComboboxOptionState) => ReactNode)` | Custom option content. Default: media, label, description, hint and a check mark. |
| `searchDebounce` | `number` | Milliseconds to wait after typing before `onSearch`. Default 250. |
| `size` | `"sm" \| "md" \| "lg"` |  |
| `value` | `string \| null` | Selected IANA zone id (controlled). Legacy ids select their current name (`Asia/Calcutta` → `Asia/Kolkata`); a zone the list does not contain is still shown. |
| `zones` | `string[]` | Limit the list to these zones. Default: every zone the runtime knows, or a static list. |

## Variants

| Prop | Values | Description |
| --- | --- | --- |
| `size` | `'sm'``'md'``'lg'` | Height, as Combobox. |

## States

| State | Exposed as | Behaviour |
| --- | --- | --- |
| Sections | `role="group"` | "Detected", "Pinned", "Recent" and "All time zones" while nothing is typed. |
| Searching | – | A flat list of matches in offset order. |
| Empty | `role="status"` | "No time zones match". |
| Selected / invalid / disabled | – | As Combobox. |

## Interaction behaviour

- Typing filters by every word: city, IANA id, long name ("India Standard Time"), offset (`+5:30`, `utc-8`, `+0530`) or country and abbreviation for common zones ("india", "us pacific", "bst").
- The full list is built only when the field is first focused or opened, so pages with many fields stay fast.
- A zone listed under "Detected" or "Pinned" also appears in its place in "All time zones".

## Keyboard support

| Keys | Action |
| --- | --- |
| ArrowDown / ArrowUp | Opens the list, then moves. |
| PageDown / PageUp, Home / End | Moves ten zones, or to the first or last. |
| Enter | Selects the active zone. |
| Escape | Closes and restores the selected label. |

## Accessibility

Role

combobox, controlling a listbox

Accessible name

A FieldLabel or

aria-label

.

- Each option reads its offset, city, IANA id and long name.
- Sections are labelled groups.
- Inherits Combobox’s APG keyboard and `aria-activedescendant` support.

## Responsive behaviour

- The list matches the field width (at least 12rem); long descriptions wrap.

## Theme support

- Combobox tokens; the input uses tabular numbers so offsets line up.

## Do and don't

### Do

- Store the IANA id the picker returns, not the offset.
- Pin the zones the workspace already uses (offices, teammates).
- Pass `now` when the page is server rendered and must match exactly.

### Don't

- Do not hard-code a short list of zones unless the product truly only supports those.
- Do not show offsets without a place; "UTC+01:00" is London in summer and Lagos all year.

## Related components

- [Combobox](https://monarchui.dev/components/combobox/): The searchable select it is built on.
- [Select](https://monarchui.dev/components/select/): For a handful of fixed choices.

## Dependencies

| Kind | Depends on |
| --- | --- |
| npm packages | `react` |
| Monarch components | [Combobox](https://monarchui.dev/components/combobox/) |

Uses only `Intl`; no time zone data is bundled beyond the fallback id list and search aliases.

## Testing coverage

- Offsets formatted; DST-aware offsets, city and long name for an instant; unknown zones fall back.
- Static list when `Intl.supportedValuesOf` is missing; legacy ids canonicalised.
- Detected, Pinned, Recent and All sections in offset order; device detection after mount.
- Search by country, offset and "us pacific"; selects; empty message.
- A value outside the list, a legacy value and a custom label.

| Check | Covered |
| --- | --- |
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery/timezone-select; Cross-browser axe: /components/timezone-select |

Tests: `packages/react/src/components/scheduling/timezone-select`

## All example code

### Detected and pinned zones

```tsx
import { useState } from 'react';
import { Field, FieldDescription, FieldLabel } from '@rookcom/monarch-react';
import { TimezoneSelect } from '@rookcom/monarch-react/scheduling';

const NOW = Date.UTC(2026, 0, 15, 12);

function WorkspaceZone() {
  const [zone, setZone] = useState<string | null>('Asia/Kolkata');
  return (
    <Field style={{ width: '100%', maxWidth: 320 }}>
      <FieldLabel>Workspace time zone</FieldLabel>
      <TimezoneSelect
        value={zone}
        onValueChange={setZone}
        now={NOW}
        detectedZone="Asia/Kolkata"
        pinned={['Europe/London', 'America/New_York']}
        placeholder="Search city, country or offset"
      />
      <FieldDescription>Reports and reminders use this zone.</FieldDescription>
    </Field>
  );
}

<WorkspaceZone />
```

### Curated list, small

```tsx
import { TimezoneSelect } from '@rookcom/monarch-react/scheduling';

const NOW = Date.UTC(2026, 0, 15, 12);

const COMMON = [
  'UTC',
  'America/Los_Angeles',
  'America/Chicago',
  'America/New_York',
  'Europe/London',
  'Europe/Berlin',
  'Asia/Dubai',
  'Asia/Kolkata',
  'Asia/Singapore',
  'Asia/Tokyo',
  'Australia/Sydney',
];

<div style={{ width: '100%', maxWidth: 260 }}>
  <TimezoneSelect
    aria-label="Sending time zone"
    size="sm"
    zones={COMMON}
    now={NOW}
    defaultValue="Europe/Berlin"
    detectedZone={null}
  />
</div>
```
