“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.