Calendar
A standalone date picker grid built without an external primitive. Supports controlled and uncontrolled selection, min/max bounds, and custom disabled-date predicates.
Import
import { Calendar } from "chunks-ui";Basic Usage
<Calendar />| Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday |
|---|---|---|---|---|---|---|
Controlled
function ControlledCalendar() {
const [date, setDate] = useState<Date | null>(null);
return (
<Calendar value={date} onValueChange={setDate} />
);
}| Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday |
|---|---|---|---|---|---|---|
No date selected
When value changes to another day, the calendar shows that day’s month. A re-render with the same day, such as an inline value={new Date()}, keeps the month the user moved to. The same rule means that setting a new Date for the day that is already selected does not move the view back, so a “Today” button does nothing visible while today is selected and the user is on another month. To show the month again, control the month: see Shown Month.
Shown Month
The calendar opens on the month of the selected day, else today’s, and the arrows and keys move it. To control the shown month, pass month, any date in that month, and onMonthChange. onMonthChange gets the first day of the month the user moves to, at local midnight: by the arrows, by a key that moves focus out of the shown month, or by a click on a day from a neighbouring month. The view then waits for a new month. If you keep the month, a key that would leave it leaves focus where it is. When the new month comes later, for example from a transition, focus moves with the view to the day the key moved to. If you set another month instead, for example to keep the view inside a range, focus goes to that month’s Tab stop.
function MonthCalendar() {
const [date, setDate] = useState<Date | null>(null);
const [month, setMonth] = useState(() => new Date());
return (
<>
<Button
onClick={() => {
const today = new Date();
setDate(today);
setMonth(today);
}}
>
Today
</Button>
<Calendar value={date} onValueChange={setDate} month={month} onMonthChange={setMonth} />
</>
);
}| Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday |
|---|---|---|---|---|---|---|
With month, a new value does not move the view: set month too, as the Today button does. An Invalid Date, or a date before year 1, as month shows the selected day’s month, else today’s.
Without month, onMonthChange still tells you when the user moves to another month, for example to load that month’s data. A new value that moves the view does not call it.
function MonthChangeCalendar() {
const [moved, setMoved] = useState<Date | null>(null);
return (
<>
<Calendar onMonthChange={setMoved} />
<p>
{moved
? `Moved to ${moved.toLocaleDateString("en-US", { month: "long", year: "numeric" })}`
: "No month change yet"}
</p>
</>
);
}| Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday |
|---|---|---|---|---|---|---|
No month change yet
Disabled Dates
Use min/max to set a date range, or isDateDisabled to disable individual dates with a predicate:
const today = new Date();
const isWeekend = (date: Date) => date.getDay() === 0 || date.getDay() === 6;
<Calendar
min={today}
isDateDisabled={isWeekend}
/>A disabled day is dimmed and shows the not-allowed cursor.
| Sunday | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday |
|---|---|---|---|---|---|---|
Week Start and Outside Days
weekStartsOn sets the first column of the week: 0 is Sunday (the default), 1 is Monday. showOutsideDays fills the empty cells before the 1st and after the last day of the month with dimmed days from the neighbouring months. Clicking one of them selects it and shows its month. They are never Tab stops. In forced-colors mode, such as a Windows contrast theme, they take the system colour for dimmed text (GrayText); there, the selected day takes the system highlight (Highlight), today is underlined in place of its ring, and a disabled day is struck through in GrayText at full opacity, so it differs from an outside day.
<Calendar weekStartsOn={1} showOutsideDays />| Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday |
|---|---|---|---|---|---|---|
Keyboard
The day grid follows the keyboard pattern of the WAI-ARIA date picker example . It has one Tab stop: the selected day if it is in the shown month, else today, else the 1st of the month. After you move with the keys, the day you moved to becomes the Tab stop. Days from neighbouring months (showOutsideDays) are never Tab stops.
| Key | Moves focus to |
|---|---|
| ← / → | The previous / next day |
| ↑ / ↓ | The same weekday in the previous / next week |
| Home / End | The first / last day of the week, following weekStartsOn |
| Page Up / Page Down | The same day in the previous / next month |
| Shift + Page Up / Page Down | The same day in the previous / next year |
Enter or Space selects the focused day. When focus moves past the shown month, the calendar shows the new month. If the target month is shorter, focus goes to its last day: Page Up from March 31 goes to February 28.
Disabled days (min, max, isDateDisabled) can take focus, and screen readers announce them as unavailable. Arrow keys do not skip them, but Enter, Space and clicks do not select them.
The left and right arrows are not mirrored in right-to-left layouts.
Calendar Props
Labels
labels replaces the built-in English text, for example to translate it. Keys you leave out keep their default from DEFAULT_CALENDAR_LABELS. The keys that depend on a date are functions of that date, so you can pass Intl formatters. They get the date at local midnight: format it in the local time zone, not with timeZone: "UTC". A server component can’t pass functions, so set them in a client component. See Labels.
Each column header shows weekday and screen readers read weekdayLong; set both when you translate them (see Labels).