Hey folks, Rahul here ๐
Calendars look simple. A grid of numbers, right? Then you hit timezones, locale formatting, range selections, disabled dates, and the absolutely cursed Date API โ and suddenly you're questioning your career choices.
I've built calendar components at scale, and the gap between "renders a month grid" and "production-ready date picker" is enormous. Let's bridge it.
R โ Requirements
Functional Requirements
- Single date selection with month/year navigation
- Date range selection (start โ end) with visual feedback
- Min/max date boundaries and arbitrary disabled dates
- Locale-aware formatting (week starts on Monday vs Sunday)
- Keyboard navigation (arrow keys, Enter, Escape)
- Time picker integration (optional datetime mode)
- Preset ranges: "Last 7 days", "This month", "Custom"
Non-Functional Requirements
- Performance: Month transitions must be <16ms (single frame)
- Accessibility: Full ARIA grid pattern with live region announcements
- i18n: Support RTL layouts, non-Gregorian calendars conceptually
- Size: <5KB gzipped for the core (no moment.js!)
A โ Architecture
Why Date Math Is Cursed
Before we architect, let's acknowledge the enemy:
Rule #1: Never store or compare dates as Date objects internally. Use date strings (YYYY-MM-DD) for calendar logic โ they're timezone-free and comparable.
Component Architecture
State Machine Approach
Range selection is a two-step state machine, and modeling it explicitly prevents the #1 range picker bug (inconsistent start/end states):
D โ Data Model
Calendar Grid Generation
Why 42 Cells Always?
Some months need 4 weeks, some need 6. If you dynamically size the grid, the calendar jumps in height when navigating months. Always rendering 6 rows (42 cells) with leading/trailing days from adjacent months keeps the layout rock-solid.
I โ Interface Definition
Component API
ARIA Grid Pattern
O โ Optimizations
1. Roving TabIndex for Keyboard Nav
2. Memoized Grid Computation
3. Transition Animation
4. Lightweight Date Utils (No Libraries)
Production Gotchas Rahul Has Debugged ๐ฅ
- Timezone Traps:
new Date("2024-03-15")is parsed as UTC, which in negative-offset timezones becomes March 14 local. Always appendT00:00:00or use thenew Date(year, month, day)constructor. - Month Overflow:
new Date(2024, 0, 32)silently becomes Feb 1. When adding months, always clamp to the last day of the target month. - Focus Management: When the popover opens, focus should move to the currently selected date (or today). When it closes, focus must return to the trigger button โ or forms break for keyboard users.
- Range Hover Preview: The visual range highlight on hover should swap start/end if the hovered date is before the start date โ users expect this, and it prevents confusion.
- Locale Week Start: The US starts weeks on Sunday, most of Europe on Monday, and some Middle Eastern countries on Saturday.
Intl.Localecan detect this.
Next up: #8: Design a Nested Comments System โ recursive data structures, optimistic threading, collapse/expand state, and real-time comment streaming. ๐งต