Skip to content

Calendar

A month, inline, with the days it is holding lit up. The same grid the four pickers open, without a popup around it: for a page where the dates are always visible.

tsx
import { Calendar } from 'neba';

<Calendar value={day} onValueChange={setDay} />;

Props

PropTypeDefaultDescription
sizeshared'xs' | 'sm' | 'md' | 'lg' | 'xl''md'The height of a cell and the type scale: 32px at md, the same ladder as a Button
colorshared'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'info''primary'Semantic colour role. Arbitrary colour values are not accepted
elevationshared0 | 1 | 2 | 30Drop shadow depth. 0 by default: a calendar sitting in a page is not floating
mode'single' | 'multiple' | 'range''single'Decides what the value is: one day, an array of them, or a { start, end } span
valueDate | null | Date[] | CalendarRangeThe value, whose type follows mode. Use with onValueChange for a controlled calendar
defaultValueDate | null | Date[] | CalendarRangeThe initial value, for an uncontrolled calendar
onValueChange(value) => voidCalled when the selection changes
monthDateThe month on screen. Use with onMonthChange to control it
defaultMonthDateWhich month it opens on. Defaults to the month of the value, or this one
onMonthChange(month: Date) => voidCalled when the month on screen changes
granularity'day' | 'month' | 'year''day'Which unit a click chooses: a day, a whole month, a whole year. The same as on DatePicker
renderDay(date: Date) => ReactNodeWhat a day cell draws under its number: a dot, a count, a bar. Not room for a day of entries
borderedbooleantrueDraws the sheet the picker's popup draws. Off for the bare grid
minDateDate | nullThe earliest date that may be chosen, compared at granularity
maxDateDate | nullThe other end of the same span
shouldDisableDate(date: Date) => booleanBlocks individual cells inside the range: weekends, holidays, a room already booked. The cell stays in the grid, unavailable, and the callback is handed the value that cell would produce
weekStartsOnshared0 | 1 | 2 | 3 | 4 | 5 | 6Which day the week starts on, Sunday being 0. Defaults to whatever the locale says
localestringBCP 47 tag deciding the month and weekday names and the header's order
showOutsideDaysbooleantrueDraws the leading and trailing days of the neighbouring months
labelsPartial<PickerLabels>The strings a screen reader hears. Every one defaults to the locale’s wording

Native <div> attributes pass through to the root. The shared axes are described in prop conventions.

What it is not

A scheduler. The cells are the control ladder's heights (32px at md), so renderDay is room for a dot, a count or a bar under the number, and not for a day's worth of entries. A component that drew those would need a different grid, and calling this one that would be a promise the sizes cannot keep.

Use it for choosing, filtering and marking. Reach for DatePicker when the date should be behind a field instead.

Examples

mode

mode decides what the value is.

modevalue
single (default)Date | null
multipleDate[]
range{ start: Date | null, end: Date | null }

In multiple, clicking a day that is already held takes it back out: the only way a pointer can undo one.

In range, the first click sets the start and the second sets the end. A click below the start begins a new span rather than inverting the old one, because inverting is the behaviour that makes a reader believe they mis-clicked. Once a span is finished, the next click starts another.

renderDay

Whatever it returns is drawn inside the day cell, under the number. The cell is position: relative, so an absolutely positioned mark lands where you put it.

A hook rather than an events prop: the caller is the only one who knows what a day has on it, and taking a data shape here would mean having an opinion about one.

granularity

The same three units DatePicker offers. At month or year the grid opens on that view and a click there is the answer, and the value is the first day of what was chosen.

minDate · maxDate · shouldDisableDate

Read at granularity, exactly as on DatePicker. A blocked cell keeps its place in the grid and is marked with aria-disabled rather than the disabled attribute, so it stays on the arrow-key path.

bordered and elevation

bordered draws the sheet the picker's popup draws. Turn it off for a bare grid to put inside a Card that already has an edge. elevation is 0 by default: a calendar sitting in a page is not floating.

Keyboard

KeyWhat it does
Moves by a day or a week, stepping the month at the edges
Home / EndTo the start or the end of the week
PageUp / PageDownBy a month: with Shift, by a year

The grid has a single tab stop, so Tab leaves it rather than walking forty-two cells.

Accessibility

  • The grid is a role="grid" of role="gridcell" buttons, each named with the full date rather than the bare number.
  • A held day carries aria-selected; today carries aria-current="date" and a dot under the number.
  • Anything renderDay draws is inside the cell's accessible name unless you mark it aria-hidden. A dot that repeats what a label already says should be hidden; a count that adds something should not.

Released under the MIT License