Calendar
Inline month grid on Ark UI's date-picker machine — single or range selection, restriction predicates, event dots, and month/year navigation.
Calendar is the inline month grid — Ark's date-picker machine rendered inline (no
trigger/popover), so it's always visible rather than living behind a field. It renders the same
grid module DatePicker will embed in its popover once that lands. The
value is a native Date | null (or { start, end } under range) — Ark's internal DateValue
representation never leaks out.
import { Calendar } from '@42/ui-react/calendar';
<Calendar value={date} onChange={setDate} />Calendar is a bare widget, like Table — there's no built-in label /
description / error. A DatePicker popover (trigger + field wrapper) is
where that belongs.
Playground
Component
Dir
Presets
Modes
Props
Code
<Component numOfMonths={1} />Range & numOfMonths
range selects a { start, end } pair instead of a single date — click once for the start,
again for the end. numOfMonths shows that many months side by side; it defaults to 1 regardless
of range — pass numOfMonths={2} to show a full range without paging.
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 1 | 2 | 3 | 4 | 5 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
30 | 31 | 1 | 2 | 3 | 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 1 | 2 | 3 |
import { useState } from 'react';
import { Calendar, type DateRangeValue } from '@42/ui-react/calendar';
const [range, setRange] = useState<DateRangeValue>({ start: null, end: null });
<Calendar range numOfMonths={2} value={range} onChange={setRange} />Restricting selection
isDateUnavailable takes a plain predicate over a native Date — greyed-out, aria-disabled
days the user can't pick. min / max bound the selectable range the same way.
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
30 | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | 1 | 2 | 3 |
<Calendar isDateUnavailable={(date) => date.getDay() === 0 || date.getDay() === 6} />A follow-up PR (DatePicker) re-exports a set of composable predicate helpers —
disablePast(), disableFuture(), disableWeekends(), beforeDate(d), afterDate(d),
disableDates([...]), and anyOf(...) / allOf(...) to combine them — from
@42/ui-react/date-picker, built on the exact same conversion boundary Calendar uses
internally.
Event dots
renderDay(date, { isUnavailable, isSelected }) decorates a specific day's cell — draw a dot,
badge, or any marker from your own event data. It receives a native Date, converted from Ark's
internal value at the same timeZone boundary the rest of the component uses.
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
30 | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | 1 | 2 | 3 |
<Calendar
renderDay={(date) =>
hasEvent(date) ? <span className="mt-0.5 block size-1 rounded-full bg-(--c-solid)" /> : null
}
/>Month/year navigation
Click the header label to cycle the view — day → month → year — then pick a year, a month, and
land back on a day grid. MonthSelect / YearSelect dropdowns in the day view offer a
quicker direct jump (shown only when a single month is visible). minView / maxView bound
the levels reachable this way, e.g. minView="month" for a picker that never drills into
individual days.
<Calendar minView="month" maxView="year" />Week numbers
| S | M | T | W | T | F | S | |
|---|---|---|---|---|---|---|---|
| 26 | 30 | 1 | 2 | 3 | 4 | 5 | 6 |
| 27 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 28 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 29 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 30 | 28 | 29 | 30 | 31 | 1 | 2 | 3 |
<Calendar withWeekNumbers />Fixed height
Months naturally span 4–6 week rows depending on how the days line up, so the grid's height can
shift while paging between months. fixedWeeks always renders 6 rows (padding with the
adjacent month's days) to keep the height constant.
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
30 | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
<Calendar fixedWeeks />Sizes
Five sizes — xs through xl — matching the rest of the family.
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 1 | 2 | 3 | 4 | 5 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 1 | 2 | 3 | 4 | 5 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 1 | 2 | 3 | 4 | 5 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 1 | 2 | 3 | 4 | 5 |
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
26 | 27 | 28 | 29 | 30 | 31 | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 1 | 2 | 3 | 4 | 5 |
Code
<Calendar size="xs" defaultValue={new Date()} /><Calendar size="sm" defaultValue={new Date()} /><Calendar size="md" defaultValue={new Date()} /><Calendar size="lg" defaultValue={new Date()} /><Calendar size="xl" defaultValue={new Date()} />Controlled
Drive the value from state. onChange hands you the next Date | null (or DateRangeValue
under range).
'use client';
import { useState } from 'react';
import { Calendar } from '@42/ui-react/calendar';
export function EvaluationDate() {
const [date, setDate] = useState<Date | null>(null);
return <Calendar value={date} onChange={setDate} />;
}Locale & time zone
locale overrides the active locale context (month/day names, first-day-of-week); timeZone
overrides the local time zone used for every Date ↔ DateValue conversion at the boundary —
default getLocalTimeZone(), not Ark's own machine default of "UTC".
<Calendar locale="fr-FR" timeZone="Europe/Paris" />Translations
locale drives month/day names and first-day-of-week automatically, but Ark's own built-in
accessible names (day cells, prev/next, the view-cycling header, week numbers) are English by
default — translations overrides them. It's partial: unset keys keep Ark's own English text.
<Calendar
locale="fr-FR"
translations={{
clearTrigger: 'Effacer la sélection',
trigger: (open) => (open ? 'Fermer le calendrier' : 'Ouvrir le calendrier'),
}}
/>API
Prop
Type
Accessibility
role="grid"with full keyboard navigation from Ark — arrows move between days,PageUp/PageDownstep a month,Home/Endjump to the start/end of the week.- Unavailable and out-of-bounds days carry
aria-disabled; the selected day (or range) carriesaria-selected; today carriesaria-current="date". - The header's view-cycling label and prev/next buttons are real
<button>s with accessible names from Ark's built-in translations — override them withtranslations(see above). - Range selection announces the in-progress start/end via Ark's live region.
TagsInput
Free-form, creatable tags on Ark UI's TagsInput machine — type to mint a tag, no fixed option list. Editable, paste-aware, with Pill-styled chips.
Time Input
HOUR/MINUTE (+ SECOND) + AM/PM time entry composed from NumberInput + SegmentGroup — Ark ships no time-picker machine, so this is the kit's own composed time field.