shadcn.io is not affiliated with official shadcn/ui
Time Zone Select Form Block for Radix UI
A grouped select of sixteen time zones with the offset in each label, a real read of the device's own zone after the page loads, and one sentence saying how far the chosen zone is from it.
Installation
Overview
Choose one zone and see how far it is from where you are with this time zone select form for React with Radix UI. A compact header sits over a hairline frame with a plus mark on each corner, in two cells. The first holds a Select of sixteen zones in four groups (Americas, Universal, Europe and Asia and Pacific) with the offset in each label ("UTC+01:00, Lisbon") and an outline Button, "Use this device's zone". The second is about this device: after the page loads it reads the zone name the browser reports, shows it with its offset (dashes until then) and says in one sentence how far the chosen zone is from it ("The team zone is 2 hours ahead of this device"). When the device's zone is not in the list, the cell says so and the button explains instead of choosing. The offsets in the list are typed for 14 October 2026 and ignore daylight rules. It's built from Select, Field, Button, Shine Button and Guides, with a CSS entrance only. For team and account settings, schedulers and any form that stores one zone.
FAQ
How do I change the zones and their offsets?
Edit ZONES at the top: each entry has an id, a city, a group, its minutes from UTC and any other names a browser may report for it (aliases, such as "Asia/Calcutta" for Kolkata). The labels, the groups, the difference sentence and the status all come from that list. The offsets are fixed for one day on purpose, so a daylight change never moves the text under a reader; if you need live offsets, compute them with Intl.DateTimeFormat as browserMinutes does for the device.
What exactly is read from the device, and when?
Only the zone's name: Intl.DateTimeFormat().resolvedOptions().timeZone, read once after the first render, so the server's markup never depends on it and the cell shows dashes until then. Nothing is stored or sent. If the name is in the list (or is one of its aliases) the typed offset is used; if not, the browser is asked for the zone's offset on the block's day, and the cell says the zone is not in the list.
Why does Use this device's zone not look disabled when it can't choose?
A disabled button gives no reason. Here it stays pressable and quiet: pressing it when the device's zone is not in the list says "Your device reports Pacific/Fiji: not in this list" in the foot, and pressing it before the zone is read says so too. When the zone is in the list it selects it and says which one.
How does the select work from the keyboard?
The trigger opens with Enter, Space or the arrow keys, typing a letter jumps to a zone that starts with it, and Escape closes the list with focus back on the trigger. The trigger is named by its label, "Team time zone". The groups are named in the list, and the offset is in every option's text, so nothing depends on colour.
What stays local, and how do I connect it for real?
Nothing is saved: Save team zone says "Lisbon set in this demo. Nothing was saved." and stays quiet while nothing changed. To make it real, store the zone's IANA name (such as "Europe/Lisbon"), never the offset, send it from the submit handler and let your server do the daylight arithmetic. Show its answer in the same status line.
How do I connect it to react-hook-form or TanStack Form?
The value is one string, the IANA name. With react-hook-form, use Controller and call field.onChange with the name from the select's change handler; the device button can call setValue with the listed zone. With TanStack Form, validate that the name is one you support and keep the device read outside the form's state.