Skip to Content
ComponentsCalendar

Calendar

The Calendar component provides a flexible date picker with support for single date selection, date ranges, and multiple date selection. Built with accessibility in mind and full keyboard navigation support.

Live Examples

Live Example
☀️
Copy
View Code
import { Calendar } from '@jarllyng/nostromo' export default function CalendarExample() { return ( <Calendar mode="single" label="Select Date" placeholder="Choose a date" /> )...
Live Example
☀️
Copy
View Code
import { Calendar } from '@jarllyng/nostromo' export default function CalendarRangeExample() { return ( <Calendar mode="range" label="Select Date Range" placeholder="Choose start and end date" /> )...
Live Example
☀️
Copy
View Code
import { Calendar } from '@jarllyng/nostromo' export default function CalendarMultipleExample() { return ( <Calendar mode="multiple" label="Select Multiple Dates" placeholder="Choose multiple dates" /> )...

Import

import { Calendar } from "@jarllyng/nostromo"; // or import { Calendar } from "@jarllyng/nostromo/calendar";

Usage

import { Calendar } from "@jarllyng/nostromo"; export default function Example() { return ( <Calendar mode="single" label="Select Date" placeholder="Choose a date" /> ); }

Features

Selection Modes

  • Single - Select one date
  • Range - Select a date range (from/to)
  • Multiple - Select multiple individual dates

Date Constraints

  • Min/Max dates - Restrict selectable date range
  • Disabled dates - Disable specific dates
  • Disabled days - Disable specific weekdays (e.g., weekends)

Customization

  • Locale support - Customize date formatting
  • First day of week - Sunday or Monday
  • Show outside days - Display days from previous/next month
  • Multiple months - Display multiple months side-by-side

Robust Date Handling

  • date-fns integration - Uses date-fns library for reliable date manipulation
  • Leap year support - Correctly handles leap years and edge cases
  • Timezone aware - Proper timezone handling for date operations
  • Localization ready - Built-in support for different locales

Props

PropTypeDefaultRequiredDescription
mode"single" | "range" | "multiple""single"NoSelection mode
valueDate | Date[] | { from?: Date; to?: Date }undefinedNoControlled value
defaultValueDate | Date[] | { from?: Date; to?: Date }undefinedNoUncontrolled default value
onChange(value: Date | Date[] | { from?: Date; to?: Date } | undefined) => voidundefinedNoCallback when value changes
minDateDateundefinedNoMinimum selectable date
maxDateDateundefinedNoMaximum selectable date
disabledDatesDate[]undefinedNoArray of disabled dates
disabledDaysnumber[]undefinedNoArray of disabled weekdays (0=Sun, 1=Mon, etc.)
localestring"en-US"NoLocale for date formatting
placeholderstring"Select date..."NoInput placeholder text
labelstringundefinedNoInput label
errorbooleanfalseNoError state
helperTextstringundefinedNoHelper text below input
classNamestringundefinedNoAdditional CSS classes
inputClassNamestringundefinedNoAdditional CSS classes for input
calendarClassNamestringundefinedNoAdditional CSS classes for calendar popover
variant"default" | "outline" | "ghost""default"NoCalendar visual variant
size"sm" | "md" | "lg""md"NoCalendar size
showOutsideDaysbooleantrueNoShow days from previous/next month
numberOfMonthsnumber1NoNumber of months to display
firstDayOfWeek0 | 11NoFirst day of week (0=Sunday, 1=Monday)
labelsCalendarLabelsEnglish defaultsNoThe words the calendar says itself

Examples

Single Date Selection

<Calendar mode="single" label="Select Date" placeholder="Choose a date" onChange={(date) => console.log("Selected:", date)} />

Date Range Selection

<Calendar mode="range" label="Select Date Range" placeholder="Choose start and end date" onChange={(range) => console.log("Range:", range)} />

Multiple Date Selection

<Calendar mode="multiple" label="Select Multiple Dates" placeholder="Choose multiple dates" onChange={(dates) => console.log("Selected dates:", dates)} />

With Date Constraints

<Calendar mode="single" label="Select Future Date" minDate={new Date()} maxDate={new Date(2025, 11, 31)} disabledDays={[0, 6]} // Disable weekends />

With Disabled Dates

const holidays = [ new Date(2025, 0, 1), // New Year's Day new Date(2025, 6, 4), // Independence Day new Date(2025, 11, 25) // Christmas ] <Calendar mode="single" label="Select Date" disabledDates={holidays} />

Controlled Component

const [selectedDate, setSelectedDate] = useState<Date | undefined>(); <Calendar mode="single" value={selectedDate} onChange={setSelectedDate} label="Controlled Calendar" />;

Accessibility

  • ✅ WCAG 2.1 AA compliant
  • ✅ Keyboard navigation - Full keyboard support with arrow keys
  • ✅ ARIA labels - Proper labels for all interactive elements
  • ✅ Screen reader support - Announcements for date selection with full date format
  • ✅ Focus management - Proper focus handling in popover with visual focus indicators
  • ✅ Date announcements - Clear date format announcements (weekday, year, month, day)
  • ✅ Role attributes - Proper ARIA roles (application, button, selected states)
  • ✅ Disabled state - Proper aria-disabled attributes for disabled dates

Keyboard Navigation

  • Arrow Right/Left - Navigate to next/previous day
  • Arrow Up/Down - Navigate to same day next/previous week
  • Enter/Space - Select focused date
  • Escape - Close calendar popover
  • Tab - Navigate between controls
  • Home - Navigate to first day of month
  • End - Navigate to last day of month
  • Page Up - Navigate to previous month (same day)
  • Page Down - Navigate to next month (same day)

Do’s and Don’ts

✅ Do

  • Use appropriate mode for your use case
  • Provide clear labels and helper text
  • Set min/max dates when applicable
  • Use disabledDays for business rules (e.g., weekends)
  • Provide error feedback when validation fails

❌ Don’t

  • Use range mode for single date selection
  • Forget to handle empty states
  • Use without labels for accessibility
  • Disable too many dates (hurts UX)
  • Use multiple mode for date ranges

Localisation

locale moves every date: the value on the trigger, the weekday headings, the month heading and the day labels a screen reader reads. It is passed to Intl, so any locale tag the runtime knows works and nothing has to be imported.

<Calendar locale="da-DK" value={new Date(2026, 8, 14)} /> // 14. sep. 2026, over søn. man. tirs. ons. tors. fre. lør.

A locale cannot translate “Today”. Those words are labels:

<Calendar locale="da-DK" labels={{ openCalendar: "Åbn kalender", placeholder: "Vælg dato...", previousMonth: "Forrige måned", nextMonth: "Næste måned", today: "I dag", selectEndDate: "Vælg slutdato", multipleSelected: (count) => `${count} datoer valgt`, }} />

Anything you leave out keeps its English default, and the placeholder prop still wins over labels.placeholder.

Hiding the outside days keeps the columns

showOutsideDays={false} hides the dates from the neighbouring months, and the cells they occupied stay behind as empty spacers. They are what puts the 1st of the month under the right weekday.

The spacers are aria-hidden and are not controls, so a screen reader and the Tab key both skip them.

Keyboard

Arrow keys move a day or a week, Home and End go to the ends of the month, Page Up and Page Down move a month, Enter and Space select, Escape closes.

Every one of them lands on a date that can actually be focused. A movement that would land on a disabled date keeps going in the same direction until it finds one that is not, and if nothing in that direction is enabled, focus stays where it is rather than moving to a control that cannot hold it.

A month step keeps the day number where the target month has one: Page Down from 31 March is 30 April, not 1 May.

Opening the calendar puts focus on the selected date, or on today, or on the first enabled day of the month shown, in that order. The month shown is the month of the selected date.

  • Input - Used for the date input field
  • Popover - Used for calendar popover (Radix UI)
  • Button - Used for navigation buttons
Last updated on