Work with calendar dates, build month grids, and read and format dates in someone's language.

@ui-sdk/core/date provides date arithmetic, month grids, localized labels, and parsing for calendars and date fields.

The helpers work with date strings such as "2026-10-16" and run without a DOM. Calendar and Date Picker use the same helpers.

Dates as Strings

Real Dates

Build a date with isoDate, and read its fields with dateParts. Months run from 1 to 12. Both functions return undefined when the date doesn't exist:

import { dateParts, isISODate, isoDate } from "@ui-sdk/core/date"

isoDate(2026, 10, 16) // "2026-10-16"
isoDate(2026, 2, 30) // undefined
dateParts("2026-10-16") // { year: 2026, month: 10, day: 16 }
isISODate("2026-02-30") // false
Console
import * as React from "react"
import { dateParts, isISODate, isoDate, toISODate } from "@ui-sdk/core/date"
import {
  Field,
  FieldControl,
  FieldDescription,
  FieldLabel,
} from "@ui-sdk/react/field"
import { useLog } from "./log"

const labels = { year: "Year", month: "Month", day: "Day" }

function Example() {
  const log = useLog()
  const [fields, setFields] = React.useState({
    year: "2026",
    month: "2",
    day: "30",
  })
  const [picked, setPicked] = React.useState("2026-10-16")
  const built = isoDate(
    Number(fields.year),
    Number(fields.month),
    Number(fields.day)
  )
  const change = (next: typeof fields) => {
    setFields(next)
    const date = isoDate(
      Number(next.year),
      Number(next.month),
      Number(next.day)
    )
    log(
      `isoDate(${next.year}, ${next.month}, ${next.day}): ${date ?? "no such day"}`
    )
  }
  const pick = (next: string) => {
    setPicked(next)
    const parts = isISODate(next) ? dateParts(next) : undefined
    log(
      parts
        ? `${next}: year ${parts.year}, month ${parts.month}, day ${parts.day}`
        : "Pick a date"
    )
  }
  return (
    <div data-slot="stack">
      <div data-slot="date-field-group">
        {(["year", "month", "day"] as const).map((field) => (
          <Field key={field} invalid={!built}>
            <FieldLabel>{labels[field]}</FieldLabel>
            <FieldControl
              inputMode="numeric"
              value={fields[field]}
              onChange={(event) =>
                change({ ...fields, [field]: event.target.value })
              }
            />
          </Field>
        ))}
      </div>
      <div data-slot="date-field-group">
        <Field>
          <FieldLabel>Picked date</FieldLabel>
          <FieldControl
            type="date"
            value={picked}
            onChange={(event) => pick(event.target.value)}
          />
        </Field>
        <button type="button" onClick={() => pick(toISODate(new Date())!)}>
          Today
        </button>
      </div>
    </div>
  )
}

export { Example }
Change February 30 to February 28 to get a valid date. Pick another date below to read its year, month, and day, or press Today.

These strings use YYYY-MM-DD, the same format as <input type="date">. Valid dates sort in date order, so comparisons such as start < end work directly.

To bring in an existing value, use toISODate. It accepts an ISO string, a Date, or an object with year, month, and day, including a Temporal.PlainDate:

import { toISODate } from "@ui-sdk/core/date"

toISODate({ year: 2026, month: 10, day: 16 }) // "2026-10-16"
toISODate(new Date()) // Today's date in the runtime's local time zone.

A Date represents an instant, so this conversion uses its local year, month, and day. The resulting string has no time zone.

Arithmetic

Whole Days

For a three-night stay, add three days to check-in:

import { addDays, daysBetween } from "@ui-sdk/core/date"

addDays("2026-12-24", 3) // "2026-12-27"
daysBetween("2026-12-24", "2026-12-27") // 3

Negative numbers move backward. The arithmetic uses whole days, so crossing a daylight saving change doesn't add or lose a day.

Use clampDate to keep a date inside a booking season. weekday returns 0 for Sunday through 6 for Saturday:

import { clampDate, weekday } from "@ui-sdk/core/date"

clampDate("2026-10-20", "2026-11-01", "2027-01-31") // "2026-11-01"
weekday("2026-12-24") // 4: Thursday.
Console
import * as React from "react"
import {
  addDays,
  clampDate,
  daysBetween,
  formatDate,
  weekday,
} from "@ui-sdk/core/date"
import {
  Field,
  FieldControl,
  FieldDescription,
  FieldLabel,
} from "@ui-sdk/react/field"
import { useLog } from "./log"

const season = { min: "2026-11-01", max: "2027-01-31" }
const today = "2026-10-16"
const days = [
  "Sunday",
  "Monday",
  "Tuesday",
  "Wednesday",
  "Thursday",
  "Friday",
  "Saturday",
]

function stay(picked: string, nights: number) {
  const checkIn = clampDate(picked || today, season.min, season.max)
  return { checkIn, checkOut: addDays(checkIn, nights) }
}

function Example() {
  const log = useLog()
  const [picked, setPicked] = React.useState("2026-12-24")
  const [nights, setNights] = React.useState(3)
  const { checkIn, checkOut } = stay(picked, nights)
  const change = (nextPicked: string, nextNights: number) => {
    setPicked(nextPicked)
    setNights(nextNights)
    const next = stay(nextPicked, nextNights)
    if (next.checkIn !== nextPicked) {
      log(`Moved into the season: ${next.checkIn}`)
    }
    log(
      `${days[weekday(next.checkIn)]} to ${days[weekday(next.checkOut)]}, ${daysBetween(today, next.checkIn)} days after ${formatDate(today, "en-US", { month: "short", day: "numeric" })}`
    )
  }
  return (
    <div data-slot="stack">
      <div data-slot="date-field-group">
        <Field>
          <FieldLabel>Check-in</FieldLabel>
          <FieldControl
            type="date"
            value={picked}
            onChange={(event) => change(event.target.value, nights)}
          />
        </Field>
        <Field>
          <FieldLabel>Nights</FieldLabel>
          <FieldControl
            type="number"
            min={1}
            max={14}
            value={nights}
            onChange={(event) =>
              change(picked, Number(event.target.value) || 1)
            }
          />
        </Field>
      </div>
      <Field>
        <FieldLabel>Check-out</FieldLabel>
        <FieldControl type="date" readOnly value={checkOut} data-check-out />
        <FieldDescription data-check-in>
          Check-in {days[weekday(checkIn)]},{" "}
          {formatDate(checkIn, "en-US", { month: "long", day: "numeric" })}
        </FieldDescription>
      </Field>
    </div>
  )
}

export { Example }
Change the check-in date or number of nights. Try a check-in before November 1 to see it move into the booking season.

Weeks and Months

Moving by months keeps the day when possible. January 31 becomes February 28 because February has no 31st:

import { addMonths, addYears } from "@ui-sdk/core/date"

addMonths("2026-01-31", 1) // "2026-02-28"
addYears("2028-02-29", 1) // "2029-02-28"

For boundaries, use startOfWeek and endOfWeek, or startOfMonth and endOfMonth. The week helpers take the first weekday, with 0 for Sunday:

import {
  daysInMonth,
  endOfMonth,
  isSameMonth,
  startOfWeek,
} from "@ui-sdk/core/date"

startOfWeek("2026-10-16", 1) // "2026-10-12": Monday.
endOfMonth("2026-02-10") // "2026-02-28"
daysInMonth(2028, 2) // 29
isSameMonth("2026-09-28", "2026-10-04") // false
import * as React from "react"
import {
  addMonths,
  addYears,
  daysInMonth,
  endOfMonth,
  endOfWeek,
  isSameMonth,
  startOfMonth,
  startOfWeek,
} from "@ui-sdk/core/date"

function Example() {
  const [date, setDate] = React.useState("2026-01-31")
  const [weekStartsOn, setWeekStartsOn] = React.useState(1)
  const [year, month] = date.split("-").map(Number)
  const week = {
    start: startOfWeek(date, weekStartsOn),
    end: endOfWeek(date, weekStartsOn),
  }
  const steps: [string, string, () => string][] = [
    ["month-back", "− Month", () => addMonths(date, -1)],
    ["month", "+ Month", () => addMonths(date, 1)],
    ["year", "+ Year", () => addYears(date, 1)],
  ]
  return (
    <div data-slot="stack">
      <div data-slot="controls">
        {steps.map(([value, text, next]) => (
          <button
            key={value}
            type="button"
            data-size="sm"
            onClick={() => setDate(next())}
          >
            {text}
          </button>
        ))}
        <button
          type="button"
          data-size="sm"
          onClick={() => setDate("2026-01-31")}
        >
          Reset
        </button>
        <select
          aria-label="Weeks start on"
          value={weekStartsOn}
          onChange={(event) => setWeekStartsOn(Number(event.target.value))}
        >
          <option value={0}>Weeks start on Sunday</option>
          <option value={1}>Weeks start on Monday</option>
          <option value={6}>Weeks start on Saturday</option>
        </select>
      </div>
      <p data-date data-slot="date-value">
        {date}
      </p>
      <dl>
        <dt>Week</dt>
        <dd data-this-week>
          {week.start} to {week.end}
          {!isSameMonth(week.start, week.end) && ", across two months"}
        </dd>
        <dt>Month</dt>
        <dd data-this-month>
          {startOfMonth(date)} to {endOfMonth(date)}, {daysInMonth(year, month)}{" "}
          days
        </dd>
      </dl>
    </div>
  )
}

export { Example }
Press + Month from January 31, then change which day starts the week. Reset and add two years to try the same move in a leap year.

Month Grids

A Month to Pick From

monthGrid returns rows of seven ISO dates. Days from the neighboring months fill the edges. Choose the first weekday with weekStartsOn:

import { firstDayOfWeek, monthGrid } from "@ui-sdk/core/date"

const weeks = monthGrid("2026-10-01", {
  weekStartsOn: firstDayOfWeek("de-DE"),
})
// The first week runs from "2026-09-28" to "2026-10-04".

Render one row per week and one button per date. This example adds roving focus, using each date as a key. Its custom move adds one day for a horizontal arrow or seven for a vertical arrow. When focus reaches another month, the example renders that month.

import * as React from "react"
import { createRovingFocus, definePrimitive, selects } from "@ui-sdk/core"
import {
  addDays,
  addMonths,
  firstDayOfWeek,
  formatDate,
  isSameMonth,
  monthGrid,
  startOfMonth,
  weekdayNames,
  type ISODate,
} from "@ui-sdk/core/date"
import { createParts } from "@ui-sdk/react/primitive"

type MonthOptions = {
  defaultValue: ISODate
  locale: string
}

type MonthState = {
  value: ISODate
  month: ISODate
  focused: string | null
  locale: string
}

type MonthRootState = {
  title: string
  weekdays: string[]
  weeks: ISODate[][]
}

const steps: Record<string, number> = {
  ArrowUp: -7,
  ArrowDown: 7,
  ArrowLeft: -1,
  ArrowRight: 1,
}

const month = definePrimitive({
  name: "month",
  options: { defaultValue: "string", locale: "string" },
  state: (options: MonthOptions): MonthState => ({
    value: options.defaultValue,
    month: startOfMonth(options.defaultValue),
    focused: null,
    locale: options.locale,
  }),
  sync: (options) => ({ locale: options.locale }),
  api: (context) => {
    const { store } = context
    const roving = createRovingFocus<MonthState, { value: string }>(context, {
      slot: "day",
      key: "focused",
      remember: false,
      preferred: (day) => day.key === store.get().value,
      claims: (state, day) => day.key === state.value,
      move: (event, { from, rtl }) => {
        const by = steps[event.key]
        return by === undefined
          ? undefined
          : addDays(from.key, rtl && Math.abs(by) === 1 ? -by : by)
      },
    })
    let view: (MonthRootState & { month: ISODate; locale: string }) | undefined
    const viewOf = (state: MonthState) => {
      if (view?.month !== state.month || view.locale !== state.locale) {
        const weekStartsOn = firstDayOfWeek(state.locale)
        view = {
          month: state.month,
          locale: state.locale,
          title: formatDate(state.month, state.locale, {
            month: "long",
            year: "numeric",
          }),
          weekdays: weekdayNames(state.locale, "short", weekStartsOn),
          weeks: monthGrid(state.month, { weekStartsOn }),
        }
      }
      return view
    }
    return {
      roving,
      viewOf,
      select: (date: ISODate) => store.set({ value: date }),
      turn: (by: number) =>
        store.set({ month: addMonths(store.get().month, by) }),
      keydown(event: KeyboardEvent, el: HTMLElement) {
        const moved = roving.keydown(event, el)
        if (moved && !isSameMonth(moved.key, store.get().month)) {
          store.set({ month: startOfMonth(moved.key) })
        }
      },
    }
  },
  slots: (slot) => ({
    root: slot<{}, MonthRootState>({
      state: (state, { api }) => {
        const { title, weekdays, weeks } = api.viewOf(state)
        return { title, weekdays, weeks }
      },
    }),
    grid: slot({ attrs: () => ({ role: "grid" }) }),
    day: slot<{ value: string }, { selected: boolean; outside: boolean }>({
      props: { value: "string" },
      selects: selects({ focused: ({ key }) => [key, null] }),
      state: (state, { key }) => ({
        selected: key === state.value,
        outside: !isSameMonth(key, state.month),
      }),
      attrs: (state, part) => ({
        type: "button",
        role: "gridcell",
        tabindex: part.api.roving.tabIndex(state, part),
        "aria-selected": String(part.key === state.value),
        "aria-label": formatDate(part.key, state.locale),
        "data-selected": part.key === state.value ? "" : undefined,
        "data-outside": isSameMonth(part.key, state.month) ? undefined : "",
      }),
      press: ({ api, key }) => api.select(key),
      on: { keydown: (event, { el, api }) => api.keydown(event, el!) },
      bind: (el, part) => part.api.roving.bind(el, part),
    }),
    previous: slot({
      attrs: () => ({ type: "button" }),
      press: ({ api }) => api.turn(-1),
    }),
    next: slot({
      attrs: () => ({ type: "button" }),
      press: ({ api }) => api.turn(1),
    }),
  }),
})

const parts = createParts(month, {
  root: "div",
  grid: "div",
  day: "button",
  previous: "button",
  next: "button",
})

const Month = parts.Root

const MonthGrid = parts.Grid

const MonthDay = parts.Day

const MonthPrevious = parts.Previous

const MonthNext = parts.Next

function Example() {
  const [locale, setLocale] = React.useState("en-US")
  return (
    <div data-slot="stack">
      <select
        aria-label="Locale"
        value={locale}
        onChange={(event) => setLocale(event.target.value)}
      >
        <option value="en-US">English (US)</option>
        <option value="de-DE">Deutsch</option>
      </select>
      <Month defaultValue="2026-10-16" locale={locale}>
        {({ title, weekdays, weeks }) => (
          <>
            <div data-slot="row">
              <MonthPrevious aria-label="Previous month">‹</MonthPrevious>
              <span aria-live="polite" data-slot="month-heading">
                {title}
              </span>
              <MonthNext aria-label="Next month">›</MonthNext>
            </div>
            <MonthGrid aria-label={title}>
              <div role="row" data-slot="month-row">
                {weekdays.map((name) => (
                  <span key={name} role="columnheader">
                    {name}
                  </span>
                ))}
              </div>
              {weeks.map((week) => (
                <div key={week[0]} role="row" data-slot="month-row">
                  {week.map((day) => (
                    <MonthDay key={day} value={day}>
                      {Number(day.slice(8))}
                    </MonthDay>
                  ))}
                </div>
              ))}
            </MonthGrid>
          </>
        )}
      </Month>
    </div>
  )
}

export {
  Example,
  month,
  Month,
  MonthDay,
  MonthGrid,
  MonthNext,
  MonthPrevious,
  type MonthOptions,
  type MonthRootState,
  type MonthState,
}
Tab into the month and use the arrow keys to move by days and weeks. Move beyond the month to turn the page, or change the locale to see a different first weekday.

Weeks That Fit the Month

A grid has six weeks by default, which keeps adjacent calendars the same height. Pass fixedWeeks: false to return only the weeks the month needs:

monthGrid("2026-02-01").length // 6
monthGrid("2026-02-01", { fixedWeeks: false }).length // 4
import * as React from "react"
import { formatDate, isSameMonth, monthGrid } from "@ui-sdk/core/date"

const months = ["2026-02-01", "2026-03-01", "2026-08-01"]

function Example() {
  const [fixedWeeks, setFixedWeeks] = React.useState(true)
  return (
    <div data-slot="stack">
      <div data-slot="controls">
        <label data-slot="label">
          <input
            type="checkbox"
            checked={fixedWeeks}
            onChange={(event) => setFixedWeeks(event.target.checked)}
          />
          Six weeks each
        </label>
      </div>
      <div data-slot="month-sizes-row">
        {months.map((month) => (
          <div key={month} data-month={month} data-slot="month-sizes-month">
            <p>
              {formatDate(month, "en-US", { month: "long", year: "numeric" })}
            </p>
            {monthGrid(month, { fixedWeeks }).map((week) => (
              <div key={week[0]} data-week data-slot="month-sizes-week">
                {week.map((day) => (
                  <span
                    key={day}
                    data-outside={isSameMonth(day, month) ? undefined : ""}
                  >
                    {Number(day.slice(8))}
                  </span>
                ))}
              </div>
            ))}
          </div>
        ))}
      </div>
    </div>
  )
}

export { Example }
Clear Six weeks each. February, March, and August 2026 need four, five, and six rows when weeks start on Sunday.

Names and Labels

Names in Any Language

Pass a locale to format a date for the person reading it. Without one, these functions use the runtime's locale:

import { formatDate, formatDateRange } from "@ui-sdk/core/date"

formatDate("2026-10-16", "de-DE") // "Freitag, 16. Oktober 2026"
formatDate("2026-10-16", "en-US", { month: "long", year: "numeric" }) // "October 2026"
formatDateRange("2026-10-16", "2026-10-20", "en-US") // "Oct 16 – 20, 2026"

formatDate takes Intl.DateTimeFormat options. Its default includes the weekday, day, month, and year, which makes a useful accessible label for a day button.

For a calendar's headings, get month and weekday names separately. Keep the weekday names in the same order as the grid:

import { firstDayOfWeek, monthNames, weekdayNames } from "@ui-sdk/core/date"

const firstDay = firstDayOfWeek("de-DE") // 1: Monday.
const weekdays = weekdayNames("de-DE", "short", firstDay)
const months = monthNames("de-DE")

You can get the page's locale with pageLocale. firstDayOfWeek also respects an explicit week preference, such as "en-US-u-fw-mon" for Monday.

import * as React from "react"
import {
  dateFormatter,
  firstDayOfWeek,
  formatDate,
  formatDateRange,
  formatDateToParts,
  monthNames,
  weekdayNames,
} from "@ui-sdk/core/date"

const locales = [
  ["en-US", "English (US)"],
  ["en-US-u-fw-mon", "English (US), weeks from Monday"],
  ["de-DE", "Deutsch"],
  ["fr-FR", "Français"],
  ["ja-JP", "日本語"],
  ["fa-IR", "فارسی"],
]

const date = "2026-10-16"

function Example() {
  const [locale, setLocale] = React.useState("en-US")
  const first = firstDayOfWeek(locale)
  return (
    <div data-slot="stack">
      <div data-slot="controls">
        <select
          aria-label="Locale"
          value={locale}
          onChange={(event) => setLocale(event.target.value)}
        >
          {locales.map(([value, text]) => (
            <option key={value} value={value}>
              {text}
            </option>
          ))}
        </select>
      </div>
      <p data-label data-slot="date-label">
        {formatDateToParts(date, locale, { month: "long", day: "numeric" }).map(
          (part, index) => (
            <span key={index} data-type={part.type}>
              {part.value}
            </span>
          )
        )}
      </p>
      <dl>
        <dt>Long</dt>
        <dd data-long>{formatDate(date, locale)}</dd>
        <dt>Range</dt>
        <dd data-range>{formatDateRange(date, "2026-10-20", locale)}</dd>
        <dt>Week</dt>
        <dd data-weekdays>{weekdayNames(locale, "short", first).join(" ")}</dd>
        <dt>Months</dt>
        <dd data-months>
          {monthNames(locale, "short").slice(0, 4).join(", ")}, …
        </dd>
        <dt>Calendar</dt>
        <dd data-calendar>
          {dateFormatter(locale, { month: "long" }).resolvedOptions().calendar}
        </dd>
      </dl>
    </div>
  )
}

export { Example }
Change the locale to compare the date label, range, weekday order, and month names. Choose Persian to see localized labels on the same Gregorian dates.

For a label with separate styles, formatDateToParts returns each formatted piece and its type:

import { formatDateToParts } from "@ui-sdk/core/date"

const parts = formatDateToParts("2026-10-16", "en-US", {
  month: "long",
  day: "numeric",
})
// [{ type: "month", value: "October" }, { type: "literal", value: " " }, { type: "day", value: "16" }]

These helpers use Intl and reuse its formatters. They keep month and day labels aligned with the Gregorian grid. A locale that uses different months, such as Persian, uses Gregorian labels; calendars that share those months, such as Thai Buddhist, can keep their own years. dateFormatter(locale, options) exposes the cached formatter when you need it directly.

Today

todayDate() returns today's date where the code runs. Pass a time zone when the date belongs to a particular place:

import { todayDate } from "@ui-sdk/core/date"

todayDate()
todayDate("Asia/Tokyo")
import * as React from "react"
import { formatDate, todayDate } from "@ui-sdk/core/date"

const zones = [
  ["Pacific/Auckland", "Auckland"],
  ["Asia/Tokyo", "Tokyo"],
  ["Europe/London", "London"],
  ["America/Los_Angeles", "Los Angeles"],
]

function Example() {
  const [mounted, setMounted] = React.useState(false)
  React.useEffect(() => setMounted(true), [])
  return (
    <ul aria-label="Today" data-slot="list">
      {zones.map(([zone, city]) => (
        <li key={zone} data-slot="row">
          {city}
          <span data-slot="hint" data-zone={zone}>
            {mounted
              ? formatDate(todayDate(zone), "en-US", { dateStyle: "medium" })
              : ""}
          </span>
        </li>
      ))}
      <li data-slot="row">
        Here
        <span data-slot="hint" data-zone="here">
          {mounted
            ? formatDate(todayDate(), "en-US", { dateStyle: "medium" })
            : ""}
        </span>
      </li>
    </ul>
  )
}

export { Example }
Compare today's date in each city with Here, the browser's local date. Cities can show different dates near midnight.

For server rendering, pass the same time zone on the server and client, or wait until the component mounts to mark today.

Typing a Date

Read What People Type

parseDate reads the locale's date order and returns an ISO date. It returns undefined for incomplete or invalid input:

import { parseDate } from "@ui-sdk/core/date"

parseDate("10/16/2026", "en-US") // "2026-10-16"
parseDate("16.10.2026", "de-DE") // "2026-10-16"
parseDate("16 oct 2026", "fr") // "2026-10-16"
parseDate("Feb 30", "en-US") // undefined

Dots, slashes, and spaces work as separators. The parser also accepts ISO dates, month names, locale digits, and digits without separators, such as "16102026" in German.

When the year is missing, the parser uses the reference date's year. That defaults to the current year. Pass referenceDate when the result should stay predictable:

parseDate("October 16", "en-US", { referenceDate: "2026-01-01" }) // "2026-10-16"
parseDate("16.10.70", "de-DE", { referenceDate: "2026-01-01" }) // "1970-10-16"
parseDate("16.10.70", "de-DE", { referenceDate: "2060-01-01" }) // "2070-10-16"

Two-digit years fall in the 100-year window ending 20 years after the reference year.

Console
import * as React from "react"
import { datePattern, formatDate, parseDate } from "@ui-sdk/core/date"
import {
  Field,
  FieldControl,
  FieldDescription,
  FieldLabel,
} from "@ui-sdk/react/field"
import { useLog } from "./log"

const locales = [
  ["en-US", "English (US)"],
  ["de-DE", "Deutsch"],
  ["fr-FR", "Français"],
  ["ar-EG", "العربية"],
]

function Example() {
  const log = useLog()
  const [locale, setLocale] = React.useState("en-US")
  const [text, setText] = React.useState("10/16/26")
  const [reference, setReference] = React.useState("2026-10-16")
  const pattern = datePattern(locale)
  const read = (next: {
    locale?: string
    text?: string
    reference?: string
  }) => {
    const options = { locale, text, reference, ...next }
    const date = parseDate(options.text, options.locale, {
      referenceDate: options.reference,
    })
    log(
      `“${options.text}”: ${date ? `${date}, ${formatDate(date, options.locale)}` : "not a date"}`
    )
  }
  return (
    <div data-slot="stack">
      <div data-slot="controls">
        <select
          aria-label="Locale"
          value={locale}
          onChange={(event) => {
            setLocale(event.target.value)
            read({ locale: event.target.value })
          }}
        >
          {locales.map(([value, label]) => (
            <option key={value} value={value}>
              {label}
            </option>
          ))}
        </select>
        <input
          type="date"
          aria-label="Reference date"
          value={reference}
          onChange={(event) => {
            const next = event.target.value || "2026-10-16"
            setReference(next)
            read({ reference: next })
          }}
        />
      </div>
      <Field>
        <FieldLabel>Date</FieldLabel>
        <FieldControl
          placeholder={pattern.placeholder}
          value={text}
          onChange={(event) => {
            setText(event.target.value)
            read({ text: event.target.value })
          }}
        />
        <FieldDescription data-pattern>
          {pattern.fields.join(` ${pattern.separator} `)}
        </FieldDescription>
      </Field>
    </div>
  )
}

export { Example }
Type a date, then change the locale to try another order. Enter 16.10.70 in German and change the reference year from 2026 to 2060 to see which century is used.

Use datePattern for the input's placeholder and a hint showing the order:

import { datePattern } from "@ui-sdk/core/date"

const pattern = datePattern("de-DE")
pattern.fields // ["day", "month", "year"]
pattern.placeholder // "TT.MM.JJJJ"
pattern.separator // "."
// pattern.example is December 31 in the current year, written in this locale.

API Reference

All of these functions come from @ui-sdk/core/date.

You Want ToUse
Validate or convert a dateisoDate, isISODate, dateParts, toISODate
Move a date or measure a stayaddDays, daysBetween, weekday, clampDate
Move between months or find their boundariesaddMonths, addYears, startOfWeek, endOfWeek, startOfMonth, endOfMonth, daysInMonth, isSameMonth
Build rows for a calendarmonthGrid
Label dates and calendar headingsformatDate, formatDateRange, formatDateToParts, monthNames, weekdayNames, firstDayOfWeek, dateFormatter
Find today in a time zonetodayDate
Read typed dates or show their formatparseDate, datePattern