# Build a Calendar Component: Date Picker, Range, and Recurrence

Source: https://www.techinterview.org/post/3233475063/build-calendar-component-date-picker-frontend/
Updated: 2026-06-30 · techinterview.org

"Build a date picker" or "build a calendar component" is a deceptively hard frontend interview question. It exposes whether you understand time zones, locale-aware date handling, the accessibility patterns of grid widgets, and the small UX details that make a date picker feel professional.

## Functional requirements

- Display a month view with selectable days

- Navigate months (back/forward)

- Single date selection (or range, or multi-select — pick scope)

- Disable past dates or specific dates

- Highlight today

- Keyboard navigation

- Screen-reader friendly

- Locale-aware (week starts Sunday vs Monday, month/day names)

## Architecture

Three core pieces:

- State: current month, selected date(s)

- Layout: 7-column grid with 5–6 rows

- Day cell: button with date, selected state, disabled state

## Date library

Native Date is treacherous (time zone bugs, immutability, no formatting). Use:

- **date-fns:** tree-shakeable, functional, modern

- **Day.js:** tiny (2KB), drop-in for moment users

- **Luxon:** if you need time zone math

Avoid moment.js — deprecated and large.

## Generating the month grid

Algorithm:

- Find first day of the month

- Find first day of the week containing it (offset back to Sunday or Monday)

- Generate 6 × 7 = 42 days from that starting point

- Mark days outside the current month as muted

Always render 6 rows — different month lengths fit cleanly.

## Time zones

The most-broken aspect of date pickers. Recommended:

- Calendar shows dates in the user's local time zone

- Internal state is a date string ("2026-05-15") not a Date object

- When sending to API, convert to ISO string with explicit time zone

- For events with time, store a separate time field plus the date

## Range selection

Two clicks: first sets start, second sets end. Hover preview shows what the range would be if the user clicks now.

Edge cases:

- User clicks end before start → swap

- Same-day selection → range of one day

- Range spans months → both months visible (typically two-pane layout)

## Keyboard navigation

The ARIA Authoring Practices recommend:

- Tab moves into and out of the calendar grid

- Arrow keys move between days

- Page Up/Down moves between months

- Shift + Page Up/Down moves between years

- Home/End jumps to first/last day of week

- Enter or Space selects

## ARIA

The grid is role="grid" or role="application". Each day is a button with aria-label="May 15, 2026, Monday." Today has aria-current="date." Selected days have aria-pressed="true."

The grid_cell ARIA pattern is detailed but worth implementing for production date pickers.

## Locale

Use Intl.DateTimeFormat for month and day names. The user's locale should drive:

- First day of week (Sunday in US, Monday in EU)

- Month names ("May" vs "mai" vs "5月")

- Day-of-week abbreviations

- Date format on display

## Common mistakes

- Storing dates as JavaScript Date objects (timezone issues)

- Forgetting accessibility (no keyboard nav, no aria)

- Hardcoding "Sunday" as the first day of week

- Re-rendering the entire calendar on every keystroke (performance)

- Not handling DST transitions in time-aware date pickers

## Should I use a library?

For production: yes. React Aria Calendar, react-day-picker, or @internationalized/date are battle-tested and accessible. Hand-rolling is for interview answers and learning.

## Frequently Asked Questions

### How do I handle disabling dates based on availability?

Take a function prop: isDateDisabled(date) → boolean. Each day cell calls it. Memoize for performance with large calendars.

### What if the user wants to pick a time as well?

Add a separate time picker below the date grid. Combine into a Date or ISO string when storing.

### How do I handle a range that spans years?

Two-pane layout works visually. Internally just two dates. Performance: don't render years of days; render one or two months at a time.
