Skip to content

Date Picker

<quiet-date-picker> stable since 5.2

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 TemplateResult . 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>

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.

Snooze until…
Today Tomorrow In 3 days Next week In 30 days In 90 days
<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.

CDN Self-hosted

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.

Search this website Toggle dark mode View the code on GitHub Follow @quietui.org on Bluesky Follow @quiet_ui on X

    No results found