# Date Field (/ui/components/react/date-field)



<Playground component="date-field" />

## Usage [#usage]

```tsx
import { DateField } from '@appica/ui-react/date-field'
```

```tsx
<DateField value={date} onValueChange={setDate} />
```

`DateField` is a keyboard-first date entry: the `format` string is split into editable day/month/year segments, each a `spinbutton&#x60;. Type a number, paste, or press &#x2A;*↑/↓** to step a segment; focus advances automatically. The value is a native `Date` (or `null` while empty). It's not based on Base UI — it's a custom component sharing the [`Input`](/ui/components/react/input) styling.

## Examples [#examples]

### Variants [#variants]

```tsx
import { DateField } from '@appica/ui-react/date-field'

const variants = ['outline', 'soft'] as const

export default function DateFieldVariants() {
  return (
    <div className="flex max-w-50 flex-wrap items-center gap-4">
      {variants.map((variant) => (
        <DateField key={variant} defaultValue={new Date(2026, 5, 23)} variant={variant} />
      ))}
    </div>
  )
}
```

### Sizes [#sizes]

```tsx
import { DateField } from '@appica/ui-react/date-field'

const sizes = ['sm', 'md', 'lg'] as const

export default function DateFieldSizes() {
  return (
    <div className="flex max-w-50 flex-wrap items-center gap-4">
      {sizes.map((size) => (
        <DateField key={size} defaultValue={new Date(2026, 5, 23)} size={size} />
      ))}
    </div>
  )
}
```

### Formats [#formats]

The `format` string (date-fns tokens) decides which segments show and how — numeric (`MM`), abbreviated (`MMM`), or full (`MMMM`) months, 2- or 4-digit years, and any literal separators.

```tsx
import { DateField } from '@appica/ui-react/date-field'

const formats = ['MM/dd/yyyy', 'yyyy-MM-dd', 'dd.MM.yyyy', 'MMM d, yyyy']

export default function DateFieldFormats() {
  return (
    <div className="flex max-w-50 flex-wrap items-center gap-4">
      {formats.map((format) => (
        <DateField key={format} defaultValue={new Date(2026, 5, 23)} format={format} />
      ))}
    </div>
  )
}
```

### Controlled [#controlled]

Drive the value with `value` + `onValueChange`. The callback fires with a `Date` once every segment is filled, or `null` when the field is cleared.

```tsx
'use client'

import * as React from 'react'
import { DateField } from '@appica/ui-react/date-field'

export default function DateFieldControlled() {
  const [date, setDate] = React.useState<Date | null>(new Date(2026, 5, 23))

  return (
    <div className="flex w-full max-w-50 flex-col items-center gap-3">
      <DateField value={date} onValueChange={setDate} />
      <p className="text-foreground-muted w-full text-sm">
        Value: <span className="text-foreground font-medium">{date ? date.toLocaleDateString() : 'null'}</span>
      </p>
    </div>
  )
}
```

### With an icon [#with-an-icon]

Use `startSlot` / `endSlot` to render adornments inside the field frame.

```tsx
import { DateField } from '@appica/ui-react/date-field'
import { Calendar } from '@appica/icons-react'

export default function DateFieldWithIcon() {
  return <DateField className="max-w-50" defaultValue={new Date(2026, 5, 23)} startSlot={<Calendar />} />
}
```

### Disabled, read-only & error states [#disabled-read-only--error-states]

`disabled` greys out the field and blocks interaction; `readOnly` keeps it focusable and readable but prevents edits. Set `aria-invalid` to paint the error state — it's mirrored to `data-invalid`. The field also inherits `disabled` and the error state from a surrounding [`Field`](/ui/components/react/field), so wrapping it in a `Field` marked `invalid` produces the same styling and is the better choice when you're also rendering a validation message.

```tsx
import { DateField } from '@appica/ui-react/date-field'

export default function DateFieldStates() {
  return (
    <div className="flex w-full max-w-40 flex-wrap items-center gap-4">
      <DateField defaultValue={new Date(2026, 5, 23)} disabled />
      <DateField defaultValue={new Date(2026, 5, 23)} readOnly />
      <DateField defaultValue={new Date(2026, 5, 23)} aria-invalid />
    </div>
  )
}
```

## RTL [#rtl]

Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior — roving focus, popup placement, and the like — follows the same direction.

```tsx
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'

export default function RootLayout({ children }) {
  return (
    <html lang="ar" dir="rtl">
      <body>
        <DirectionProvider dir="rtl">{children}</DirectionProvider>
      </body>
    </html>
  )
}
```

For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).

<RtlPreview component="date-field" />

## API reference [#api-reference]

| Prop                    | <ColMinWidth width="180">Type</ColMinWidth> | Default        | <ColMinWidth width="220">Description</ColMinWidth>                                     |
| ----------------------- | ------------------------------------------- | -------------- | -------------------------------------------------------------------------------------- |
| `value`                 | <Code>Date \| null</Code>                   | —              | Controlled value. Pair with `onValueChange`.                                           |
| `defaultValue`          | <Code>Date \| null</Code>                   | —              | Uncontrolled initial value.                                                            |
| `onValueChange`         | <Code>(date: Date \| null) => void</Code>   | —              | Fires with a `Date` when complete, or `null` when cleared.                             |
| `format`                | `string`                                    | `'MM/dd/yyyy'` | date-fns token string defining the segments and separators.                            |
| `variant`               | <Code>'outline' \| 'soft'</Code>            | `'outline'`    | Field appearance — bordered or filled.                                                 |
| `size`                  | <Code>'sm' \| 'md' \| 'lg'</Code>           | `'md'`         | Height, padding, and text scale.                                                       |
| `startSlot` / `endSlot` | `ReactNode`                                 | —              | Adornments rendered inside the field frame.                                            |
| `disabled`              | `boolean`                                   | `false`        | Blocks interaction and removes the segments from the tab order.                        |
| `readOnly`              | `boolean`                                   | `false`        | Segments stay focusable and readable but can't be edited.                              |
| `required`              | `boolean`                                   | `false`        | Marks the hidden form input as required (needs `name`).                                |
| `name`                  | `string`                                    | —              | Renders a hidden `<input>` with the ISO date for form submission.                      |
| `unstyled`              | `boolean`                                   | `false`        | Drop the input appearance — for composing inside another field (used by `DatePicker`). |
| `className`             | `string`                                    | —              | Extra classes on the root, merged via `tailwind-merge`.                                |

Renders a `<div role="group">` and forwards the remaining `<div>` props (`id`, `aria-*`, `ref`, …). The error styling is driven by the `data-invalid` attribute; setting `aria-invalid` marks the field invalid — it sets `data-invalid` for styling and conveys the state to assistive technology.

## Accessibility [#accessibility]

* The field is a `role="group"`; each segment is a `role="spinbutton"` with `aria-label` (day/month/year) and `aria-valuenow`/`aria-valuetext`.
* **←/→*&#x2A; move between segments, &#x2A;*↑/↓** step the focused segment, **Home/End** jump to the first/last, **Backspace/Delete** clear; typing fills and auto-advances.
* `disabled` removes the segments from the tab order; `readOnly` keeps them focusable but inert.
* Segment focus styling uses a transition wrapped in `motion-reduce:`, so it's skipped under reduced motion.
