Zetta v1.6

Components · Data display & feedback

Date picker

A month grid for picking a date — 32px day nodes on the popover surface. The selected day takes a primary fill, today wears a hairline-strong ring, and days outside the current month dim back. Pair it with an input to form the full date-picker field.

Preview

Build with the skill

No package to install — hand this to your AI to generate or migrate the component in your stack.

Prompt for your AI
Apply the Zetta "Date picker" component to this project using the zetta-design-md skill.

WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `date-picker` spec (variants: day, day-selected, day-today, day-disabled, day-outside) and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `date-picker` spec in this project's stack.

AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.

ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility.

Anatomy & rules

[ 2024-01-15  📅 ]   ← trigger input (standard input token)
┌───────────────────┐
│  < January 2024 > │  ← calendar-header
│ Mo Tu We Th Fr Sa │  ← calendar-weekday-label
│  1  2  3  4  5  6 │
│  7  8  9 10 11 12 │  ← calendar-day (selected: primary fill)
│ ...               │
└───────────────────┘

Parts:

  • Trigger input — standard input token; trailing calendar_today icon (16px, {colors.muted})
  • Popovercalendar-container: {colors.popover} surface, 24px padding, {elevation.shadow.overlay}
  • Header — prev/next arrows (button-ghost), month+year label (clickable → drill-down)
  • Weekday labels — abbreviated (Mon–Sun), 12px / 500 / {colors.muted}
  • Day nodes — 32×32px; hover, selected, today, disabled, outside-month states
State Input border Fill Notes
Default (empty) {colors.border-strong} {colors.surface-card} Placeholder visible
Default (has value) {colors.border-strong} {colors.surface-card} Date value shown
Hover {colors.muted} {colors.surface-card}
Focus {colors.primary} + glow {colors.surface-card} Calendar opens
Open {colors.primary} + glow {colors.surface-card} Calendar panel visible
Selected date {colors.primary} on calendar day Day fills with primary
Today (unselected) {colors.primary-bg} on calendar day Subtle today indicator
Disabled {colors.disabled-border} {colors.disabled-bg} No calendar opens
Error {colors.danger} + glow {colors.surface-card} Validation message shown
Read-only {colors.hairline} {colors.surface-muted} No calendar opens

When to use

  • Any form field that requires a single date input.

When not to use

Do

  • Always pair with a text field so users can type dates directly as an alternative to the calendar.
  • Disable past dates when a future date is required (or vice versa) using min/max.

Don't

  • Don't embed the calendar grid inline in a form — use the popover trigger pattern.
  • Placeholder format matches the expected input format: "DD/MM/YYYY" or "MM/DD/YYYY".
  • Error messages: be specific. "Date must be after today." not "Invalid date."
  • Label: always visible — never use placeholder as label.
  • Date range context: if part of a range, label as "Start date" / "End date".

Keyboard

Key Behavior
Tab Move focus to the date input
Enter / Space Open / close the calendar panel
Arrow keys Navigate days in the calendar grid
Enter Select the focused date, closing the calendar
Page Up / Page Down Navigate months
Home / End First / last day of the current week
Escape Close the calendar without selecting

Accessibility

  • ARIA: trigger uses aria-haspopup="dialog" and aria-expanded. Calendar uses role="dialog" with aria-label="Choose date".
  • Day nodes: role="gridcell", aria-selected for selected date, aria-disabled for disabled dates.
  • Accessibility theme: day nodes require 44×44px override — pending per zetta-accessibility-v1_6.md.

Spec

Values and token references resolved straight from the Zetta spec.

calendar-container
backgroundColor
{colors.popover}
textColor
{colors.popover-foreground}
borderColor
{colors.hairline}
borderWidth
1px
borderRadius
{rounded.base}
shadow
{elevation.shadow.overlay}
padding
{spacing.page}
display
grid
gap
{spacing.sm}
headerHeight
32px
dayNodeSize
32px
dayNodeRadius
{rounded.base}
dayNodeFontFamily
Geist
dayNodeFontSize
14px
calendar-header
height
32px
display
flex
alignItems
center
justifyContent
space-between
calendar-day
textColor
{colors.ink}
fontWeight
400
display
flex
alignItems
center
justifyContent
center
cursor
pointer
calendar-day-hover
backgroundColor
{colors.primary-bg}
textColor
{colors.primary}
borderRadius
{rounded.base}
calendar-day-focus
outline
2px solid {colors.border-focus}
outlineOffset
2px
borderRadius
{rounded.base}
calendar-day-selected
backgroundColor
{colors.primary}
textColor
{colors.primary-text}
fontWeight
600
borderRadius
{rounded.base}
calendar-day-today
borderColor
{colors.border-strong}
borderWidth
1px
borderRadius
{rounded.base}
calendar-day-disabled
textColor
{colors.disabled-text}
cursor
not-allowed
calendar-day-outside
textColor
{colors.disabled-text}
opacity
0.4
cursor
default
calendar-weekday-label
textColor
{colors.muted}
fontFamily
Geist
fontSize
12px
fontWeight
500
textAlign
center
calendar-month-grid
display
grid
gridTemplateColumns
repeat(3, 1fr)
gap
{spacing.sm}
padding
{spacing.md}
calendar-month-cell
height
40px
borderRadius
{rounded.base}
fontFamily
Geist
fontSize
14px
fontWeight
400
textColor
{colors.ink}
display
flex
alignItems
center
justifyContent
center
cursor
pointer
calendar-month-cell-hover
backgroundColor
{colors.primary-bg}
textColor
{colors.primary}
calendar-month-cell-selected
backgroundColor
{colors.primary}
textColor
{colors.primary-text}
fontWeight
600
calendar-month-cell-current
borderColor
{colors.border-strong}
borderWidth
1px
calendar-month-cell-focus
outline
2px solid {colors.border-focus}
outlineOffset
2px
calendar-year-grid
display
grid
gridTemplateColumns
repeat(3, 1fr)
gap
{spacing.sm}
padding
{spacing.md}
maxHeight
200px
overflowY
auto
calendar-year-cell
height
40px
borderRadius
{rounded.base}
fontFamily
Geist
fontSize
14px
fontWeight
400
textColor
{colors.ink}
display
flex
alignItems
center
justifyContent
center
cursor
pointer
calendar-year-cell-hover
backgroundColor
{colors.primary-bg}
textColor
{colors.primary}
calendar-year-cell-selected
backgroundColor
{colors.primary}
textColor
{colors.primary-text}
fontWeight
600
calendar-year-cell-current
borderColor
{colors.border-strong}
borderWidth
1px
calendar-year-cell-focus
outline
2px solid {colors.border-focus}
outlineOffset
2px

Known gaps

  • Calendar trigger input — uses standard input token; no separate trigger token needed.
  • Invalid range (passes through disabled day) error state — not yet specified.
  • Accessibility theme 44×44px day node override — pending.
  • Month picker drill-down animation spec — not yet specified.