# DateRangePicker

```tsx
import { DateRangePicker } from '@tangible/ui';
```

Interactive docs and examples: [DateRangePicker in Storybook](https://storybook-tangible-ui.pages.dev/?path=/docs/forms-daterangepicker--docs).

## DateRangePicker props

| Prop | Type | Default | Description |
| - | - | - | - |
| `aria-describedby?` | `string` | | ID of element(s) that describe the trigger. `Field.Control` injects this so helper text and errors are announced; without it the injection is silently dropped (no rest-spread on this component). |
| `aria-invalid?` | `"false" \| "true" \| boolean` | | Marks the control invalid. Lands on the trigger button, which also wears `tui-input-group` — that is what lights the danger border. |
| `aria-label?` | `string` | | |
| `aria-labelledby?` | `string` | | ID of the element that labels the trigger. `Field.Control` injects this, and it outranks `aria-label` per AccName 2A — which is why the trigger's own `aria-label` fallback does not have to change. The trigger's value span is appended so the current date stays audible — except under `asChild`, where there is no value span and a phantom id would strip the name. |
| `calendarLabels?` | `CalendarLabels` | | |
| `children?` | `ReactNode` | | Trigger placement. Omit and the picker renders its own input-group button, exactly as before. Provide children to place the trigger yourself — inside a text field's suffix slot, a toolbar — with `<DateRangePicker.Trigger>` marking the spot, and `asChild` on that Trigger to supply your own element: `<DateRangePicker value={range} onChange={commit}> <TextInput value={text} onChange={(e) => setText(e.target.value)} suffix={ <DateRangePicker.Trigger asChild> <IconButton icon="lms/calendar" label="Pick a date range" /> </DateRangePicker.Trigger> } /> </DateRangePicker>` Children with no Trigger among them leaves the picker unopenable; a dev warning fires for that case. |
| `className?` | `string` | | |
| `defaultValue?` | `DateRange \| null` | | |
| `disabled?` | `boolean` | | |
| `id?` | `string` | | |
| `isDayDisabled?` | `(day: Date) => boolean` | | Per-day disable hook, applied on top of min/max. |
| `labels?` | `DateRangePickerLabels` | | |
| `locale?` | `string` | `the document language, falling back to 'en-US'` | BCP 47 locale for month/weekday names and numeric formats. |
| `max?` | `Date` | | Latest selectable day (inclusive, time-of-day ignored). |
| `min?` | `Date` | | Earliest selectable day (inclusive, time-of-day ignored). |
| `onChange?` | `(range: DateRange) => void` | | Fires on Apply with a complete range. |
| `presets?` | `DateRangePreset[] \| false` | `the standard six (Today … Last month)` | Quick-range presets for the side rail; `false` hides the rail. |
| `value?` | `DateRange \| null` | | |
| `weekStartsOn?` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6` | `1` | First day of the week. 0 = Sunday … 6 = Saturday. |
| `withTime?` | `boolean` | `false` | Show times in the trigger and From/To fields. |

### DateRangePicker.Trigger

Also exported as `DateRangePickerTrigger`.

| Prop | Type | Default | Description |
| - | - | - | - |
| `asChild?` | `boolean` | | Render `children` as the trigger instead of the built-in input-group button. The child owns its accessible name and receives no chrome — see this file's header for what that costs and why. |
| `children?` | `React.ReactNode` | | Trigger element, used when `asChild` is set. |
| `className?` | `string` | | |

## Other exports

- `defaultDateRangePresets`: `DateRangePreset[]`. The standard quick-range rail: Today … Last month.
- `defaultDateRangePickerLabels`: `Required<DateRangePickerLabels>`

## Examples

- [With Field Error](https://storybook-tangible-ui.pages.dev/?path=/story/forms-daterangepicker--with-field-error): DateRangePicker inside Field with Field.Error — `aria-invalid` and
  `aria-describedby` land on the trigger, which is the `tui-input-group`
  element, so the danger border lights and the error is announced.

- [asChild trigger](https://storybook-tangible-ui.pages.dev/?path=/story/forms-daterangepicker--as-child-trigger): Same escape hatch as DatePicker's: `DateRangePicker.Trigger asChild` renders
  your element as the trigger, keeping the presets rail, From/To fields, dual
  calendar and Apply/Cancel intact. Three things differ from the default trigger:
  - The child owns its accessible name — name it for the *action* ("Filter
    dates"), since the selected range is not appended to it.
  - No `tui-input-group` chrome, so `aria-invalid` forwards without painting;
    an invalid custom trigger needs its own `[aria-invalid="true"]` rule (see
    DatePicker's AsChildInvalid story for the CSS).
  - `disabled` arrives as `aria-disabled` + `data-disabled` and the popover
    refuses to open — the child may not be a `<button>`.
  The child must be natively focusable and keyboard-activatable: under `asChild`
  the picker attaches no key handler of its own.

  Started empty on purpose, unlike DatePicker's populated equivalent — the pair
  covers both states of the "value lives outside the trigger" pattern.

- [asChild trigger (disabled)](https://storybook-tangible-ui.pages.dev/?path=/story/forms-daterangepicker--as-child-disabled): The disabled counterpart, matching DatePicker's: `aria-disabled` +
  `data-disabled` on a still-focusable element, and an open-direction-only guard
  so a form disabling its controls mid-submit cannot strand an open popover.

Other stories: Default, With time (mock anatomy), No Presets ([interactive page](https://storybook-tangible-ui.pages.dev/?path=/docs/forms-daterangepicker--docs)).

Component API tokens: shared with [DatePicker](https://storybook-tangible-ui.pages.dev/docs/components/datepicker.md).

---

Interactive version: https://storybook-tangible-ui.pages.dev/?path=/docs/forms-daterangepicker--docs
