calendar

One component, three modes. Bind a signal and get dates back as plain strings.

SuMoTuWeThFrSa

value → null

usage

import { Calendar } from 'qwik-date';

<Calendar bind:value={date} onChange$={(value) => console.log(value)} />

Dates are YYYY-MM-DD strings: no time, no timezone, no Date objects to compare. Today gets aria-current="date".

examples

range

With mode="range" the value is { start, end }. Before the second click, the days up to the pointer show the range you'd get.

SuMoTuWeThFrSa

value → { start: '2026-10-29', end: '2026-11-04' }

multiple

With mode="multiple" the value is an array of days. Click a day again to unmark it.

SuMoTuWeThFrSa

value → ['2026-10-29', '2026-10-30', '2026-10-31', '2026-11-05']

two months

months={2} shows two months side by side. They stack on narrow screens.

SuMoTuWeThFrSa
SuMoTuWeThFrSa

disabled days

min, max, disabledDates and disabledWeekdays rule out days. The keyboard skips them.

SuMoTuWeThFrSa

min="2026-10-29" · disabledWeekdays={[0, 6]}

months and years

The title is a button: a click shows the months of the year, another one twelve years, and a third one the days again. Choosing a month or a year shows its days. Nothing is selected on the way.

SuMoTuWeThFrSa

click the title: the past months and years are ruled out

  • In those views, ‹ and › move a year, or twelve years, with their own labels.
  • min and max disable the months and years past them, and a year past them shows the month of the limit.
  • The keyboard goes along the months and the years too, and Escape goes back to the days: see keyboard.

display options

Full weeks, week numbers, the weekday names and the first day of the week. Turn them on and off:

SuMoTuWeThFrSa
40
41
42
43
44
45
  • fullWeeks: the days of the months around fill the first and the last week, disabled, and every month shows six weeks, so the calendar keeps its height from month to month.
  • showWeekNumber: a column with the ISO week number of each row.
  • showDaysOfWeek={false}: no weekday names over the grid.
  • weekStartsOn: the first column, from 0 (Sunday, the default) to 6. Home and End follow it. See also localization.

api

Everything qwik-date exports, and what the calendar puts in the DOM for your CSS.

props

Click a prop for the details and an example. new came in 2.x, renamed had another name in 1.x. The headless Calendar.Root takes the same options, except showDaysOfWeek, theme, unstyled, the icons and the props of each part, which only the styled Calendar has: the headless parts take their props directly.

// selection

mode'single' | 'range' | 'multiple''single'new

What a click selects: one day, a start and an end, or any days. It sets the type of value, bind:value and onChange$.

type
'single' | 'range' | 'multiple'
default
'single'
<Calendar mode="range" bind:value={range} />
valueLocalDate | DateRange | LocalDate[] | nullnullrenamed

The selected value, selected from the start: a day, a DateRange with mode="range", or the days with mode="multiple" ([] by default). The calendar opens on its month, or on today's without one.

type
LocalDate | DateRange | LocalDate[] | null
default
null
1.x
date
<Calendar value="2026-10-29" />
bind:valueSignal<…>–renamed

A signal of the value's type, kept in sync in both directions: write to it and the calendar shows that month.

type
Signal<…>
default
–
1.x
bind:date
const date = useSignal<LocalDate | null>(null);

<Calendar bind:value={date} />
onChange$(value) => void–renamed

Called with the new value on every choice. In a range, also after the first day, with end: null; with mode="multiple", on every mark and unmark, with the days in order.

type
(value) => void
default
–
1.x
onDateChange$
<Calendar onChange$={(value) => console.log(value)} />

// limits

min, maxLocalDate–new

The first and the last day that can be chosen. The month buttons stop at their months, and the months and years views disable the ones past them.

type
LocalDate
default
–
<Calendar min="2026-10-29" max="2027-01-27" />
disabledDatesreadonly LocalDate[]–new

Days that can't be chosen. The keyboard skips them, and a range can't cross them: it ends right before the first one, as with every day ruled out.

type
readonly LocalDate[]
default
–
<Calendar disabledDates={['2026-12-25', '2027-01-01']} />
disabledWeekdaysreadonly WeekDay[]–new

Weekdays that can't be chosen, 0 for Sunday: [0, 6] disables the weekends.

type
readonly WeekDay[]
default
–
<Calendar disabledWeekdays={[0, 6]} />

// display

monthsnumber1new

How many months show side by side, with one Tab stop for all of them. The buttons move one month, and they stack on narrow screens.

type
number
default
1
<Calendar mode="range" months={2} />
fullWeeksbooleanfalse

Fills the first and the last week with the days of the months around, disabled, and shows six weeks in every month.

type
boolean
default
false
<Calendar fullWeeks />
showWeekNumberbooleanfalse

Adds a column with the ISO week number of each row.

type
boolean
default
false
<Calendar showWeekNumber />
showDaysOfWeekbooleantrue

Shows the weekday names over the grid.

type
boolean
default
true
<Calendar showDaysOfWeek={false} />
prevIcon, nextIconComponent<PropsOf<'svg'>>chevronsrenamed

Components for the icons of the month buttons. They get iconProps, and the library styles turn them around in right-to-left layouts.

type
Component<PropsOf<'svg'>>
default
chevrons
1.x
iconLeft, iconRight
<Calendar prevIcon={ArrowLeft} nextIcon={ArrowRight} />

// locale

localeLocale'en'

'en', 'es', or any BCP 47 tag ('fr-FR'). Other languages take their month and weekday names from Intl, and need labels.

type
Locale
default
'en'
<Calendar locale="es" />
labelsLabels | Partial<Labels>–new

The texts Intl can't give: the calendar's name and the labels of the buttons. With en and es they're built in, and labels overrides some of them; any other locale needs all of them, and TypeScript asks for them.

type
Labels | Partial<Labels>
default
–
<Calendar locale="fr-FR" labels={fr} weekStartsOn={1} />
weekStartsOnWeekDay0new

The first column, from 0 (Sunday) to 6.

type
WeekDay
default
0
<Calendar weekStartsOn={1} />

// forms & focus

namestring–new

Submits the value with the form around the calendar, in hidden inputs: name for a day (empty without one), name.start and name.end for a range, a name[] per day for multiple (nothing without days). A Qwik Router action gets an object for a range and an array for multiple.

type
string
default
–
<form>
  <Calendar name="due" />
</form>
autoFocusbooleanfalsenew

Focuses the day where Tab enters every time the calendar shows up: on load, or when a popover, dialog or details around it opens. Without it, the calendar never takes the focus.

type
boolean
default
false
<Calendar autoFocus containerProps={{ popover: 'auto' }} />

// styling

themeTheme | Signal<Theme>'light'new

'light', 'dark' or 'auto', which follows prefers-color-scheme with CSS only. A signal changes it at runtime.

type
Theme | Signal<Theme>
default
'light'
<Calendar theme="auto" />
unstyledbooleanfalserenamed

Leaves the calendar without the library styles: style it through the props of its parts and its data attributes.

type
boolean
default
false
1.x
unStyled
<Calendar unstyled />

// props of each part

containerPropsOmit<PropsOf<'div'>, 'onChange$'>{}

The root, a <div role="group">. Its aria-label names the calendar, followed by the selection. No DOM onChange$: the calendar's is the value's.

type
Omit<PropsOf<'div'>, 'onChange$'>
default
{}
<Calendar containerProps={{ class: 'calendar', 'aria-label': 'Due date' }} />
headerPropsPropsOf<'header'>{}

The <header> with the month buttons and the title. With months, each month's header, around its title.

type
PropsOf<'header'>
default
{}
<Calendar headerProps={{ class: 'header' }} />
navButtonPropsPropsOf<'button'>{}renamed

Both month buttons. prevButtonProps and nextButtonProps win over it, and an onClick$ in both runs both.

type
PropsOf<'button'>
default
{}
1.x
actionButtonProps
<Calendar navButtonProps={{ class: 'nav' }} />
prevButtonProps, nextButtonPropsPropsOf<'button'>{}renamed

The previous and the next button, one each.

type
PropsOf<'button'>
default
{}
1.x
actionLeftProps, actionRightProps
<Calendar prevButtonProps={{ title: 'Previous' }} />
titlePropsPropsOf<'div'>{}

The title, the live region that announces each month and view. Its id names the first grid.

type
PropsOf<'div'>
default
{}
<Calendar titleProps={{ class: 'title' }} />
iconPropsPropsOf<'svg'>{}

Both icons: the chevrons, or prevIcon and nextIcon. The chevrons are 1em wide, so the text around sizes them.

type
PropsOf<'svg'>
default
{}
<Calendar iconProps={{ 'stroke-width': 2 }} />
calendarPropsPropsOf<'table'>{}

Each days grid, a <table role="grid">.

type
PropsOf<'table'>
default
{}
<Calendar calendarProps={{ class: 'grid' }} />
theadPropsPropsOf<'thead'>{}

The head of the grid, with the weekday names.

type
PropsOf<'thead'>
default
{}
<Calendar theadProps={{ class: 'weekdays' }} />
theadRowPropsPropsOf<'tr'>{}

The row of the weekday names.

type
PropsOf<'tr'>
default
{}
<Calendar theadRowProps={{ class: 'weekdays-row' }} />
headerCellPropsPropsOf<'th'>{}

Each weekday name.

type
PropsOf<'th'>
default
{}
<Calendar headerCellProps={{ class: 'weekday' }} />
tbodyPropsPropsOf<'tbody'>{}

The body of the grid, with the weeks.

type
PropsOf<'tbody'>
default
{}
<Calendar tbodyProps={{ class: 'weeks' }} />
tbodyRowPropsPropsOf<'tr'>{}

Each week. Rows get data-direction after a move, to animate them.

type
PropsOf<'tr'>
default
{}
<Calendar tbodyRowProps={{ class: 'week' }} />
cellPropsPropsOf<'td'>{}

The cell around each day, with aria-selected and aria-disabled.

type
PropsOf<'td'>
default
{}
<Calendar cellProps={{ class: 'cell' }} />
dayButtonPropsPropsOf<'button'>{}

Each day button: style its states by the data attributes below.

type
PropsOf<'button'>
default
{}
<Calendar dayButtonProps={{ class: 'day' }} />
weekNumberPropsPropsOf<'td'>{}

Each week number cell, with showWeekNumber.

type
PropsOf<'td'>
default
{}
<Calendar showWeekNumber weekNumberProps={{ class: 'week-number' }} />

data attributes

The styled Calendar and the headless parts set these, so each state can be styled with plain CSS or Tailwind's data-[selected]:.

attributeonwhen
data-qwik-daterootAlways, on the styled Calendar. The headless root doesn't have it.
data-themerootlight, dark or auto, from the theme prop.
data-unstyledrootWith unstyled.
data-viewrootdays, months or years: the view on show.
data-directionroot, rowsnext or prev, after each move. None on load.
data-valuedayThe day, as YYYY-MM-DD.
data-selecteddayThe selected day, or an end of the range.
data-range-startdayThe first day of the range.
data-range-enddayThe last day of the range.
data-in-rangedayThe days between the ends.
data-previewdayThe range a click would make, while the range has no end.
data-unavailabledayRuled out by min, max or the disabled days.
aria-current="date"dayToday.
disableddayCan't be chosen.
data-periodtableThe months and the years grids.
data-month, data-yearmonth, yearThe cell's month (2026-10) or year.
/* a range in your own color */
[data-qwik-date] [data-in-range] { background: #dbeafe; }
[data-qwik-date] :is([data-range-start], [data-range-end]) { background: #2563eb; color: white; }
[data-qwik-date] [data-unavailable] { text-decoration: line-through; }

keyboard

The grid is a single Tab stop, even with several months. Days that can't be chosen are skipped.

// days

←→The previous or next day. Mirrored in right-to-left layouts.↑↓The same weekday, a week before or after.HomeEndThe start or the end of the week.PageUpPageDownThe same day in the previous or next month, or that month's last day.EnterSpaceChooses the focused day.TabEnters on the selected day, or on the first one that can be chosen.

// months and years, after clicking the title

←→↑↓Move across months or years, past the edges of the page.EnterSpaceShow the days of that month, or of the same month in that year.EscapeBack to the days, with the focus on the title. A popover around stays open.

types

All exported from qwik-date, along with CalendarProps.

import type { CalendarProps, DateRange, Labels, LocalDate, Locale, Theme, WeekDay } from 'qwik-date';

// a day, without time or timezone: '2026-10-29'
type LocalDate = `${number}-${number}-${number}`;

// the value of mode="range": end is null until the second day
type DateRange = { start: LocalDate; end: LocalDate | null };

// 0 is Sunday, as in Date#getDay()
type WeekDay = 0 | 1 | 2 | 3 | 4 | 5 | 6;

type Theme = 'light' | 'dark' | 'auto';
type Locale = 'en' | 'es' | (string & {});

// built in for en and es, required for any other locale
interface Labels {
  calendar: string;
  previousMonth: string;   nextMonth: string;
  previousYear: string;    nextYear: string;
  previousYears: string;   nextYears: string;
  selectedDate: string;    selectedRange: string;   selectedDates: string;
}

functions

To show a value in a trigger or a label. They work in any locale, with a capital letter on the month.

formatDate(value: LocalDate, locale?: Locale): string            // locale = 'en'
formatDateRange(range: DateRange, locale?: Locale): string     // '…' while end is null
try a locale
> formatDate('2026-10-29')
'Oct 29, 2026'
> formatDateRange({ start: '2026-10-29', end: '2026-11-05' })
'Oct 29, 2026 – Nov 05, 2026'
> formatDateRange({ start: '2026-10-29', end: null })
'Oct 29, 2026 – …'