Date Selector
Accessible one- or two-month calendar for choosing a date range, one date or several dates.
c2-date-selector chooses a start and end date without a time-zone conversion. The first activation starts a range,
the second completes it, and the third starts over. Use min and max for availability, months="1" for compact
layouts, and locale or week-start to match the audience. Arrow keys move between days; Page Up and Page Down move
between months. The calendar keeps a usable seven-column minimum width; on mobile, a two-month selector stacks the
months vertically.
Import
import '@c2n/components/date-selector'Usage
September 2026
October 2026
October 2026
November 2026
September 2026
Listen for change to receive { from, to } in event.detail; both are YYYY-MM-DD strings. input fires at the
same time for generic form bindings. With name="trip", a complete range submits trip and trip-end; override the
second key with end-name. required treats an unfinished range as missing.
One date or several
mode="single" turns the calendar into an inline date picker: each activation replaces value, a single YYYY-MM-DD
date. mode="multiple" toggles dates in and out of value, kept in ascending order and separated by commas, and marks
the grid aria-multiselectable. Both leave from and to alone, add value to the change detail, submit value
under name (one entry per date in multiple mode), and treat an empty value as missing when required.
marked lists dates that carry a dot (events, availability): an array in script, or ;-separated in markup. For more
than a dot, such as a price or an event count, set renderDay: it receives { date, day, selected, disabled, marked, today } for each day and returns what to draw under the number. The day button, its accessible name and the selection
stay the component’s. The gallery shows both.
September 2026
Limiting the date range
min and max are inclusive YYYY-MM-DD bounds. Days outside them are greyed out (--c2-date-selector__day__disabled--opacity) and cannot be clicked or reached with the keyboard, and the previous/next month buttons are disabled once the next month would hold no selectable day. For a range relative to today, compute the bounds when the page loads — the examples below do so from data-min-days / data-max-days offsets:
November 2026
November 2026
Build the bounds from the local date parts rather than toISOString(), which is UTC and can land on the wrong day:
import type { DateSelector } from '@c2n/components/date-selector'
function isoDaysFromToday(offset: number) {
const date = new Date()
date.setDate(date.getDate() + offset)
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${date.getFullYear()}-${month}-${day}`
}
const booking = document.querySelector<DateSelector>('#booking')!
booking.min = isoDaysFromToday(-2) // d-2 and later
const history = document.querySelector<DateSelector>('#history')!
history.max = isoDaysFromToday(-1) // d-1 and earlier: past dates only