Skip to main contentSkip to docs navigation

Datepicker

Calendar and date picker popover with input or button toggles, single, multiple, and range date selection, and multi-month layouts.

@layer components
Requires JavaScript
Depends onVanilla Calendar Pro
Component tokens

Introduction

The Datepicker wraps Vanilla Calendar Pro to render a calendar as an input-attached popover, a button-triggered popover, or an always-visible inline calendar. It supports single, multiple, and ranged date selection, adapts to Chassis's color modes, and exposes any Vanilla Calendar Pro setting through a vcpOptions pass-through.

Basic structure

Add data-cx-toggle="datepicker" to a text input to initialize it as a datepicker trigger. Use type="text" rather than a native date input type so the browser's built-in date picker UI doesn't compete with the calendar popover.

HTML
<label for="datepicker1" class="form-label">Datepicker</label>
<input type="text" class="form-input" id="datepicker1" data-cx-toggle="datepicker" placeholder="Choose date…">

By default the calendar opens below the input on focus. Selecting a date updates the input's value — single-selection and completed range pickers close automatically, while multiple-date pickers stay open for further selection.

Layout options

Compose the datepicker input with other form markup for labels, help text, and icon adornments.

With field

Use the Field component to lay out and wrap inputs with a label, description, and validation feedback messages in a vertical stack.

We’ll never share your email with anyone else.
HTML
<div class="form-field">
  <label for="datepicker2" class="form-label">Datepicker field</label>
  <input type="text" class="form-input" id="datepicker2" data-cx-toggle="datepicker" placeholder="Choose date…">
  <div class="form-text">
    We’ll never share your email with anyone else.
  </div>
</div>

With icon

Use the Input Adorn component to add a calendar icon alongside the datepicker input. Use a <button> for the adorn element and focus the input on click so the calendar opens.

HTML
<label for="datepickerIconStart" class="form-label">Select date</label>
<div class="form-input">
  <input type="text" class="ghost-input" id="datepickerIconStart" data-cx-toggle="datepicker" placeholder="Choose date…">
  <button type="button" class="input-adorn" onclick="this.parentElement.querySelector('.ghost-input').focus()" aria-label="Open calendar">
    <svg class="icon" aria-hidden="true"><use href="#calendar-solid"></use></svg>
  </button>
</div>

The calendar positions itself relative to the input element by default. Pass a positionElement selector or element to anchor the popover to a different ancestor instead (see Options).

Date constraints

Restrict the selectable date range using data-cx-date-min and data-cx-date-max.

HTML
<label for="datepickerMinMax" class="form-label">Event date (2026 only)</label>
<input type="text" class="form-input" id="datepickerMinMax" data-cx-toggle="datepicker" data-cx-date-min="2026-01-01" data-cx-date-max="2026-12-31" placeholder="Select a date in 2026">

Selection modes

Set data-cx-selection-mode to control how many dates a picker accepts: single (default), multiple, or multiple-ranged.

Multiple dates

Enable multiple date selection with data-cx-selection-mode="multiple".

HTML
<label for="datepicker3" class="form-label">Select multiple dates</label>
<input type="text" class="form-input" id="datepicker3" data-cx-toggle="datepicker" data-cx-selection-mode="multiple" placeholder="Select date range…">

Date range

Select a range of dates with data-cx-selection-mode="multiple-ranged". Use data-cx-selected-dates to preselect a date range.

HTML
<label for="datepicker4" class="form-label">Select date range</label>
<input type="text" class="form-input" id="datepicker4" data-cx-toggle="datepicker" data-cx-selection-mode="multiple-ranged" data-cx-selected-dates='["2026-06-18", "2026-07-07"]' placeholder="Select start and end dates…">

Multi-month layout

Display multiple months side-by-side with data-cx-display-months-count. This gives date-range pickers enough width to show both ends of the range without navigating between months.

Multiple selection

Combine data-cx-selection-mode="multiple" with data-cx-display-months-count="2" to let a picker select individual dates that fall in different months.

HTML
<label for="datepickerMultiMonth" class="form-label">Select dates</label>
<input type="text" class="form-input" id="datepickerMultiMonth" data-cx-toggle="datepicker" data-cx-selection-mode="multiple" data-cx-display-months-count="2" placeholder="Select dates">

Range selection

Combine data-cx-selection-mode="multiple-ranged" with data-cx-display-months-count="2" to preselect a range that spans a month boundary.

HTML
<label for="datepickerRangeTwoMonths" class="form-label">Select date range</label>
<input type="text" class="form-input" id="datepickerRangeTwoMonths" data-cx-toggle="datepicker" data-cx-selection-mode="multiple-ranged" data-cx-display-months-count="2" data-cx-selected-dates='["2026-06-16", "2026-07-07"]' placeholder="Select start and end dates…">

Advanced features

Secondary options that fine-tune calendar behavior — which day starts the week and where the popover opens relative to the input.

First day of week

Set the first day of the week (0 = Sunday, 1 = Monday, etc.) with data-cx-first-weekday.

HTML
<label for="datepicker6" class="form-label">Week starts on Sunday</label>
<input type="text" class="form-input" id="datepicker6" data-cx-toggle="datepicker" data-cx-first-weekday="0" placeholder="Select a date">

Placement

Control where the calendar appears relative to the input with data-cx-placement. Options are left (default), center, right, and auto.

HTML
<div class="d-flex gap-md">
  <div>
    <label for="datepickerLeft" class="form-label">Left aligned</label>
    <input type="text" class="form-input" id="datepickerLeft" data-cx-toggle="datepicker" data-cx-placement="left" placeholder="Left">
  </div>
  <div>
    <label for="datepickerCenter" class="form-label">Center aligned</label>
    <input type="text" class="form-input" id="datepickerCenter" data-cx-toggle="datepicker" data-cx-placement="center" placeholder="Center">
  </div>
  <div>
    <label for="datepickerRight" class="form-label">Right aligned</label>
    <input type="text" class="form-input" id="datepickerRight" data-cx-toggle="datepicker" data-cx-placement="right" placeholder="Right">
  </div>
</div>

Button trigger

Use a button instead of an input for use cases like dashboard date filters. Add data-cx-datepicker-display to the text element to preserve icons when the date updates.

HTML
<button type="button" class="button outline secondary" data-cx-toggle="datepicker">
<svg class="icon" aria-hidden="true"><use href="#calendar-solid"></use></svg>
<span data-cx-datepicker-display>Select date</span>
</button>

For date range selection (e.g., dashboard time filters), use data-cx-selection-mode="multiple-ranged". The picker closes once both the start and end dates are selected.

HTML
<button type="button" class="button outline secondary" data-cx-toggle="datepicker" data-cx-selection-mode="multiple-ranged">
  <svg class="icon" aria-hidden="true"><use href="#calendar-solid"></use></svg>
  <span data-cx-datepicker-display>Select dates</span>
</button>

Display the selected date in a separate element with the displayElement option:

JavaScript
import { Datepicker } from '@chassis-ui/css'

const datepicker = new Datepicker(buttonElement, {
  selectionMode: 'multiple-ranged',
  displayElement: '#date-display' // Selector or element
})

Inline calendar

Render the calendar inline (always visible, no popup) with data-cx-inline="true". Use this to embed a calendar directly in the page instead of behind a trigger.

HTML
<div data-cx-toggle="datepicker" data-cx-inline="true"></div>

Inline datepickers support the same selection modes as popover datepickers:

HTML
<div data-cx-toggle="datepicker" data-cx-inline="true" data-cx-selection-mode="multiple-ranged"></div>

Multiple months inline:

HTML
<div data-cx-toggle="datepicker" data-cx-inline="true" data-cx-display-months-count="2"></div>

Bind to form

Include a hidden input inside the container to bind the selection to a form field. The plugin updates its value with the selected date(s) in YYYY-MM-DD format (comma-separated for multiple dates):

HTML
<form>
<div data-cx-toggle="datepicker" data-cx-inline="true">
  <input type="hidden" name="selected_date">
</div>
<button type="submit" class="button solid primary mt-md">Submit</button>
</form>

Custom date formatting

Control how dates are displayed using the dateFormat option. Pass an Intl.DateTimeFormat options object or a function(date, locale).

JavaScript
import { Datepicker } from '@chassis-ui/css'

// Using Intl.DateTimeFormat options
const datepicker = new Datepicker(element, {
  dateFormat: { month: 'short', day: 'numeric', year: 'numeric' }
  // Output: "Dec 23, 2026 – Dec 28, 2026"
})

// Using a custom function
const datepickerCustom = new Datepicker(element, {
  dateFormat: (date, locale) => date.toLocaleDateString(locale, { month: 'short', day: 'numeric' })
  // Output: "Dec 23 – Dec 28"
})

Theming

The datepicker adapts to Chassis's color modes. When data-cx-theme="dark" is set on a parent element or the <html> element, the calendar popup inherits that theme.

Inherited from parent

When a parent element carries a theme, both the input and calendar popup inherit it:

HTML
<div data-cx-theme="dark" class="p-md bg-main p-md rounded">
  <label for="datepickerDark" class="form-label">Dark mode datepicker</label>
  <input type="text" class="form-input" id="datepickerDark" data-cx-toggle="datepicker" placeholder="Select a date">
</div>

Datepicker-only theme

Use data-cx-datepicker-theme to set the datepicker popup’s theme independently of the input — for example, a light input paired with a dark datepicker, or the reverse:

HTML
<label for="datepickerTheme" class="form-label">Light input, dark datepicker</label>
<input data-cx-toggle="datepicker" data-cx-datepicker-theme="dark" type="text" class="form-input" id="datepickerTheme" placeholder="Select a date">

Accessibility

Vanilla Calendar Pro marks calendar day state with ARIA attributes: the selected day carries aria-selected="true", disabled days carry aria-disabled="true", and today's date carries aria-current="date". Chassis styles each of these states directly from that generated markup — see scss/_datepicker.scss.

Chassis doesn't add aria-expanded or aria-haspopup to the trigger element. Add them manually, and keep aria-expanded in sync with the show.cx.datepicker / hide.cx.datepicker events, if the trigger needs to announce popover state to assistive technology.

JavaScript API

The Datepicker plugin wraps Vanilla Calendar Pro and exposes its configuration through Chassis-managed options, data attributes, and events. Chassis JS ships as an ES module — import the Datepicker class:

JavaScript
import { Datepicker } from '@chassis-ui/css'

Triggers

Add data-cx-toggle="datepicker" to any input or button element to initialize it as a datepicker without writing JavaScript.

AttributeDescription
data-cx-toggle="datepicker"Initializes the datepicker on the input (or button) element.
data-cx-inlineWhen true, renders the calendar inline instead of a popup.
HTML
<input type="text" class="form-input" data-cx-toggle="datepicker">

Initialization

For programmatic access — calling methods or listening to events — instantiate each element with the Datepicker class:

JavaScript
const datepickerEl = document.getElementById('myDatepicker')
const datepicker = new Datepicker(datepickerEl, {
  selectionMode: 'single',
  firstWeekday: 1
})

Methods

The plugin exposes instance methods for programmatic control, plus the static lookup methods shared by every Chassis component:

MethodDescription
show()Shows the datepicker calendar
hide()Hides the datepicker calendar
toggle()Toggles the datepicker visibility
getSelectedDates()Returns an array of selected dates in YYYY-MM-DD format
setSelectedDates(dates)Sets the selected dates. Expects an array of YYYY-MM-DD strings
dispose()Destroys the datepicker instance
getInstance(element)Static method to get the datepicker instance from a DOM element
getOrCreateInstance(element)Static method to get or create a datepicker instance

Options

Options are set via data-cx-* attributes or passed to the constructor as an object:

Options are set via data-cx-* attributes or passed to the constructor as an object. Attribute names use the kebab-case form of the option name — data-cx-custom-class, not data-cx-customClass. Attribute values are parsed to their native types:"true" → true, "0" → 0, and valid JSON strings to objects.

Use data-cx-config to pass multiple options as a JSON string:data-cx-config='{"delay":200}'. Individual data-cx-* attributes take precedence over data-cx-config. You can also use JSON values in individual attributes, such as data-cx-delay='{"show":100,"hide":200}'.

When initializing components, Chassis merges configurations from multiple sources in this priority order: default settings, data-cx-config values, individual data-cx-*attributes, and finally any JavaScript object options. Values defined later in this sequence override earlier ones.

NameTypeDefaultDescription
dateMinstring, number, DatenullMinimum selectable date. Format: YYYY-MM-DD
dateMaxstring, number, DatenullMaximum selectable date. Format: YYYY-MM-DD
dateFormatobject, functionnullDate formatting. Pass Intl.DateTimeFormat options or a function(date, locale).
displayElementstring, element, booleannullElement to show formatted date. For buttons, defaults to the button itself. Set to false to disable.
displayMonthsCountnumber1Number of months to display side-by-side in the calendar.
firstWeekdaynumber1First day of week (0 = Sunday, 1 = Monday, etc.)
inlinebooleanfalseRender calendar inline (always visible, no popup).
localestringbrowser language (2-letter)Locale for date formatting (e.g., 'en-US', 'de-DE'). Pass 'default' to use the runtime's default locale formatting.
positionElementstring, elementnullElement to position the calendar relative to. Checks for a .form-adorn ancestor first, then falls back to the input (or button) itself.
selectedDatesarray[]Pre-selected dates in YYYY-MM-DD format
selectionModestring'single'Selection mode: 'single', 'multiple', or 'multiple-ranged'
placementstring'left'Calendar position relative to input: 'left', 'center', 'right', 'auto'
datepickerThemestringnullForce datepicker popup theme: 'light', 'dark', 'auto', or null to inherit from ancestor [data-cx-theme]
vcpOptionsobject{}Pass-through object for any Vanilla Calendar Pro option

Advanced configuration

For settings not directly exposed by Chassis's options, pass any Vanilla Calendar Pro setting through vcpOptions. Chassis-managed options — like firstWeekday, selectionMode, and locale — take precedence over the same key inside vcpOptions, so reserve it for settings without a dedicated Chassis option:

JavaScript
const datepicker = new Datepicker(element, {
  vcpOptions: {
    disableDatesPast: true,                     // Disable past dates
    disableWeekdays: [0, 6],                    // Disable weekends
    disableDates: ['2026-12-25', '2026-12-26'], // Disable specific dates
    selectedHolidays: ['2026-01-01'],           // Highlight holidays
    selectionTimeMode: 24                       // Enable 24-hour time selection
  }
})

See the Vanilla Calendar Pro documentation for all available options.

Events

The plugin fires events around the show/hide lifecycle and date selection:

EventDescription
show.cx.datepickerFires immediately when the show method is called
shown.cx.datepickerFires when the datepicker has been made visible
hide.cx.datepickerFires immediately when the hide method is called
hidden.cx.datepickerFires when the datepicker has been hidden
change.cx.datepickerFires when a date is selected. Event includes dates (array) and event properties
JavaScript
const datepickerEl = document.getElementById('myDatepicker')
datepickerEl.addEventListener('change.cx.datepicker', event => {
  console.log('Selected dates:', event.dates)
})

CSS

The Datepicker component can be customized at both runtime (via custom properties) and compile time (via Sass variables).

Custom properties

The Datepicker component exposes CSS custom properties to control its appearance at runtime.

--cx-padding-x: var(--cx-datepicker-padding-x, 0.5rem);
--cx-padding-y: var(--cx-datepicker-padding-y, 0.5rem);
--cx-gap: var(--cx-datepicker-gap, 0.25rem);
--cx-bg-color: var(--cx-datepicker-bg-color, var(--cx-default-bg-main));
--cx-fg-color: var(--cx-datepicker-fg-color, var(--cx-default-fg-main));
--cx-border-color: var(--cx-datepicker-border-color, var(--cx-default-transparent-color));
--cx-border-width: var(--cx-datepicker-border-width, var(--cx-border-width-md));
--cx-border-radius: var(--cx-datepicker-border-radius, var(--cx-border-radius-lg));
--cx-day-border-radius: var(--cx-datepicker-day-border-radius, var(--cx-border-radius-md));
--cx-box-shadow: var(--cx-datepicker-box-shadow, var(--cx-box-shadow-md));
--cx-font-size: var(--cx-datepicker-font-size, var(--cx-font-size-sm));
--cx-today-font-weight: var(--cx-datepicker-today-font-weight, var(--cx-font-weight-strong));
--cx-day-width: var(--cx-datepicker-day-width, 2rem);
--cx-week-width: var(--cx-datepicker-week-width, 14rem);
--cx-zindex: var(--cx-datepicker-zindex, 1000);
--cx-header-font-size: var(--cx-datepicker-header-header-font-size, var(--cx-font-size-sm));
--cx-header-font-weight: var(--cx-datepicker-header-header-font-weight, var(--cx-font-weight-strong));
--cx-arrow-icon: var(--cx-datepicker-arrow-icon, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 24 24'%3e%3cpath d='M12 16c-.3 0-.5-.1-.7-.3l-6-6c-.4-.4-.4-1 0-1.4s1-.4 1.4 0l5.3 5.3 5.3-5.3c.4-.4 1-.4 1.4 0s.4 1 0 1.4l-6 6c-.2.2-.4.3-.7.3'/%3e%3c/svg%3e"));
--cx-weekday-fg-color: var(--cx-datepicker-weekday-fg-color, var(--cx-default-fg-main));
--cx-weekday-font-size: var(--cx-datepicker-weekday-font-size, var(--cx-font-size-text-xs));
--cx-day-hover-bg-color: var(--cx-datepicker-day-hover-bg-color, var(--cx-default-bg-even));
--cx-day-selected-bg-color: var(--cx-datepicker-day-selected-bg-color, var(--cx-primary-base-color));
--cx-day-selected-fg-color: var(--cx-datepicker-day-selected-fg-color, var(--cx-primary-contrast-color));
--cx-day-range-bg-color: var(--cx-datepicker-day-range-bg-color, var(--cx-default-bg-highlight));
--cx-day-range-fg-color: var(--cx-datepicker-day-range-fg-color, var(--cx-default-fg-idle));
--cx-day-today-bg-color: var(--cx-datepicker-day-today-bg-color, var(--cx-default-transparent-color));
--cx-day-today-fg-color: var(--cx-datepicker-day-today-fg-color, var(--cx-default-cue-main));
--cx-day-weekend-bg-color: var(--cx-datepicker-day-weekend-bg-color, var(--cx-default-transparent-color));
--cx-day-weekend-fg-color: var(--cx-datepicker-day-weekend-fg-color, var(--cx-default-fg-subtle));
--cx-day-disabled-fg-color: var(--cx-datepicker-day-disabled-fg-color, var(--cx-default-fg-slight));

Sass variables

The Datepicker component uses Sass variables in scss/config/_defaults.scss to define its defaults; they are also exposed as CSS custom properties using the --cx- prefix for runtime override — $variable-name becomes --cx-variable-name. See the component anatomy page.

$datepicker-arrow-icon:               "<svg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 24 24'><path d='M12 16c-.3 0-.5-.1-.7-.3l-6-6c-.4-.4-.4-1 0-1.4s1-.4 1.4 0l5.3 5.3 5.3-5.3c.4-.4 1-.4 1.4 0s.4 1 0 1.4l-6 6c-.2.2-.4.3-.7.3'/></svg>";
$datepicker-weekday-font-size:        $font-size-xs;

Design tokens

The Datepicker component consumes design tokens from Chassis Tokens with the $cx- prefix. — See thedesign tokens page.

$datepicker-fg-idle:            $cx-color-datepicker-fg-idle;
// $datepicker-bg-idle:            $cx-color-datepicker-bg-idle;
// $datepicker-fg-hover:           $cx-color-datepicker-fg-hover;
$datepicker-bg-hover:           $cx-color-datepicker-bg-hover;
// $datepicker-fg-press:           $cx-color-datepicker-fg-press;
// $datepicker-bg-press:           $cx-color-datepicker-bg-press;
$datepicker-fg-active:          $cx-color-datepicker-fg-active;
$datepicker-bg-active:          $cx-color-datepicker-bg-active;
$datepicker-fg-range:           $cx-color-datepicker-fg-range;
$datepicker-bg-range:           $cx-color-datepicker-bg-range;
$datepicker-fg-today:           $cx-color-datepicker-fg-today;
$datepicker-bg-today:           $cx-color-datepicker-bg-today;
$datepicker-fg-weekend:         $cx-color-datepicker-fg-weekend;
$datepicker-bg-weekend:         $cx-color-datepicker-bg-weekend;
$datepicker-fg-outside:         $cx-color-datepicker-fg-outside;
// $datepicker-bg-outside:         $cx-color-datepicker-bg-outside;
$datepicker-fg-main:            $cx-color-datepicker-fg-main;
$datepicker-bg-main:            $cx-color-datepicker-bg-main;
$datepicker-border-main:        $cx-color-datepicker-border-main;
// $datepicker-border-subtle:      $cx-color-datepicker-border-subtle;
$datepicker-border-radius-main: $cx-border-radius-datepicker-main;
$datepicker-border-radius-day:  $cx-border-radius-datepicker-day;
// $datepicker-border-radius-menu: $cx-border-radius-datepicker-menu;
$datepicker-font-day:           $cx-font-datepicker-day;
$datepicker-font-today:         $cx-font-datepicker-today;
$datepicker-font-label:         $cx-font-datepicker-label;
$datepicker-border-width-main:  $cx-border-width-datepicker-main;
$datepicker-shadow-main:        $cx-shadow-datepicker-main;
$datepicker-day-width:          $cx-size-datepicker-day-width;
// $datepicker-day-height:         $cx-size-datepicker-day-height;
$datepicker-week-width:         $cx-size-datepicker-week-width;
// $datepicker-menu-width:         $cx-size-datepicker-menu-width;
$datepicker-padding-y:          $cx-space-datepicker-padding-y;
$datepicker-padding-x:          $cx-space-datepicker-padding-x;
$datepicker-gap:                $cx-space-datepicker-gap;