Inputs
Calendar
Month grid — one date, a range, or a handful of dates.
import { Calendar } from 'omaris' Examples
Basic
One date, bound. The value is a Date at local midnight; the caption opens a month and year grid for jumping further than a few presses.
Sunday, September 27, 2026
<script lang="ts"> import { Calendar, Text, latn } from 'omaris'; let day = $state<Date | null>(new Date()); const long = new Intl.DateTimeFormat('en-US', latn({ dateStyle: 'full' }));</script><div class="flex flex-col items-start gap-2"> <Calendar bind:value={day} class="rounded-shape-lg border border-border" /> <Text variant="body-sm" tone="muted">{day ? long.format(day) : 'No date'}</Text></div> Range
mode="range": the first press starts it, the second ends it, and in between the band follows the pointer or the focus. months={2} puts two side by side from sm up and stacks them below.
Sep 30 – Oct 5
<script lang="ts"> import { Calendar, Text, addDays, startOfDay, latn, type DateRange } from 'omaris'; const today = startOfDay(new Date()); let stay = $state<DateRange | null>({ start: addDays(today, 3), end: addDays(today, 8) }); const short = new Intl.DateTimeFormat('en-US', latn({ month: 'short', day: 'numeric' }));</script><div class="flex flex-col items-start gap-2"> <Calendar mode="range" months={2} min={today} bind:value={stay} class="rounded-shape-lg border border-border" /> <Text variant="body-sm" tone="muted"> {stay ? short.formatRange(stay.start, stay.end) : 'Pick a check-in day'} </Text></div> Multiple and bounds
mode="multiple" toggles days in and out of a sorted array. min, max and isDateDisabled take days off the table — here, the next six weeks with Fridays and Saturdays closed — and showWeekNumbers adds the ISO week.
0 shifts picked
<script lang="ts"> import { Calendar, Text, addDays, startOfDay } from 'omaris'; const today = startOfDay(new Date()); let shifts = $state<Date[]>([]); const closed = (date: Date) => date.getDay() === 5 || date.getDay() === 6;</script><div class="flex flex-col items-start gap-2"> <Calendar mode="multiple" bind:value={shifts} min={today} max={addDays(today, 42)} isDateDisabled={closed} showWeekNumbers class="rounded-shape-lg border border-border" /> <Text variant="body-sm" tone="muted"> {shifts.length === 1 ? '1 shift' : `${shifts.length} shifts`} picked </Text></div> Locale
Names come from Intl and the first weekday from the locale — Saturday in Iraq, Monday in Germany. Inside dir="rtl" the arrows, the keys and the band all mirror, and the digits stay 0–9.
<script lang="ts"> import { Calendar } from 'omaris';</script><div class="flex flex-wrap items-start gap-4"> <div dir="rtl" lang="ar-IQ"> <Calendar locale="ar-IQ" class="rounded-shape-lg border border-border" /> </div> <Calendar locale="de-DE" weekdayFormat="short" class="rounded-shape-lg border border-border" /></div> Overridden
class is the root, so a card's padding replaces the calendar's own, and classes reaches every part — here square faces, a tinted caption and a wider cell through the --cal-cell property the grid is sized from.
<script lang="ts"> import { Calendar } from 'omaris';</script><div class="w-full max-w-sm rounded-shape-xl bg-surface-container-low p-2"> <Calendar class="flex w-full p-1 [--cal-cell:3rem]" aria-label="Delivery day" classes={{ face: 'rounded-shape-sm', caption: 'text-primary', weekday: 'text-label-sm uppercase' }} /></div> When to use it
Use it for
- A date picked by looking at the month it falls in — a delivery day, a booking, a shift — where seeing the weekday and the week around it is the point.
- A span of days with
mode="range": a stay, a leave request, a report period.months={2}shows a range that crosses a month end without paging. - Several separate days with
mode="multiple"— rota shifts, class dates, blackout days. - A calendar that stays on the page: a booking sidebar, a scheduling panel, the body of a card. For one that opens from a field, reach for the field.
Not for
- A date inside a form, shown as text until it is edited → Date Field. It wraps this calendar in a popover on a desktop and a sheet on a phone.
- A period filter on a dashboard, with shortcuts like "Last 30 days" → Date Range Field.
- A date far from today — a birthday, an expiry year → the
DatePickerwheels in Picker. Paging back forty years a month at a time is the wrong tool, even with the year grid. - A time of day →
TimePickerin Picker. - Showing events across a month or a week. This picks dates; it does not lay out a schedule.
Do
- Keep values at local midnight —
new Date(2026, 2, 3), orstartOfDayon anything else. The calendar hands back midnights, and compares by day. - Set
minandmaxwhenever the valid window is known. The arrows stop at its months and the out-of-range days grey out, which is kinder than an error after the fact. - Use
isDateDisabledfor the rules a window cannot express — weekends, holidays, sold-out nights. - Leave
localeandweekStartsOnalone unless the page's language is wrong for the calendar. Both follow<html lang>, and a week that starts on the wrong day is the first thing people notice.
Don't
- Build dates from ISO strings —
new Date('2026-03-03')is UTC midnight, which is the 2nd of March west of Greenwich. - Expect a range's
valueto change on the first press. It is set once the range is whole; the half-drawn state is the calendar's own. - Set
showOutsideDayswith two months — the neighbouring month is already on screen, so it is ignored. - Squeeze it much under 280px. The cells shrink to fit, but below about 36px a day is hard to hit with a finger.
API
Calendar
Month grid — one date, a range, or a handful of dates.
Values are plain Dates at local midnight: new Date(2026, 2, 3) is March 3rd, and so is every date the calendar gives back. What value holds follows mode — a Date (or null) for single, a { start, end } (or null) for range, a Date[] for multiple.
It is the full WAI-ARIA date grid: arrows move a day or a week, Home and End go to the ends of the week, PageUp/PageDown a month, with Shift a year, and only one day is ever a tab stop. The caption opens a month and year grid for jumping further than a few presses. A range previews itself from its first day to wherever the pointer or the focus is, and its band is drawn with logical sides, so it reads right in Arabic.
Names come from Intl in the page's language, with Western digits always; the week starts on the day that language starts it on.
import { Calendar } from 'omaris' <Calendar bind:value={day} min={today} /><Calendar mode="range" months={2} bind:value={range} /> Parts
Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.
root- The whole calendar.
--cal-cellhere sizes every day. months- The row of months — side by side from
smup, stacked below it. month- One month: its header and its grid. Hidden under the month/year grid.
header- Caption and the previous/next buttons.
caption- The month and year — a button that opens the month/year grid.
nav- The previous and next buttons.
grid- One month's
role="grid". weeks- The weeks, which slide when the month changes.
row- A week, or the weekday headings — seven columns, eight with week numbers.
weekday- A weekday column heading.
weekNumber- The ISO week number at the start of a row.
cell- The
gridcellaround a day. day- The day's button — the full cell, so the hit area is the cell.
track- Holds the band and the face, and is exactly as tall as the face.
band- The range band behind the faces, rounded where a row or the range ends.
face- The round face with the day number on it.
picker- The month or year grid the caption opens, laid over the days.
pickerHeader- The year (or the span of years) and its arrows.
pickerGrid- Three columns of months or years.
pickerItem- A month or a year in that grid.
Props
mode M 'single' (the default), 'range' or 'multiple'. Decides what value holds.
value bindableCalendarValue<M> The selection, at local midnight. A Date or null in single, { start, end } or null in range — set on the second press, never half-filled — and a sorted Date[] in multiple.
month bindableDate The first month on screen. Defaults to the selection's month, or today's.
min Date Earliest selectable day. The previous button stops at its month.
max Date Latest selectable day. The next button stops at its month.
isDateDisabled (date: Date) => boolean Days that cannot be picked — weekends, holidays, sold-out dates.
locale string BCP-47 tag for names and the first weekday. Defaults to <html lang>, then the browser.
weekStartsOn Weekday 0 is Sunday. Defaults to the locale's own first day.
months Defaults to 1
1 | 2 Months side by side. They stack below the sm breakpoint.
showOutsideDays Defaults to true
boolean Fill the first and last rows with the neighbouring months' days. Ignored with two months, where the neighbour is already on screen.
showWeekNumbers Defaults to false
boolean ISO week numbers at the start of each row.
weekdayFormat Defaults to 'narrow'
'narrow' | 'short' Weekday headings: narrow ("M") or short ("Mon").
autofocus Defaults to false
boolean Focus the active day as soon as the calendar mounts — for one that opens in a popup.
swipe Defaults to true
boolean Swipe sideways on a touch screen to change month.
haptics Defaults to true
boolean Vibrate on a pick, where the haptics store is on.
labels CalendarLabels Accessible names for the arrows, the caption and the week column — for translation.
onchange (value: CalendarValue<M>) => void Fires with the new value after every pick — for range, once the range is whole.
class string Merged onto the root, so class="p-0" removes the padding.
classes CalendarClasses Per-part Tailwind overrides. class still covers the root.