# Date input

> A date field on Vercel's UI SDK typed one part at a time in the locale's order, with an optional time, min and max, unavailable days and an ISO value.

Canonical: https://hextaui.com/docs/ui-sdk-date-input
Last updated: 2026-11-14

> **Beta.** Built on Vercel’s UI SDK 0.1.0-beta.2 (`@ui-sdk/react`), a public beta. Its API may change.

```tsx title="components/examples/ui-sdk-date-input/demo.tsx"
import { IconCalendar } from "@tabler/icons-react"

import { Label } from "@/components/ui/label"
import { DateInput, DateInputSegments } from "@/components/ui/ui-sdk-date-input"

function UiSdkDateInputDemo() {
  return (
    <div className="flex w-full max-w-44 flex-col gap-2">
      <Label id="birthday">Birthday</Label>
      <DateInput aria-labelledby="birthday" name="birthday">
        <IconCalendar aria-hidden="true" />
        <DateInputSegments />
      </DateInput>
    </div>
  )
}

export { UiSdkDateInputDemo }
```

## Installation

### CLI

```bash
npx shadcn@latest add https://hextaui.com/r/ui-sdk-date-input.json
```

This adds the component, the HextaUI theme tokens and any HextaUI components it depends on.

### Manual

Add the HextaUI theme tokens (https://hextaui.com/docs/installation#theme) to your global CSS if you haven't yet, then install the dependencies.

```bash
pnpm add @ui-sdk/react@0.1.0-beta.2 @ui-sdk/core@0.1.0-beta.2 cn
```

Copy and paste the following code into your project.

```tsx title="components/ui/ui-sdk-date-input.tsx"
"use client"

import * as React from "react"
import * as DateInputPrimitive from "@ui-sdk/react/date-input"
import { cn } from "cn"

type ClassName<State> =
  string | ((state: State) => string | undefined) | undefined

function mergeClassName<State>(base: string, className: ClassName<State>) {
  return typeof className === "function"
    ? (state: State) => cn(base, className(state))
    : cn(base, className)
}

type DateInputState = DateInputPrimitive.DateInputState

type DateInputContextValue = {
  state: DateInputState | null
  invalid: boolean
}

const DateInputContext = React.createContext<DateInputContextValue>({
  state: null,
  invalid: false,
})

type DateInputProps = React.ComponentProps<
  typeof DateInputPrimitive.DateInput
> & {
  invalid?: boolean
}

function DateInput({
  className,
  children,
  invalid = false,
  ...props
}: DateInputProps) {
  return (
    <DateInputPrimitive.DateInput
      data-invalid={invalid ? "" : undefined}
      className={mergeClassName(
        "group/date-input flex h-9 w-full min-w-0 cursor-text items-center gap-1.5 rounded-md bg-transparent px-2.5 text-sm text-foreground inset-ring-(length:--hairline) inset-ring-input transition-[background-color,box-shadow] duration-150 ease-out-cubic has-[input:focus-visible]:ring-3 has-[input:focus-visible]:ring-focus-ring has-[input:focus-visible]:inset-ring-ring data-invalid:inset-ring-destructive data-invalid:has-[input:focus-visible]:ring-destructive/20 data-invalid:has-[input:focus-visible]:inset-ring-destructive data-readonly:cursor-default data-readonly:bg-muted/40 motion-reduce:transition-none dark:bg-input/30 dark:data-readonly:bg-input/20 forced-colors:border pointer-coarse:h-10 data-disabled:cursor-not-allowed data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 [&>svg]:text-muted-foreground [@media(hover:hover)]:hover:not-has-[input:focus-visible]:not-data-disabled:not-data-invalid:inset-ring-ring/70",
        className
      )}
      {...props}
    >
      {(state) => (
        <DateInputContext.Provider value={{ state, invalid }}>
          {children === undefined ? (
            <DateInputSegments />
          ) : typeof children === "function" ? (
            children(state)
          ) : (
            children
          )}
        </DateInputContext.Provider>
      )}
    </DateInputPrimitive.DateInput>
  )
}

function DateInputSegments() {
  const { state } = React.useContext(DateInputContext)
  if (!state) return null
  return (
    <span data-slot="date-input-segments" className="flex min-w-0 items-center">
      {state.segments.map((segment) => (
        <DateInputSegment key={segment.key} segment={segment} />
      ))}
    </span>
  )
}

function DateInputSegment({
  className,
  segment,
  ...props
}: React.ComponentProps<typeof DateInputPrimitive.DateInputSegment>) {
  const { invalid } = React.useContext(DateInputContext)
  if (!segment.editable) {
    return (
      <DateInputPrimitive.DateInputSegment
        segment={segment}
        className={mergeClassName(
          "whitespace-pre text-muted-foreground",
          className
        )}
        {...props}
      />
    )
  }
  return (
    <DateInputPrimitive.DateInputSegment
      segment={segment}
      aria-invalid={invalid || undefined}
      className={mergeClassName(
        "box-content w-[2ch] min-w-0 rounded-[calc(var(--radius-sm)-1px)] bg-transparent px-[2px] py-0.5 text-center tabular-nums caret-transparent outline-none selection:bg-transparent placeholder:text-muted-foreground read-only:cursor-default focus:bg-primary focus:text-primary-foreground focus:placeholder:text-primary-foreground/60 disabled:cursor-not-allowed data-[type=dayPeriod]:field-sizing-content data-[type=dayPeriod]:w-auto data-[type=dayPeriod]:min-w-[2ch] not-supports-[field-sizing:content]:data-[type=dayPeriod]:w-[2.75em] data-[type=year]:w-[4ch] forced-colors:focus:bg-[Highlight] forced-colors:focus:text-[HighlightText] pointer-coarse:text-touch",
        className
      )}
      {...props}
    />
  )
}

const createDateInputController = DateInputPrimitive.createDateInputController

export {
  DateInput,
  DateInputSegments,
  DateInputSegment,
  createDateInputController,
}
export type { DateInputProps, DateInputState }
export type {
  DateInputController,
  DateInputGranularity,
  DateInputSegmentState,
  DateMatchers,
  HourCycle,
} from "@ui-sdk/react/date-input"
```

Update the import paths to match your project setup.

## Usage

UI SDK adds a little CSS of its own in a cascade layer named `ui-sdk`. Declare that layer at the top of your global stylesheet, above the Tailwind import, so your classes win over it.

```css
@layer ui-sdk;

@import "tailwindcss";
```

```tsx
import { DateInput } from "@/components/ui/ui-sdk-date-input"
```

```tsx
<Label id="start-date">Start date</Label>
<DateInput aria-labelledby="start-date" defaultValue="2026-11-02" />
```

Use a date input for dates people know by heart, like a birthday. The value is an ISO date like `"2026-11-02"`, and it stays `null` until every part holds something. Each part fills as you type and moves on, the day is kept within its month, and a whole date can be pasted into any part. For picking from a calendar, use the Date Picker.

The field is a group, so name it with `aria-labelledby` or `aria-label`. Clicking anywhere in the box focuses the nearest part.

## Composition

```text
DateInput
└── DateInputSegments
    └── DateInputSegment
```

`DateInput` renders its parts on its own. Add `DateInputSegments` yourself to put an icon or other content beside them.

## Examples

### Date and time

`granularity` set to `"hour"`, `"minute"` or `"second"` adds the time, and the value becomes a local date and time like `"2026-11-02T14:30"`.

```tsx title="components/examples/ui-sdk-date-input/date-time.tsx"
import { Label } from "@/components/ui/label"
import { DateInput } from "@/components/ui/ui-sdk-date-input"

function UiSdkDateInputDateTime() {
  return (
    <div className="flex w-full max-w-60 flex-col gap-2">
      <Label id="departure">Departure</Label>
      <DateInput
        aria-labelledby="departure"
        granularity="minute"
        defaultValue="2026-11-02T14:30"
        locale="en-US"
      />
    </div>
  )
}

export { UiSdkDateInputDateTime }
```

### Min and max

A date before `min` or after `max` marks the field invalid, and its form won’t submit.

```tsx title="components/examples/ui-sdk-date-input/min-max.tsx"
"use client"

import * as React from "react"

import { Label } from "@/components/ui/label"
import { DateInput } from "@/components/ui/ui-sdk-date-input"

const min = "2026-01-01"
const max = "2026-12-31"

function UiSdkDateInputMinMax() {
  const [date, setDate] = React.useState<string | null>("2026-06-15")
  const outside = date !== null && (date < min || date > max)

  return (
    <div className="flex w-full max-w-44 flex-col gap-2">
      <Label id="check-in">Check-in</Label>
      <DateInput
        aria-labelledby="check-in"
        aria-describedby="check-in-message"
        min={min}
        max={max}
        defaultValue="2026-06-15"
        onValueChange={(value) => setDate(value)}
      />
      <p
        id="check-in-message"
        className={
          outside
            ? "m-0 text-xs text-destructive"
            : "m-0 text-xs text-muted-foreground"
        }
      >
        {outside ? "Pick a day in 2026." : "Any day in 2026."}
      </p>
    </div>
  )
}

export { UiSdkDateInputMinMax }
```

### Unavailable dates

`unavailable` takes ISO dates, `{ from, to }` ranges, weekdays or a function, and a date on one is invalid.

```tsx title="components/examples/ui-sdk-date-input/unavailable.tsx"
"use client"

import * as React from "react"

import { Label } from "@/components/ui/label"
import { DateInput } from "@/components/ui/ui-sdk-date-input"

function isWeekend(date: string) {
  const day = new Date(`${date}T12:00`).getDay()
  return day === 0 || day === 6
}

function UiSdkDateInputUnavailable() {
  const [date, setDate] = React.useState<string | null>("2026-11-02")
  const weekend = date !== null && isWeekend(date)

  return (
    <div className="flex w-full max-w-44 flex-col gap-2">
      <Label id="delivery-date">Delivery date</Label>
      <DateInput
        aria-labelledby="delivery-date"
        aria-describedby="delivery-date-message"
        defaultValue="2026-11-02"
        unavailable={{ weekday: [0, 6] }}
        onValueChange={(value) => setDate(value)}
      />
      <p
        id="delivery-date-message"
        className={
          weekend
            ? "m-0 text-xs text-destructive"
            : "m-0 text-xs text-muted-foreground"
        }
      >
        {weekend
          ? "We don’t deliver on weekends."
          : "We deliver Monday to Friday."}
      </p>
    </div>
  )
}

export { UiSdkDateInputUnavailable }
```

### Locale

`locale` sets the order and the separators. The digits are always 0 to 9 and the calendar is always Gregorian, so the value means the same date everywhere.

```tsx title="components/examples/ui-sdk-date-input/locale.tsx"
import { Label } from "@/components/ui/label"
import { DateInput } from "@/components/ui/ui-sdk-date-input"

const locales = [
  { locale: "en-GB", label: "Date (en-GB)" },
  { locale: "de-DE", label: "Datum (de-DE)" },
  { locale: "ja-JP", label: "日付 (ja-JP)" },
]

function UiSdkDateInputLocale() {
  return (
    <div className="flex w-full max-w-44 flex-col gap-4">
      {locales.map(({ locale, label }) => (
        <div key={locale} lang={locale} className="flex flex-col gap-2">
          <Label id={`date-${locale}`}>{label}</Label>
          <DateInput
            aria-labelledby={`date-${locale}`}
            locale={locale}
            defaultValue="2026-11-02"
          />
        </div>
      ))}
    </div>
  )
}

export { UiSdkDateInputLocale }
```

### Form

A hidden input submits the ISO date under `name`, here beside a Time Input. `required` stops an empty or half-typed date, and a reset brings back the default.

```tsx title="components/examples/ui-sdk-date-input/form.tsx"
"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import { DateInput } from "@/components/ui/ui-sdk-date-input"
import { TimeInput } from "@/components/ui/ui-sdk-time-input"

function UiSdkDateInputForm() {
  const [submitted, setSubmitted] = React.useState<string | null>(null)

  return (
    <form
      className="flex w-full max-w-72 flex-col gap-3"
      onSubmit={(event) => {
        event.preventDefault()
        const data = new FormData(event.currentTarget)
        setSubmitted(`${data.get("date")} at ${data.get("time")}`)
      }}
    >
      <div className="grid grid-cols-[1fr_auto] gap-2">
        <div className="flex min-w-0 flex-col gap-2">
          <Label id="booking-date">Date</Label>
          <DateInput
            aria-labelledby="booking-date"
            name="date"
            defaultValue="2026-11-02"
            required
          />
        </div>
        <div className="flex flex-col gap-2">
          <Label id="booking-time">Time</Label>
          <TimeInput
            aria-labelledby="booking-time"
            name="time"
            defaultValue="09:30"
            required
          />
        </div>
      </div>
      <div className="flex items-center gap-3">
        <Button type="submit" size="sm">
          Book
        </Button>
        <Button type="reset" size="sm" variant="ghost">
          Reset
        </Button>
      </div>
      <p className="m-0 text-sm text-muted-foreground tabular-nums">
        {submitted ? `Submitted ${submitted}` : "Nothing submitted yet."}
      </p>
    </form>
  )
}

export { UiSdkDateInputForm }
```

### Controller

`createDateInputController()` sets, clears or focuses the field from anywhere. Focus a part by its unit, like `"year"`.

```tsx title="components/examples/ui-sdk-date-input/controller.tsx"
"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import {
  DateInput,
  createDateInputController,
} from "@/components/ui/ui-sdk-date-input"

function UiSdkDateInputController() {
  const [controller] = React.useState(createDateInputController)

  return (
    <div className="flex w-full max-w-60 flex-col gap-2">
      <Label id="publish-on">Publish on</Label>
      <DateInput aria-labelledby="publish-on" controller={controller} />
      <div className="flex items-center gap-2 pt-1">
        <Button
          size="sm"
          variant="outline"
          onClick={() => controller.setValue("2026-12-25")}
        >
          Christmas
        </Button>
        <Button
          size="sm"
          variant="ghost"
          onClick={() => controller.focus("year")}
        >
          Edit year
        </Button>
        <Button size="sm" variant="ghost" onClick={() => controller.clear()}>
          Clear
        </Button>
      </div>
    </div>
  )
}

export { UiSdkDateInputController }
```

### Disabled

A disabled field is skipped by focus and doesn’t submit.

```tsx title="components/examples/ui-sdk-date-input/disabled.tsx"
import { Label } from "@/components/ui/label"
import { DateInput } from "@/components/ui/ui-sdk-date-input"

function UiSdkDateInputDisabled() {
  return (
    <div className="flex w-full max-w-44 flex-col gap-2">
      <Label id="start-date-disabled">Start date</Label>
      <DateInput
        aria-labelledby="start-date-disabled"
        defaultValue="2026-11-02"
        disabled
      />
    </div>
  )
}

export { UiSdkDateInputDisabled }
```

### Read-only

A read-only field takes focus and submits, but keys and typing don’t change it.

```tsx title="components/examples/ui-sdk-date-input/read-only.tsx"
import { Label } from "@/components/ui/label"
import { DateInput } from "@/components/ui/ui-sdk-date-input"

function UiSdkDateInputReadOnly() {
  return (
    <div className="flex w-full max-w-44 flex-col gap-2">
      <Label id="signed-on">Signed on</Label>
      <DateInput
        aria-labelledby="signed-on"
        defaultValue="2026-11-02"
        readOnly
      />
    </div>
  )
}

export { UiSdkDateInputReadOnly }
```

### Right to left

The parts keep the locale’s order, and the arrow keys follow them on screen.

```tsx title="components/examples/ui-sdk-date-input/rtl.tsx"
import { IconCalendar } from "@tabler/icons-react"

import { Label } from "@/components/ui/label"
import { DateInput, DateInputSegments } from "@/components/ui/ui-sdk-date-input"

function UiSdkDateInputRtl() {
  return (
    <div dir="rtl" lang="ar" className="flex w-full max-w-44 flex-col gap-2">
      <Label id="rtl-date">تاريخ البدء</Label>
      <DateInput
        aria-labelledby="rtl-date"
        locale="ar-EG"
        defaultValue="2026-11-02"
      >
        <IconCalendar aria-hidden="true" />
        <DateInputSegments />
      </DateInput>
    </div>
  )
}

export { UiSdkDateInputRtl }
```

## Keyboard

| Key | Action |
| --- | --- |
| `0–9` | Fills the part, then moves to the next one. |
| `/` `.` | Moves to the next part. |
| `↑` `↓` | Steps the part by one, wrapping around. An empty part starts at today. |
| `PageUp` `PageDown` | Steps by more: a year by 5, a month by 3, a day by 7. |
| `Home` `End` | Goes to the part’s first or last value. |
| `←` `→` | Moves to the part before or after. |
| `Backspace` | Removes the last digit, then empties the part, then moves back. |
| `Enter` | Submits the form with what’s typed. |

## Accessibility

The root is a group. Each part is a spinbutton named by its unit, like “month”, with its value and range, and an empty part reads as “Empty”. The text between parts is hidden from screen readers.

Parts are real inputs, so phones show a number pad and typing works before the page’s script runs. They grow to 16px on touch screens so phones don’t zoom in, and the focused part is highlighted rather than showing a caret.

## API reference

Each part wraps the UI SDK part of the same name and accepts all of its props, including `render` and a `className` that can be a function of the part’s state.

### DateInput

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string \| null` | – |  |
| `defaultValue` | `string \| null` | – |  |
| `onValueChange` | `(value: string \| null, details) => void` | – | Runs once every part holds something, and with null when one is emptied. |
| `granularity` | `"day" \| "hour" \| "minute" \| "second"` | `"day"` |  |
| `hourCycle` | `12 \| 24` | – | Defaults to the locale’s clock. |
| `min` | `string` | – |  |
| `max` | `string` | – |  |
| `unavailable` | `DateMatchers` | – | ISO dates, { from, to } ranges, { weekday } or a function. |
| `locale` | `string` | – | Defaults to the page’s language. |
| `invalid` | `boolean` | `false` |  |
| `disabled` | `boolean` | `false` |  |
| `readOnly` | `boolean` | `false` |  |
| `required` | `boolean` | `false` |  |
| `name` | `string` | – |  |
| `form` | `string` | – |  |
| `controller` | `DateInputController` | – | From createDateInputController(). |
| `children` | `ReactNode \| (state) => ReactNode` | – | Defaults to DateInputSegments. |
| `render` | `ReactElement \| (props, state) => ReactElement` | – |  |

| Attribute | Description |
| --- | --- |
| `data-slot="date-input"` | Target the date input in CSS. |
| `data-invalid` | Present when the date is outside min and max, unavailable, or invalid is set. |
| `data-empty` | Present while every part is empty. |
| `data-disabled` | Present when disabled. |
| `data-readonly` | Present when read-only. |
| `data-required` | Present when required. |

### DateInputSegments

Renders a DateInputSegment for each part, in the locale’s order, with the text between them.

### DateInputSegment

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `segment` | `DateInputSegmentData` | – | One of the root state’s segments. |
| `render` | `ReactElement \| (props, state) => ReactElement` | – |  |

| Attribute | Description |
| --- | --- |
| `data-slot="date-input-segment"` | Target a part in CSS. |
| `data-type` | year, month, day, hour, minute, second, dayPeriod, or literal for the text between parts. |
| `data-empty` | Present while the part is empty. |

## Notes for AI assistants

- Install a component with the shadcn CLI: `npx shadcn@latest add https://hextaui.com/r/<name>.json`. It adds the source, the HextaUI theme tokens and any HextaUI components it depends on. `https://hextaui.com/r/all.json` installs every component.
- The code is then owned by the project, like shadcn/ui. There is no HextaUI npm package. HextaUI is MIT licensed and free for personal and commercial use.
- Behavior and accessibility come from Base UI (`@base-ui/react`). Compose with the `render` prop, not `asChild`.
- Styling uses Tailwind CSS v4 with theme tokens. Merge classes with `cn` from the `cn` package.
- Icons come from `@tabler/icons-react`.
- Import components from `@/components/ui/<name>`, hooks from `@/hooks/<name>` and utilities from `@/lib/<name>`.

Every HextaUI doc: https://hextaui.com/llms.txt

## Related

- [Calendar](https://hextaui.com/docs/calendar) - A date grid for single, range and multiple selection, with sliding months, range previews and touch-sized days.
- [Checkbox](https://hextaui.com/docs/checkbox) - A checkbox whose check draws in, with indeterminate parents, groups and labels that share its hover.
- [Combobox](https://hextaui.com/docs/combobox) - A filterable select with chips, groups and async results, in a popup that resizes as you type.
- [Date picker](https://hextaui.com/docs/date-picker) - A button that opens a calendar in a popover, or a bottom sheet on phones, for single dates and ranges.
- [Field](https://hextaui.com/docs/field) - Labels, descriptions and errors wired to their control, with validation states and layouts for forms.
- [Input](https://hextaui.com/docs/input) - A text input with three sizes, invalid and read-only states, native validation styling and a 16px touch font so phones never zoom in.
