Askr
UI & Componentsexperimental

Calendar and Date Picker

Use calendar and date picker with the current published Askr packages.

  • @askrjs/themes/calendar0.0.13
  • @askrjs/themes/date-picker0.0.13

How to use calendar and date picker

Calendar and Date Picker should use the standard themed surface unless the application is deliberately implementing its own design system on top of @askrjs/ui.

  1. Import the themed component from @askrjs/themes/components; use @askrjs/ui directly only when building a custom visual system.
  2. Keep calendar and date picker labels, state, and application actions visible at the call site instead of styling internal DOM nodes.
  3. Verify keyboard behavior, focus movement or return, disabled state, and both standard themes.
import { DatePicker, DatePickerInput } from '@askrjs/themes/components';

<DatePicker value={dueDate()} onValueChange={dueDate.set}>
  <DatePickerInput aria-label="Project due date" />
</DatePicker>

Purpose

Calendar renders the visual grid of a month view and Date Picker renders a date input — both are presentation-only wrappers from @askrjs/themes, with no @askrjs/ui headless calendar or date-picker primitive backing them. There's no month math, no range-selection logic, and no popover wiring built in; you get markup and ARIA roles for a date grid, and you supply the state.

Install and import

Import `Calendar`, `CalendarHeader`, `CalendarCaption`, `CalendarNav`, `CalendarPreviousButton`, `CalendarNextButton`, `CalendarGrid`, `CalendarHead`, `CalendarBody`, `CalendarRow`, `CalendarCell`, and `CalendarDay` from `@askrjs/themes/calendar`. `DatePicker` and `DatePickerInput` come from `@askrjs/themes/date-picker`. Both subpaths compile to the same shared components barrel as the rest of the catalog, so treat the subpath as documentation of intent rather than a separate bundle.

Live examples

A working calendar means computing the days for the visible month yourself — leading and trailing days from adjacent months, which cells are `today`, `selected`, or part of a range — and passing the right booleans to `CalendarDay` on every render. Date Picker's baseline example is simpler: `DatePickerInput` defaults to `type="date"`, so a plain native date input with no extra JavaScript is a legitimate example on its own.

Anatomy

The calendar grid nests `CalendarGrid` (`role="grid"`) containing `CalendarHead` (`role="row"`) for weekday labels and `CalendarBody` holding `CalendarRow` (`role="row"`) elements of `CalendarCell` (`role="gridcell"`) wrapping `CalendarDay` buttons. `CalendarHeader` holds `CalendarCaption`, the month/year label, and `CalendarNav` with `CalendarPreviousButton` and `CalendarNextButton`. `DatePicker` is just an outer slot around `DatePickerInput`, which renders a real `<input>`.

State model

None of it is tracked internally. `CalendarDay` takes `disabled`, `outside`, `rangeStart`, `rangeMiddle`, `rangeEnd`, `selected`, and `today` as plain booleans that it turns into matching `aria-*` and `data-*` attributes — your code decides which day gets which flag on every render, including recomputing the whole grid when the visible month changes. `DatePickerInput` has no controlled-value helpers beyond what a native `<input>` already gives you; read and write its `value` the way you would any form input.

Keyboard and accessibility

`CalendarGrid`'s `role="grid"` structure is correct, but grid-style arrow-key navigation between days isn't wired up — you'd need to manage focus movement and `tabindex` yourself to match the roving-tabindex pattern the ARIA grid role expects. `CalendarPreviousButton` and `CalendarNextButton` default to `aria-label="Previous month"`/`"Next month"` (overridable via `children`), so navigation controls are at least labeled for screen readers out of the box. `DatePickerInput` inherits full native `<input>` keyboard support for free since it's a real `<input type="date">` by default.

Styling and tokens

Every calendar part carries a `data-slot` (`calendar-day`, `calendar-nav`, `calendar-grid`, and so on), and `CalendarDay` additionally exposes `data-selected`, `data-today`, `data-outside`, `data-range-start`, `data-range-middle`, and `data-range-end` so you can style day states from CSS without reaching into JavaScript. The nav buttons pick up the shared `btn btn-icon btn-ghost` classes, matching icon buttons elsewhere in the catalog. `DatePickerInput` applies the same `input` class as `Input` and other form fields.

API

Everything except `CalendarDay` accepts only the generic `CatalogComponentProps` shape (`as`, `asChild`, `class`, `children`, `ref`) with no component-specific typed props. `CalendarDay` adds the seven typed booleans listed above. `DatePickerInput` accepts a `type` prop, defaulting to `"date"`, plus whatever native `<input>` attributes you spread in — there's no `DatePickerProps` type covering things like `min`, `max`, or a selected-range shape, because that logic doesn't exist here.

Edge cases

Multi-date and range selection are entirely your responsibility — the `rangeStart`/`rangeMiddle`/`rangeEnd` flags exist so you have somewhere to put the result, not because the component computes ranges for you. If you need a popover-attached date picker rather than a bare input, you'll have to pair `DatePicker` with Popover yourself; there's no built-in trigger/content relationship between the two.

See Popover if you're building a calendar-in-a-popover date picker, since Date Picker doesn't include that wiring. See Native Select and Input OTP for a comparison of experimental components where the native element does more of the accessibility work for you.