# Timezone Select

> A searchable time zone picker with live UTC offsets, optional local times, region groups and a one-tap Use my time zone row.

- Type: React component (Inputs)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add timezone-select`
- Docs: https://wingo-ui.com/components/timezone-select

## Interaction

Click or focus the field and the list opens with every time zone, grouped by region (or by offset), each row showing its UTC offset and, with showTime, the current time there, ticking each minute while the list is open. The first row, "Use my time zone", picks the browser's zone in one go, and suggested zones sit right under it. The field text is selected on focus, so typing replaces it with a search: a city ("tokyo"), a country for common zones ("india"), an abbreviation ("EST", "CET") or an offset ("+2", "utc-5", "+5:30") all find their zone, with the matching letters in bold. Offsets and names follow daylight saving time for the date you pass, or for today. Leaving a search without a pick (Escape, a click away) brings back the zone you had. Keyboard: ArrowDown and ArrowUp move, PageUp / PageDown jump, Enter picks, Escape closes. On a phone a tap opens a full-height search sheet with 48px rows and a Cancel button; a tap on a row fills the field and closes it.

## Installation

```bash
npx wingo-ui@latest add timezone-select
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/timezone-select` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/timezone-select.tsx`

npm dependencies: `npm i lucide-react`

Wingo UI dependencies (installed automatically):

- [Combobox](https://wingo-ui.com/components/combobox) ([Markdown](https://wingo-ui.com/components/combobox.md))
- [Text match](https://wingo-ui.com/components/text-match) ([Markdown](https://wingo-ui.com/components/text-match.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
import { useState } from "react"
import { Globe } from "lucide-react"
import { TimeZoneSelect } from "@/components/ui/timezone-select"

export function ProfileTimeZone() {
  const [timeZone, setTimeZone] = useState("America/New_York")
  return (
    <TimeZoneSelect
      label="Time zone"
      description="Used for reminders and weekly reports."
      leftIcon={<Globe />}
      value={timeZone}
      onValueChange={setTimeZone}
      suggested={["America/New_York", "America/Chicago", "America/Denver", "America/Los_Angeles"]}
      suggestedLabel="United States"
      showTime
      name="timeZone"
    />
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label?` | `ReactNode` | `""` | The label above the field; it also names the list and the phone sheet. |
| `description?` | `ReactNode` | `""` | Helper text under the field, linked with aria-describedby. |
| `error?` | `ReactNode` | `""` | Replaces the description with an error and marks the field invalid. |
| `required?` | `boolean` | `false` | Adds the asterisk to the label and required to the input. |
| `invalid?` | `boolean` | `false` | The invalid look without a message. |
| `disabled?` | `boolean` | `false` | Dims the field and blocks it. |
| `readOnly?` | `boolean` | `false` | Shows the zone without letting anyone change it. |
| `value?` | `string` |  | The chosen IANA id, like "America/New_York", when you control it. Older ids ("Asia/Calcutta", "Europe/Kiev") are read as their current names. |
| `defaultValue?` | `string` | `""` | The starting zone, uncontrolled. "" is empty. |
| `onValueChange?` | `(value: string) => void` |  | Called with the IANA id of the picked zone (the browser's zone for the Use my time zone row). |
| `timeZones?` | `string[]` |  | The zones to offer. Default: every zone the browser knows (Intl.supportedValuesOf), with UTC added, or a built-in list of about 55 common zones on older runtimes. |
| `groupBy?` | `"region" \| "offset" \| "none"` | `"region"` | region groups by the id's continent (Americas, Europe, Asia...) sorted by city; offset groups by UTC offset from west to east; none is one flat list from west to east. |
| `showOffset?` | `boolean` | `true` | The UTC offset at the end of each row, like "UTC−04:00". Grouping by offset puts it in the group titles instead. |
| `showTime?` | `boolean` | `false` | The current local time in each row, refreshed every minute while the list is open. |
| `format?` | `"long" \| "short"` | `"long"` | long puts the zone name under the city in each row ("Eastern Daylight Time"); short shows the city only. The name stays searchable either way, and the field always shows the city. |
| `suggested?` | `string[]` |  | Zones listed first under suggestedLabel, like the ones your team uses. They move out of their region group. |
| `suggestedLabel?` | `string` | `"Suggested"` | The title of the suggested group. |
| `detect?` | `boolean` | `true` | Adds a first row that picks the browser's own time zone, when it is in the list. |
| `detectLabel?` | `string` | `"Use my time zone"` | The text of the detect row; the detected zone shows under it. |
| `date?` | `Date` |  | The instant used for offsets, names and times, so daylight saving time is right for a meeting next month. Default: now. |
| `locale?` | `string` | `""` | Locale for zone names and times through Intl. Falls back to the UIProvider locale, then to the browser's. City names stay as in the IANA id. |
| `regionLabels?` | `Partial<Record<string, string>>` |  | Group titles keyed by the id's first part: America, Europe, Africa, Asia, Australia, Pacific, Atlantic, Indian, Antarctica, Arctic and Other (UTC). Defaults in TIME_ZONE_REGION_LABELS. |
| `placeholder?` | `string` | `"Select a time zone"` | Shown while the field is empty, also in the phone sheet's search box. |
| `emptyText?` | `string` | `"No time zones found"` | Shown when the search matches nothing. |
| `clearable?` | `boolean` | `false` | An x while a zone is chosen; Escape on a closed list clears too. |
| `clearLabel?` | `string` | `"Clear"` | The accessible name of the x button. |
| `toggleLabel?` | `string` | `"Show time zones"` | The accessible name of the chevron button. |
| `openOnFocus?` | `boolean` | `true` | Opens the full list as soon as the field gets focus, and again on a click, like a select. Off: the list opens when you type, press ArrowDown or use the chevron. |
| `autoHighlight?` | `boolean` | `true` | The first match is highlighted while you search, so Enter picks it. |
| `highlightMatches?` | `boolean` | `true` | The matched letters of each zone in bold. |
| `fuzzy?` | `boolean` | `false` | Also matches letters in order with gaps ("lsa" finds Los Angeles), ranked last. |
| `leftIcon?` | `ReactNode` |  | An icon before the text, like a Globe. Unsized svgs follow the size. |
| `size?` | `"sm" \| "md" \| "lg"` | `"md"` | 32 / 40 / 48px like every field; md grows to 44px on touch screens. |
| `variant?` | `"soft" \| "outline" \| "underline" \| "ghost"` | `"soft"` | The box: a filled well, an outline, a bottom line, or nothing until hover. |
| `radius?` | `"none" \| "sm" \| "md" \| "lg" \| "full"` | `"md"` | Corner shape, scaled per size like the button (md is 8 / 10 / 12px). |
| `defaultOpen?` | `boolean` | `false` | Starts with the list (or the sheet) open. Pass open and onOpenChange to control it. |
| `open?` | `boolean` |  | Whether the list is open, controlled. |
| `onOpenChange?` | `(open: boolean) => void` |  | Called when the list opens or closes. |
| `responsive?` | `boolean` | `true` | Below 768px the field opens a full-height search sheet instead of the popover. |
| `sheetTitle?` | `string` | `"Time zone"` | The phone sheet's title for screen readers (its visible header is the search box); a string label wins. |
| `cancelLabel?` | `string` | `"Cancel"` | The text button next to the sheet's search box. |
| `side?` | `"top" \| "right" \| "bottom" \| "left"` | `"bottom"` | Where the list opens; it flips when there is no room. |
| `align?` | `"start" \| "center" \| "end"` | `"start"` | How the list lines up with the field. |
| `maxHeight?` | `number` | `360` | The list's height limit in px, capped by the room on screen. |
| `virtualThreshold?` | `number` | `100` | Above this many rows only the visible ones render (the full list has about 420). |
| `renderOption?` | `(option, state: { selected, highlighted, disabled, ranges }) => ReactNode` |  | Replaces a row's content; the row element, its aria and its classes stay. |
| `name?` | `string` | `""` | Submits the IANA id through a hidden input. |
| `id?` | `string` |  | The input's id; generated when left out. |
| `onChange?` | `(event: ChangeEvent<HTMLInputElement>) => void` |  | The native event of every keystroke in the search, untouched. |
| `container?` | `HTMLElement \| null` |  | Portal target for the list and the sheet (default document.body). |
| `fieldProps?` | `Partial<FieldProps>` |  | Anything else for the field wrapper: optional, labelAction, orientation, labelWidth, hideLabel. |
| `classNames?` | `Partial<Record<"field" \| "label" \| "description" \| "error" \| "counter" \| "root" \| "box" \| "input" \| "leftIcon" \| "spinner" \| "clear" \| "toggle" \| "content" \| "list" \| "item" \| "group" \| "status" \| "sheet" \| "sheetSearch" \| "cancel", string>>` |  | Extra classes for the inner parts, the same slots as the Combobox. content is the popover, sheet the phone sheet. |
| `className?` | `string` |  | Extra classes for the outermost element: the Field when there is a label or message, else the combobox root. |
| `ref?` | `Ref<HTMLInputElement> (a useRef object or an (el) => void callback)` |  | The search input. Other input props (onBlur, onKeyDown, autoFocus, aria-*, data-*) land on it too. |

## Parts

### getTimeZoneOptions (function)

Builds the option list the component uses: every zone with its city, region, offset, localized name, local time and search keywords, sorted west to east. It reads Intl, so call it in an effect or an event handler, not in a server render. Reuse it for your own list or a settings page.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `timeZones?` | `readonly string[]` |  | The ids to describe. Default: every zone the runtime knows plus UTC. |
| `date?` | `Date` | `"new Date()"` | The instant for offsets, names and times. |
| `locale?` | `string` |  | Locale for names and times. |
| `format?` | `"long" \| "short"` | `"long"` | How label is built. |
| `returns?` | `TimeZoneOption[]` |  | One option per zone. |

### formatUtcOffset (function)

The offset of a zone at an instant as text: formatUtcOffset("America/New_York", date) gives "UTC−04:00" in summer and "UTC−05:00" in winter, and "" for an unknown id.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `timeZone` | `string` |  | An IANA id. |
| `date?` | `Date` | `"new Date()"` | The instant. |
| `prefix?` | `string` | `"UTC"` | Text before the sign, e.g. "GMT". |

### getUtcOffset (function)

The offset of a zone at an instant in minutes east of UTC (-240 for New York in summer, 330 for Kolkata), NaN for an unknown id.

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `timeZone` | `string` |  | An IANA id. |
| `date?` | `Date` | `"new Date()"` | The instant. |

### TIME_ZONE_REGION_LABELS (constant)

The default group titles keyed by the id's first part (America: Americas, Indian: Indian Ocean, Other for UTC).

### TimeZoneOption (type)

{ value, city, region, offset, offsetLabel, name, label, time, keywords }: one zone as getTimeZoneOptions returns it.

### GetTimeZoneOptionsParams (type)

The parameters of getTimeZoneOptions.

### TimeZoneGroupBy (type)

"region" | "offset" | "none".

### TimeZoneFormat (type)

"long" | "short".

### TimeZoneSelectProps (type)

The props of TimeZoneSelect: its own plus the Combobox field props it passes through.

## Examples

- **With local time**: showTime adds the current time to each row and ticks each minute while the list is open.
- **Grouped by offset**: groupBy="offset" lists zones west to east under their UTC offset.
- **Suggested zones for a US team**: suggested pins the zones your team uses to the top under a title you choose.
- **Offsets for a future date**: date picks the instant for offsets and times, so a January meeting shows winter time.
- **In a settings form**: required, a validation error, clearable and a hidden input for native forms.

## See also

- [Combobox](https://wingo-ui.com/components/combobox) ([Markdown](https://wingo-ui.com/components/combobox.md))
- [Select](https://wingo-ui.com/components/select) ([Markdown](https://wingo-ui.com/components/select.md))
- [Date Time Picker](https://wingo-ui.com/components/date-time-picker) ([Markdown](https://wingo-ui.com/components/date-time-picker.md))
- [Cron Picker](https://wingo-ui.com/components/cron-picker) ([Markdown](https://wingo-ui.com/components/cron-picker.md))
- [Phone Input](https://wingo-ui.com/components/phone-input) ([Markdown](https://wingo-ui.com/components/phone-input.md))
- [Settings Page](https://wingo-ui.com/components/settings-page) ([Markdown](https://wingo-ui.com/components/settings-page.md))

---

Canonical: https://wingo-ui.com/components/timezone-select
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
