Calendar · Calendar day
Calendar — the month grid a date is picked on.
Beta · Forms · packages/css/src/components/calendar.css · React on Base UI: @pepperui/calendar
.pui-calendar (__head __month __weekdays __grid __foot) · .pui-calendar-day (--today --range --outside, aria-selected, :disabled). September 2026, Monday first, six rows.
Sizes across, states down.
What it is for
Section titled “What it is for”- Month is the text property; Has footer hides Clear / Today (a range picker’s months share one foot)
- The 42 days, the two nav buttons and the two foot buttons are exposed
- Six rows always — the panel never changes height between months
- 280 wide: 7 × 32 + 6 × 4 + 2 × 16
Use when under a Date picker’s field, or two side by side for a range — never on its own in a form (the field carries the value).
Do not use when a month or a year is picked (that is the head’s own control, later); for a schedule or an agenda (that is a Table); to show dates that cannot be picked (text).
Relates to Calendar day, Date picker, Icon button - ghost, Button - ghost, Input, Popover.
Anti-patterns Sunday-first (weeks are Monday-first) · a month with five rows · a today ring and a selected fill on the same day when it is today (today’s selected day is Kind selected) · a unicode arrow in the head · a fill changed on the instance.
Keywords: calendar, month grid, date, day picker, react-day-picker, datepicker
Figma — page Date picker: Calendar (1 variants) · Calendar day (20 variants)
shadcn: calendar
The markup — copy this
Section titled “The markup — copy this”The class, the parts, and nothing that is not in the package. The same block opens calendar.css.
<div class="pui-calendar"> <div class="pui-calendar__head"><button class="pui-icon-button pui-icon-button--ghost pui-icon-button--small">…</button> <span class="pui-calendar__month">September 2026</span> …</div> <div class="pui-calendar__weekdays"><span>Mo</span>…</div> <div class="pui-calendar__grid" role="grid"><div class="pui-calendar__row" role="row"> <button class="pui-calendar-day pui-calendar-day--outside">31</button> <button class="pui-calendar-day pui-calendar-day--today">7</button> <button class="pui-calendar-day" role="gridcell" aria-selected="true">14</button> …</div> …</div>(a grid owns rows, a row owns gridcells, aria-selected is only valid on a gridcell, axe) <div class="pui-calendar__foot"><button class="pui-button pui-button--ghost pui-button--small">Clear</button>…</div></div>The modifiers
Section titled “The modifiers”| What it is | Note | |
|---|---|---|
| Day Kind | (none) default · --today · --selected / [aria-selected=“true”] · --range · --outside | — |
Rules — Monday first; six rows always; no leading zero; a unicode arrow never in the head.
Accessibility — measured, not assumed
Section titled “Accessibility — measured, not assumed”Every number here is measured on the rendered component: contrast (npm run contrast:render), the focus ring (npm run focus) and the keyboard (npm run keyboard). The floors are principle 12’s.
Contrast
Section titled “Contrast”| ink on ground | reads | floor | the matrix calls it | |
|---|---|---|---|---|
#b5b3b3 on #ffffff |
2.08 | 3 | text.disabled on surface.page (exempt) | axe |
#544d4d on #ffffff |
8.24 | 4.5 | text.muted on surface.page (promised) | ok |
#0b0202 on #f2f2f2 |
18.32 | 4.5 | text.default on surface.sunken (promised) | ok |
#0b0202 on #ffffff |
20.51 | 4.5 | text.default on surface.page (promised) | ok |
#ffffff on #0b0202 |
20.51 | 4.5 | Primary button label (promised) | ok |
5 distinct pair(s) rendered, 1 that axe calls a violation — each one carries its reason in storybook/test/axe-allowlist.json.
The focus ring
Section titled “The focus ring”| primitive | on | the indicator | over | the change | |
|---|---|---|---|---|---|
| Calendar day | surface/page | shadow #6a66e4 2 px |
#ffffff |
4.53 | ok |
| Calendar day | surface/card | shadow #6a66e4 2 px |
#f2f2f7 |
4.06 | ok |
44 of 44 focus stop(s) in this block meet WCAG 2.4.13 on screen — the same crop screenshotted focused and unfocused, and the difference measured.
Keyboard
Section titled “Keyboard”| role | tab stops | what the pattern requires | |
|---|---|---|---|
grid .pui-calendar__grid |
1 | exactly 1 | ok |
What the consumer’s script owes. ONE tab stop in the grid: the selected day, else today, else the first of the month, takes tabindex=“0” and every other cell tabindex=“-1” — a range marks two days selected and still exposes one stop. The script owes the arrow keys by day and week, Home / End, PageUp / PageDown by month, Shift+PageUp / PageDown by year, Enter / Space to select.
Tab order across the whole library is document order over 344 stops, and Shift+Tab is its exact reverse — no keyboard trap anywhere (WCAG 2.1.2).
Pepper UI v1.5.0
The Mobupps design system. 386 tokens, 88 components, 65 pages in the workshop — every page of this site generated from the repository, so nothing here is typed twice.