Date Picker
<quiet-date-picker>
Presents a full calendar interface for picking single dates or date ranges, with calendar and year views, keyboard navigation, and rich per-cell customization. Reach for it as a primitive when you need an inline calendar or a custom date picker.
<quiet-date-picker></quiet-date-picker>
This component is a primitive for enabling date selection. It is not a form control, so it will not submit a value to a form. See date input and known date for date components that submit with forms.
Examples Jump to heading
Providing an initial value Jump to heading
Set the value attribute to an ISO 8601 date string (YYYY-MM-DD) to preselect a
date.
<quiet-date-picker value="2026-03-12"></quiet-date-picker>
The value is a calendar date (YYYY-MM-DD). No time or timezone is included. If
your app uses Date objects, convert them to a date string before assigning.
Setting a minimum and maximum date Jump to heading
Use the min and max attributes to restrict which dates are selectable. Both accept
ISO date strings. The previous and next navigation buttons disable automatically when there are no further
months to show within min/max, and keyboard paging
(PageUp/PageDown) clamps the focused date at the boundary instead of walking into
fully out-of-range months.
<quiet-date-picker initial-month="2026-04" min="2026-04-08" max="2026-04-26" value="2026-04-17" ></quiet-date-picker>
Selecting a date range Jump to heading
Set type="range" to let users pick a start and end date. The value is two ISO dates
joined by /. The first click anchors the start, hover or arrow keys preview the range, and the
second click commits. Pressing Escape during selection clears the anchor without changing the
value.
<quiet-date-picker type="range" value="2026-04-07/2026-04-23" ></quiet-date-picker>
If the user clicks an earlier date after the anchor, the start and end are swapped automatically so the range always reads left-to-right.
Disabled dates can't be chosen as a range's start or end, but a range can still span across them. The example above includes a few disabled dates to demonstrate this.
Constraining the range length Jump to heading
Use min-range and max-range to set bounds on how many days a selected range may
span (inclusive of both ends). While the user hovers during selection, cells beyond the allowed bounds are
visually disabled.
<quiet-date-picker type="range" min-range="3" max-range="14" ></quiet-date-picker>
Disabling specific dates Jump to heading
Use the disabled-dates attribute to disable individual ISO dates or inclusive ranges. Dates are
space-delimited. Ranges use : as the delimiter — for example,
2026-07-01:2026-07-07 disables every day from July 1 through July 7 inclusive.
<quiet-date-picker initial-month="2026-03" disabled-dates="2026-03-03 2026-03-04 2026-03-13:2026-03-17" ></quiet-date-picker>
Disabling days of the week Jump to heading
Use the disabled-days attribute to disable every occurrence of one or more weekdays. Accepts
space-delimited tokens: sun, mon, tue, wed,
thu, fri, sat.
<quiet-date-picker disabled-days="sat sun"></quiet-date-picker>
Custom disabled logic Jump to heading
For anything more complex than dates and weekdays, set the isDateDisallowed property to a
function. It runs for every cell and returns true when the date should be disabled. The result
is combined with disabled-dates and disabled-days via OR.
<quiet-date-picker id="date-picker__future-only"></quiet-date-picker> <script> const picker = document.getElementById('date-picker__future-only'); const today = new Date(); today.setHours(0, 0, 0, 0); picker.isDateDisallowed = (date) => date < today; </script>
Showing multiple months Jump to heading
Set the months attribute to render two or more months side by side. The
page-by attribute controls whether the previous/next buttons page by the full visible width
(visible, the default) or by a single month (single).
<quiet-date-picker type="range" months="2" ></quiet-date-picker>
First day of the week Jump to heading
By default, the first day of the week is derived from the locale. Use the
week-starts-with attribute to override it.
<quiet-date-picker week-starts-with="mon"></quiet-date-picker>
Localization Jump to heading
The date picker's weekday headers, month names, navigation labels ("Previous month", "Choose
year", etc.), and screen reader announcements are localized using the component's
lang attribute (or the page's language when not set on the component). Import the corresponding
translation so the UI strings are available. The first day of the week and date formatting are derived
automatically from the locale via Intl.
<quiet-date-picker lang="es" value="2026-04-09"></quiet-date-picker> <quiet-date-picker lang="ja" value="2026-04-09"></quiet-date-picker> <script type="module"> import '/dist/translations/es.js'; import '/dist/translations/ja.js'; </script>
This component also updates based on the page's language, which can be set using
<html lang="…">.
Hiding adjacent days Jump to heading
By default, the leading and trailing weeks of the calendar grid render days outside the current month as
muted, selectable cells. Set without-adjacent-days to hide them and show blank cells instead.
<quiet-date-picker without-adjacent-days></quiet-date-picker>
Week numbers Jump to heading
Set with-week-numbers to render the ISO or US week number along the leading edge of the grid.
The system is auto-derived from week-starts-with: weeks starting on Monday use ISO 8601
numbering, otherwise US numbering is used.
<quiet-date-picker with-week-numbers week-starts-with="mon" ></quiet-date-picker>
Weekday label format Jump to heading
Set day-format to control how weekday headers are formatted. Accepts narrow,
short (the default), or long.
<quiet-date-picker day-format="narrow"></quiet-date-picker>
Month label format Jump to heading
Set month-format to control how the month name appears in the view-toggle button and in
multi-month titles. Accepts narrow, short, or long (the default).
<quiet-date-picker month-format="short"></quiet-date-picker>
Setting the initial month Jump to heading
Use the initial-month attribute to control which month and year the calendar opens to without
committing a date. Accepts either YYYY-MM or YYYY-MM-DD (only the month and year
are used).
<quiet-date-picker initial-month="2026-03"></quiet-date-picker>
If value is also set, it will take precedence and the calendar will open to the month of the
selected date instead.
Custom day content Jump to heading
Set the renderDay property to a function to control the content of every day cell. The function
receives a Date and returns a string or a
. The default renders the day number.
<quiet-date-picker id="date-picker__events" value="2026-03-19"></quiet-date-picker> <script> const picker = document.getElementById('date-picker__events'); const events = ['2026-03-05', '2026-03-19', '2026-03-26']; picker.renderDay = (date) => { const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, '0'); const d = String(date.getDate()).padStart(2, '0'); const iso = `${y}-${m}-${d}`; if (events.includes(iso)) { return '<span>🐈</span>'; } return String(date.getDate()); }; </script>
You should only return trusted HTML from the renderDay() function, otherwise you may become
vulnerable to XSS exploits.
For per-date overrides, you can also slot custom content using the ISO date as the slot name. Slotted
content takes precedence over renderDay.
<quiet-date-picker value="2026-04-15"> <span slot="2026-04-08">🎂</span> <span slot="2026-04-22">✈️</span> </quiet-date-picker>
Custom day labels Jump to heading
Set the formatDay property to a function that returns the desired label for a given weekday.
The function receives a sample Date for that weekday. Setting formatDay overrides
day-format.
<quiet-date-picker id="date-picker__weekday-symbols"></quiet-date-picker> <script> const picker = document.getElementById('date-picker__weekday-symbols'); const symbols = ['☀', 'M', 'T', 'W', 'T', 'F', '★']; picker.formatDay = (date) => symbols[date.getDay()]; </script>
Header and footer slots Jump to heading
Use the header and footer slots to render custom content above or below the
calendar. The header is a good spot for availability hints or other guidance the user should see before
picking a date. The footer is a natural place for shortcut buttons like "Today" or preset ranges.
<quiet-date-picker id="date-picker__with-slots" value="2026-02-14"> <small slot="header" class="selected-label"></small> <div slot="footer" class="date-picker__footer"> <quiet-button class="today" size="sm">Today</quiet-button> <quiet-button class="clear" size="sm">Clear</quiet-button> </div> </quiet-date-picker> <style> .selected-label { display: block; padding-block-start: 0; text-align: center; } .date-picker__footer { display: flex; gap: .5rem; } </style> <script> const picker = document.getElementById('date-picker__with-slots'); const label = picker.querySelector('.selected-label'); const todayButton = picker.querySelector('.today'); const clearButton = picker.querySelector('.clear'); const format = (value) => { if (!value) return 'No date selected'; const [y, m, d] = value.split('-').map(Number); return new Date(y, m - 1, d).toLocaleDateString(undefined, { dateStyle: 'full' }); }; const update = () => label.textContent = format(picker.value); update(); picker.addEventListener('quiet-change', update); todayButton.addEventListener('click', () => picker.goToToday()); clearButton.addEventListener('click', () => { picker.clear(); update(); }); </script>
Relative date presets Jump to heading
Pair the footer slot with shortcut buttons that jump to common relative dates. Each button sets
value and calls goToDate() so the calendar scrolls to match. This example uses the
pattern to build a snooze picker.
<quiet-date-picker id="date-picker__snooze" class="snooze-picker"> <small slot="header" class="snooze-label">Snooze until…</small> <div slot="footer" class="snooze-presets"> <quiet-button data-offset="0" size="sm">Today</quiet-button> <quiet-button data-offset="1" size="sm">Tomorrow</quiet-button> <quiet-button data-offset="3" size="sm">In 3 days</quiet-button> <quiet-button data-offset="7" size="sm">Next week</quiet-button> <quiet-button data-offset="30" size="sm">In 30 days</quiet-button> <quiet-button data-offset="90" size="sm">In 90 days</quiet-button> </div> </quiet-date-picker> <style> .snooze-picker { width: 100%; max-width: 24rem; } .snooze-label { display: block; padding-block-start: 0; text-align: center; } .snooze-presets { display: grid; grid-template-columns: repeat(3, 1fr); gap: .5rem; width: 100%; } .snooze-presets quiet-button::part(button) { width: 100%; } @container date-picker (max-width: 22rem) { .snooze-presets { grid-template-columns: repeat(2, 1fr); } } @container date-picker (max-width: 14rem) { .snooze-presets { grid-template-columns: 1fr; } } </style> <script> const picker = document.getElementById('date-picker__snooze'); const today = new Date(); today.setHours(0, 0, 0, 0); picker.isDateDisallowed = (date) => date < today; picker.querySelectorAll('[data-offset]').forEach(button => { button.addEventListener('click', () => { const target = new Date(); target.setHours(0, 0, 0, 0); target.setDate(target.getDate() + Number(button.dataset.offset)); const y = target.getFullYear(); const m = String(target.getMonth() + 1).padStart(2, '0'); const d = String(target.getDate()).padStart(2, '0'); picker.value = `${y}-${m}-${d}`; picker.goToDate(target); }); }); </script>
Custom navigation icons Jump to heading
Use the previous-icon and next-icon slots to swap the navigation icons.
<quiet-date-picker> <quiet-icon slot="previous-icon" name="arrow-left"></quiet-icon> <quiet-icon slot="next-icon" name="arrow-right"></quiet-icon> </quiet-date-picker>
Changing the view Jump to heading
The view attribute switches between the calendar and year views.
Clicking the header button (which shows the current month and year) opens a scrollable year picker. You can
also set the view programmatically. The year list spans from 1900 (or the year of
min) to the current year plus 50 (or the year of max).
<quiet-date-picker view="year"></quiet-date-picker>
Listening for events Jump to heading
The date picker dispatches several events you can hook into.
<quiet-date-picker id="date-picker__events-demo" type="range"></quiet-date-picker> <pre id="date-picker__events-log" class="date-picker__log"></pre> <style> .date-picker__log { margin-block-start: 1rem; margin-block-end: 0; padding: .75rem; border-radius: var(--quiet-border-radius-md); background-color: var(--quiet-neutral-fill-softer); font-size: .875rem; line-height: 1.4; white-space: pre-wrap; &:empty { display: none; } } </style> <script> const picker = document.getElementById('date-picker__events-demo'); const log = document.getElementById('date-picker__events-log'); const lines = []; function append(line) { lines.unshift(line); log.textContent = lines.slice(0, 8).join('\n'); } picker.addEventListener('quiet-range-start', e => append(`range-start: ${e.detail.start}`)); picker.addEventListener('quiet-range-end', e => append(`range-end: ${e.detail.start} → ${e.detail.end}`)); picker.addEventListener('quiet-change', () => append(`change: ${picker.value || '(empty)'}`)); picker.addEventListener('quiet-view-change', e => append(`view-change: ${e.detail.view}`)); </script>
Changing the size Jump to heading
Use the size attribute to change the date picker's size.
<quiet-select label="Select a size" style="max-width: 18rem; margin-block-end: 2rem;"> <option value="xs" day-format="narrow">Extra small</option> <option value="sm">Small</option> <option value="md">Medium</option> <option value="lg">Large</option> <option value="xl">Extra large</option> </quiet-select> <quiet-date-picker size="xs" id="date-picker__size" ></quiet-date-picker> <script> const datePicker = document.getElementById('date-picker__size'); const select = datePicker.previousElementSibling; select.addEventListener('quiet-change', () => { datePicker.size = select.value; }); </script>
Disabling Jump to heading
Use the disabled attribute to make the date picker non-interactive.
<quiet-date-picker disabled value="2026-04-30"></quiet-date-picker>
API Jump to heading
Importing Jump to heading
The autoloader is the recommended way to import components but, if you prefer to do it manually, the following code snippets will be helpful.
To manually import <quiet-date-picker> from the CDN, use the following code.
import 'https://cdn.quietui.org/v5.2.0/components/date-picker/date-picker.js';
To manually import <quiet-date-picker> from a self-hosted distribution, use the
following code. Remember to replace /path/to/quiet with the appropriate local path.
import '/path/to/quiet/components/date-picker/date-picker.js';
Slots Jump to heading
Date Picker supports the following slots. Learn more about using slots
| Name | Description |
|---|---|
header
|
Custom content placed above the calendar grid. Empty by default. |
footer
|
Custom content placed below the calendar grid. Empty by default. |
previous-icon
|
A custom icon for the previous navigation button. |
next-icon
|
A custom icon for the next navigation button. |
YYYY-MM-DD
|
Per-day content. The slot name is the ISO date (e.g. 2026-05-22). Slotted content takes
precedence over the renderDay callback.
|
Properties Jump to heading
Date Picker has the following properties that can be set with corresponding attributes. In many cases, the attribute's name is the same as the property's name. If an attribute is different, it will be displayed after the property. Learn more about attributes and properties
| Property | Description | Reflects | Type | Default |
|---|---|---|---|---|
value
|
The date picker's value. In single mode this is an ISO 8601 date string
(YYYY-MM-DD). In range mode this is two dates joined by
/ (YYYY-MM-DD/YYYY-MM-DD). Empty when nothing is selected.
|
|
string
|
''
|
type
|
The selection mode. |
|
'single' |
'range'
|
'single'
|
min
|
The earliest selectable date as an ISO YYYY-MM-DD string.
|
|
string
|
|
max
|
The latest selectable date as an ISO YYYY-MM-DD string.
|
|
string
|
|
minRange
min-range
|
The minimum number of days a range may span, inclusive of both ends. Only applies when
type="range".
|
|
number
|
|
maxRange
max-range
|
The maximum number of days a range may span, inclusive of both ends. Only applies when
type="range".
|
|
number
|
|
view
|
The currently active view. |
|
'calendar' |
'year'
|
'calendar'
|
focusedDate
focused-date
|
The ISO date that holds the roving tabindex in the calendar view. Setting this scrolls the calendar to that month and year. |
|
string
|
|
initialMonth
initial-month
|
Sets the initial month and year shown when value is empty. Accepts
YYYY-MM or YYYY-MM-DD (only the month and year are used).
|
|
string
|
|
size
|
The date picker's size. |
|
'xs' |
'sm' |
'md' |
'lg' |
'xl'
|
'md'
|
months
|
Number of months to display side by side. |
|
number
|
1
|
pageBy
page-by
|
When months > 1, pages the calendar by all visible months at once or by a single
month.
|
|
'visible' |
'single'
|
'visible'
|
weekStartsWith
week-starts-with
|
The first day of the week. Defaults to the locale's first day. Valid options include
sun, mon, tue, wed, thu,
fri, and sat.
|
|
Weekday
|
|
dayFormat
day-format
|
The format used for weekday header labels. |
|
'narrow' |
'short' |
'long'
|
'short'
|
monthFormat
month-format
|
The format used for the month name in the view-toggle button and multi-month titles. |
|
'narrow' |
'short' |
'long'
|
'long'
|
withoutAdjacentDays
without-adjacent-days
|
Hides the trailing days of the previous month and the leading days of the next month. By default, these adjacent days are rendered as muted, selectable cells. |
|
boolean
|
false
|
withWeekNumbers
with-week-numbers
|
Renders week numbers along the leading edge of the calendar grid. |
|
boolean
|
false
|
disabledDates
disabled-dates
|
A space-delimited list of ISO dates (YYYY-MM-DD) or inclusive ISO date ranges
(YYYY-MM-DD:YYYY-MM-DD) to disable. Combined with disabled-days and
isDateDisallowed via OR.
|
|
string
|
''
|
disabledDays
disabled-days
|
A space-delimited list of weekday tokens (sun mon tue wed thu fri sat) to disable.
Every matching weekday is disabled. Combined with disabled-dates and
isDateDisallowed via OR.
|
|
string
|
''
|
disabled
|
Disables the entire date picker. |
|
boolean
|
false
|
isDateDisallowed
|
An overridable callback that determines whether a specific date is disallowed. Combined with
disabled-dates and disabled-days via OR. The calendar internally checks at
the month and year views, disabling a month or year only when every date in it is disallowed.
Internal caches detect changes by function identity, so reassign the property to a new function
reference when its behavior changes; mutating closed-over state without reassignment will leave the
year-view cache stale.
|
|
(date: Date) => boolean
|
|
renderDay
|
An overridable callback used to render each day cell's content. The default renders the day number.
Slotted content (<span slot="YYYY-MM-DD">…</span>) takes
precedence over this callback.
|
|
(date: Date) => string |
TemplateResult
|
|
renderYear
|
An overridable callback used to render each year cell in view="year". The
default renders the four-digit year.
|
|
(year: number) => string |
TemplateResult
|
|
formatDay
|
An overridable callback for weekday header labels. The default derives the label from
day-format. This setting will override day-format.
|
|
((date: Date) => string) |
null
|
null
|
Methods Jump to heading
Date Picker supports the following methods. You can obtain a reference to the element and call them like functions in JavaScript. Learn more about methods
| Name | Description | Arguments |
|---|---|---|
focus() |
Sets focus to the calendar; lands on the currently focused cell. | |
blur() |
Removes focus from whatever element inside the date picker currently has focus. | |
goToToday() |
Sets focused-date to today, switches view to calendar, and
selects today if it isn't disallowed. In range mode the calendar only navigates to
today without anchoring a range. Like other programmatic methods, this does not emit
quiet-change/quiet-input, and it no-ops while disabled.
|
|
goToDate() |
Sets focused-date to the given date and switches view to
calendar. No-ops while disabled.
|
date: string |
Date
|
clear() |
Clears value and any in-progress range anchor. No-ops while disabled; does
not emit change/input events.
|
|
showPreviousMonth() |
Moves to the previous page: a month (or page of months) in the calendar view, a decade in the year view. | |
showNextMonth() |
Moves to the next page: a month (or page of months) in the calendar view, a decade in the year view. | |
setView() |
Sets the active view. Equivalent to assigning the view property.
|
view: 'calendar' |
'year'
|
Events Jump to heading
Date Picker dispatches the following custom events. You can listen to them the same way was native events. Learn more about custom events
| Name | Description |
|---|---|
quiet-change |
Emitted when the user commits a selection. |
quiet-input |
Emitted when the user provides input that may not be committed yet (currently a near-alias for
quiet-change; included for parity with other form-like components).
|
quiet-focus-day |
Emitted when the focused day cell changes. event.detail.date is the focused ISO date.
|
quiet-view-change |
Emitted when view changes. event.detail.view is the new view.
|
quiet-range-start |
Emitted when the user anchors the first end of a date range. event.detail.start is the
anchor's ISO date.
|
quiet-range-end |
Emitted when the user commits the second end of a date range. event.detail.start and
event.detail.end are the committed ISO dates.
|
CSS custom properties Jump to heading
Date Picker supports the following CSS custom properties. You can style them like any other CSS property. Learn more about CSS custom properties
| Name | Description | Default |
|---|---|---|
--spacing |
The padding around the edges of the picker card. |
1em
|
--cell-gap |
The gap between day cells. |
0
|
--selection-color |
The fill color for selected days and the range fill. Defaults to
var(--quiet-primary-fill-mid).
|
|
--selection-text-color |
The text color for selected days and the range fill. Defaults to
var(--quiet-primary-text-on-mid).
|
|
--today-color |
The text color for today when it isn't selected. Defaults to
var(--quiet-primary-text-colorful).
|
CSS parts Jump to heading
Date Picker exposes internal elements that can be styled with CSS using the selectors shown below. Learn more about CSS parts
| Name | Description | CSS selector |
|---|---|---|
header |
The header row containing the previous button, view-toggle button, and next button. |
::part(header)
|
previous-button |
The previous navigation button. |
::part(previous-button)
|
next-button |
The next navigation button. |
::part(next-button)
|
view-button |
The single button that toggles between the calendar and year views. |
::part(view-button)
|
calendar |
The calendar grid (the wrapper around weekday headers and the day grid). |
::part(calendar)
|
day-header |
The row of weekday labels. |
::part(day-header)
|
weekday |
An individual weekday label. |
::part(weekday)
|
week-number |
A week-number cell shown when with-week-numbers is set.
|
::part(week-number)
|
days |
The day grid in view="calendar". |
::part(days)
|
day |
An individual day cell. |
::part(day)
|
year-grid |
The scrollable grid in view="year". |
::part(year-grid)
|
year |
An individual year cell in view="year". |
::part(year)
|
footer |
The footer container. |
::part(footer)
|
Custom States Jump to heading
Date Picker has the following custom states. You can target them with CSS using the selectors shown below. Learn more about custom states
| Name | Description | CSS selector |
|---|---|---|
disabled |
Applied when the date picker is disabled. |
:state(disabled)
|
blank |
Applied when value is empty. |
:state(blank)
|
focused |
Applied when any focusable element inside the picker has focus. |
:state(focused)
|
range |
Applied when type="range". |
:state(range)
|
tentative |
Applied when a range anchor has been set but the range isn't committed yet. |
:state(tentative)
|
view-calendar |
Applied when view="calendar". |
:state(view-calendar)
|
view-year |
Applied when view="year". |
:state(view-year)
|
Dependencies Jump to heading
Date Picker automatically imports the following elements. Sub-dependencies are also included in this list.