Guides
Temporal & Safari
Why the engine speaks Temporal, providing temporal-polyfill on Safari and synchronous requirements.
Why Temporal
The engine speaks Temporal — separate types for a moment in a time zone, a date without a time and a duration — so DST transitions, all-day events and cross-zone schedules are correct by construction rather than by discipline. There is no Date fallback and there will not be one: Date only ever builds a wall clock in the host’s zone, so a Kyiv schedule opened from Berlin would silently render Berlin’s grid.
Temporal, and Safari
Chrome and Edge ship Temporal from 144 and Firefox from 139. Modern runtimes such as Node 24+ also provide Temporal natively.
Safari and older browsers do not yet provide native Temporal. The host application must provide Temporal before rendering. Install temporal-polyfill as a separate package:
npm install temporal-polyfill
Loading efficiently (avoiding Chrome downloads)
If you use a static import 'temporal-polyfill/global', the polyfill will detect native Temporal at runtime and safely avoid overwriting it. However, bundlers (such as Vite, Webpack, or esbuild) include static imports directly into the bundle, meaning Google Chrome users will still download and parse the polyfill bytes unnecessarily.
To avoid downloading the polyfill in Chrome, Edge, and Firefox, use a conditional dynamic import before mounting your application:
// main.tsx (or application entry point) import { createRoot } from 'react-dom/client' import { App } from './App' // Only downloads the polyfill chunk in browsers without native Temporal (e.g. Safari). // In Chrome, Edge, and Firefox this evaluates to false and downloads 0 bytes. if (!('Temporal' in globalThis)) { await import('temporal-polyfill/global') } const container = document.getElementById('root') if (container) { createRoot(container).render(<App />) }
Bundlers split dynamic imports into a separate chunk. In browsers with native Temporal, the 'Temporal' in globalThis check evaluates to true, so the browser never requests the chunk (0 KB downloaded). In Safari, the chunk is fetched and applied before createRoot renders the tree.
Alternatively, if bundle splitting is not a concern, a simple static import in your entry module also works:
// Application entry module (e.g. index.ts or main.tsx) // Simpler, but bundles the polyfill for all browsers including Chrome. import 'temporal-polyfill/global'
Synchronous requirement
Chronous reads globalThis.Temporal synchronously. If it is absent, calendar results report MissingTemporalError immediately.
import { buildCalendar, MissingTemporalError } from '@midstem/chronous' try { // Chronous reads globalThis.Temporal synchronously. // If absent, MissingTemporalError is thrown immediately. buildCalendar(range, events) } catch (error) { if (error instanceof MissingTemporalError) { console.error('Temporal is not available on globalThis') } }
In React, useCalendar catches the error and returns it in the error property, with calendar set to null.
import { Calendar } from '@midstem/chronous-react' import type { CalendarRange, EventInput } from '@midstem/chronous-react' const range: CalendarRange = { view: 'week', currentDate: '2026-03-18', timeZone: 'Europe/Kyiv', } const events: EventInput[] = [ { id: 'standup', start: '2026-03-18T09:00', duration: 'PT30M' }, ] export const Board = () => ( <Calendar.Root range={range} events={events}> <Calendar.TimeGrid hourHeight={48}> <Calendar.DayColumns className="column"> <Calendar.TimeSlots className="line" /> <Calendar.TimedEvents className="event" /> </Calendar.DayColumns> </Calendar.TimeGrid> </Calendar.Root> )
Server rendering
Node 24 and modern runtimes already ship Temporal natively. On server runtimes without native Temporal, import temporal-polyfill/global at startup (e.g. in instrumentation.ts) before any calendar calls — Server rendering has the details.