--- title: Calendar description: Calendars let users pick one or more dates from a month grid. They support single, multiple, and range selection without leaving the surrounding form. category: Data display --- import { meta as CalendarMeta, Default, Multiple, Range } from '@/content/stories/calendar'; import Canvas from '@/components/component-docs/canvas'; import Demo from '@/components/component-docs/demo'; export const config = { dependencies: { components: [Canvas, Demo], scripts: [ '../../components/component-docs/demo.script.tsx', '../../components/component-docs/canvas.script.tsx', '../../components/component-docs/controls.script.tsx', ], }, }; # Calendar

Calendars let users pick one or more dates from a month grid. They support single, multiple, and range selection without leaving the surrounding form.

## Try it ## Usage Bind `value` as an ISO date string. Use `selectionMode` to switch between a single day, multiple days, or a start/end range. ```tsx import { RuiCalendar } from '@ecopages/radiant-ui/calendar'; ``` ## Derived tree `` is a **Derived Tree** host: it `render()`s the month grid from `value`, `min`, `max`, and `selection-mode`. You do not stamp `data-calendar-*` targets yourself. Parent hosts (`rui-date-field`, `rui-date-range-picker`) embed `` and query `[data-calendar-day]` inside it when opening a popup. ## Selection modes

`single` selects one date. `multiple` accepts comma-separated values. `range` expects `start/end` in `value`.

## Constrain selectable dates

Set `min` and `max` to block out-of-range bookings or past dates. Pair with `visibleMonths` for multi-month range pickers.

## Theming Calendar surfaces map to **semantic surface and selection roles**, never Tailwind palette steps: | Part | CSS roles | | --- | --- | | Month surface (`rui-calendar`) | `border`, `background`, `radius-container`, `space-inset`, `shadow-sm` | | Month title / weekday labels | `on-background`, `on-surface` | | Nav + today buttons | `rounded-control`, `surface` hover, `on-surface`, `primary` text | | Selected day | `primary`, `on-primary` (hover: `primary-container`, `on-primary-container`) | | Range preview / middle days | `primary` at reduced opacity | | Outside-month / disabled days | `on-surface` + `opacity-muted` | | Focus ring | `focus-ring` | Geometry shares control tokens (`--size-control-*`, `--radius-control`, `--space-control-*`) with `RuiInput` and `RuiButton` so adjacent form rows align. Override at the theme layer (`tokens/presets/colors/*.css`, `tokens/radius/*.css`), not in component CSS. ## API `RuiCalendar` is a custom element (``) that renders its own light-DOM month grid(s). ### Attributes | Attribute | Type | Default | Description | | --- | --- | --- | --- | | `value` | `string` | `''` | Selected date(s) in the mode-specific ISO format. | | `min` | `string` | `''` | Earliest selectable ISO date. | | `max` | `string` | `''` | Latest selectable ISO date. | | `disabled` | `boolean` | `false` | Disable all date selection. | | `locale` | `string` | `''` | BCP 47 locale tag, or comma-separated fallback list. | | `selection-mode` | `single` · `multiple` · `range` | `single` | Single, multiple, or range selection. | | `visible-months` | `number` | `1` | Month grids shown side by side. | | `page-behavior` | `visible` · `single` | `visible` | Whether paging moves by all visible months or one month. | ### Internal targets (generated) | Target | Host writes | Notes | | --- | --- | --- | | `[data-calendar-prev-month]` / `[data-calendar-next-month]` | `disabled` | paging controls | | `[data-calendar-grid]` | `role="grid"` | month grid | | `[data-calendar-day]` | `data-iso`, `tabIndex`, `aria-selected`, `disabled` | day buttons | | `[data-calendar-today]` | `disabled` | jump to today | Parent hosts query `[data-calendar-day][data-iso="…"]` for popup focus. ### Events | Event | Detail | Description | | --- | --- | --- | | `rui-change` | `{ value, start?, end? }` | Fired when the selection changes; `start` / `end` are set for `range` mode. | ### CSS classes Public BEM classes on the composed light-DOM surface (documented via `@cssclass` on `RuiCalendar`): | Class | Description | | --- | --- | | `.rui-calendar` | Root surface (`role="application"`). | | `.rui-calendar--multi` | Multi-month root modifier. | | `.rui-calendar__months` | Row of month panels. | | `.rui-calendar__month-panel` | Single month grid column. | | `.rui-calendar__header` | Month title row with paging controls. | | `.rui-calendar__nav` | Prev / next month button. | | `.rui-calendar__nav-spacer` | Placeholder where a nav button is absent. | | `.rui-calendar__month` | Month title. | | `.rui-calendar__weekdays` | Weekday header row. | | `.rui-calendar__weekday` | Weekday label. | | `.rui-calendar__grid` | Month grid (`role="grid"`). | | `.rui-calendar__week` | Grid row. | | `.rui-calendar__day` | Day cell button (`role="gridcell"`). | | `.rui-calendar__footer` | Footer row with the today button. | | `.rui-calendar__today` | "Today" jump button. | ### Theme roles | Part | CSS variables consumed | | --- | --- | | Month surface | `--border`, `--background`, `--radius-container`, `--space-inset`, `--shadow-sm` | | Month title / weekday labels | `--on-background`, `--on-surface` | | Nav + today buttons | `--radius-control`, `--surface`, `--on-surface`, `--primary` | | Selected day | `--primary`, `--on-primary`, `--primary-container`, `--on-primary-container` | | Range preview / middle days | `--primary` | | Outside / disabled | `--on-surface`, `--opacity-muted` | | Focus ring | `--focus-ring` | ## Accessibility - Day cells expose grid semantics with arrow-key navigation between dates. - Selected and today states are communicated with `aria-selected` and visible styling. - Provide a visible label or `aria-label` when the calendar is not described by surrounding text.