Skip to content

Documentation

neelam-ui

Search documentation

Getting Started
Forms
Overlays
Navigation
Data Display
Layout
AI & Chat
Blocks
GitHub repository

Calendar

A month grid for picking a date — the WAI-ARIA date-picker grid on its own, rather than pre-wired to a popover and an input.

September 2026
SunMonTueWedThuFriSat

Thu Sep 03 2026

Usage#

import { Calendar } from "neelam-ui";
 
const [selected, setSelected] = useState<Date | undefined>(new Date());
 
<Calendar selected={selected} onSelect={setSelected} />

Composing a date picker#

Calendar deliberately owns no opinion about how it is triggered. Put it in a Popover behind a button showing the formatted date and you have the usual date-picker widget:

<Popover>
  <PopoverTrigger>{format(selected)}</PopoverTrigger>
  <PopoverContent>
    <Calendar selected={selected} onSelect={setSelected} />
  </PopoverContent>
</Popover>

That is the same reasoning DateRangePicker follows — it is built from Popover, two Calendars, and a RadioGroup rather than reimplementing any of them.

Disabling dates#

disabled is a predicate, so any rule works:

<Calendar disabled={(date) => date < new Date()} />

Keyboard navigation skips disabled dates rather than landing on them, searching in the direction you were already moving. The search is bounded to a year, so a caller who disables every date cannot hang the component in an infinite loop.

Week start#

weekStartsOn takes 0 (Sunday, the default) through 6. It is not derived from the locale — that, along with range selection and year-jump shortcuts, is deliberately out of scope here.

Keyboard#

The grid is fully operable, which is the whole point of the component:

Keyboard shortcuts
KeyBehaviour
TabEnters the grid at the focusable day — one at a time, roving. Tab again leaves the grid entirely.
Moves one day back or forward.
Moves one week back or forward.
HomeEndMoves to the first or last day of the current week.
PageUpPageDownMoves to the same day in the previous or next month.
EnterSpaceSelects the focused day.

Accessibility#

  • A native <table role="grid">: rows are weeks, cells are days — a genuinely clean fit for a structure that already is a grid.
  • Each day is a real <button> recategorized as role="gridcell", so activation stays native. Selection is aria-selected, and today carries aria-current="date".
  • Roving tabIndex means the whole grid is one tab stop, and real DOM focus is moved by the arrow keys — not aria-activedescendant, matching Select and ContextMenu.
  • The month heading is aria-live="polite", so changing month announces the new one — otherwise paging through months is silent.
  • The previous/next buttons are icon-only with explicit aria-labels, and their chevrons are aria-hidden.

Offer a text input too

A grid is excellent for browsing and poor for entering a date you already know. Pairing the calendar with a typed date field is faster for many users, and essential for anyone for whom arrowing across weeks is laborious.

API reference#

Props for Calendar
PropTypeDefault
defaultMonth

Initial displayed month when uncontrolled. Defaults to `selected`'s month, or today's.

Date
defaultSelected

Initial selected date when uncontrolled.

Date
disabled

Marks specific dates unselectable (e.g. `(date) => date < new Date()` to block the past).

((date: Date) => boolean)
month

Which month is shown (any `Date` within it).

Date
onMonthChange((month: Date) => void)
onSelect

Called when a day is activated (clicked, or Enter/Space on the focused day).

((date: Date) => void)
selected

The selected date.

Date
weekStartsOn

Which weekday starts each row, `0` (Sunday) through `6` (Saturday). Defaults to `0`.

enum0

Also accepts every native <div> attribute except onSelect, which is taken over above.