Bootstrap 6 Date Picker

Date Picker

CoreUI PRO
This component is part of CoreUI PRO – a powerful UI library with over 250 components and 25+ templates, designed to help you build modern, responsive apps faster. Fully compatible with Angular, Bootstrap, React.js, and Vue.js.

Create a date field with a calendar popup. Users type the date into a section-based mask or pick it from the calendar, and the two stay in sync.

Example

Below is an example of a basic Date Picker.

Days

The input is a section-based mask — type the date directly, adjust a section with the arrow keys, or pick a day from the calendar. There is no free-text parsing and no debounce: the field always holds a valid partial date.

html
<div class="row">
  <div class="col-sm-6 col-lg-5 mb-4 mb-sm-0">
    <div data-coreui-locale="en-US" data-coreui-date-picker></div>
  </div>
  <div class="col-sm-6 col-lg-5">
    <div data-coreui-date="2026/03/15" data-coreui-locale="en-US" data-coreui-date-picker></div>
  </div>
</div>

There is no footer option. Project a <template data-coreui-template="footer"> child instead — its content is cloned into the dropdown. Buttons act through data-coreui-picker-action (today, clear, reset, close), so labels, order, and translations are yours. A button opting into the today action opts into its state too: it is disabled at initialization when today falls outside minDate/maxDate/disabledDates. This is one-way — the picker only ever sets disabled, so a button you disable in the template stays disabled.

html
<div class="row">
  <div class="col-lg-5">
    <div data-coreui-locale="en-US" data-coreui-date-picker>
      <template data-coreui-template="footer">
        <button type="button" class="btn-ghost theme-danger btn-sm me-auto" data-coreui-picker-action="today">Today</button>
        <button type="button" class="btn-ghost theme-primary btn-sm" data-coreui-picker-action="clear">Clear</button>
        <button type="button" class="btn-solid theme-primary btn-sm" data-coreui-picker-action="close">OK</button>
      </template>
    </div>
  </div>
</div>

Here maxDate is the end of last month, so the today button disables itself. The state of any other button is yours — the slot context exposes isDateSelectable(date), which answers whether a date, expressed in the field’s mask, passes the minDate/maxDate/disabledDates validation (a day mask checks the calendar day, so new Date() passes with a maxDate of today, and a month, quarter or year mask checks the whole period) — and every programmatic date still runs through the field’s validation: out of range, the field turns is-invalid, errorChange reports the reason, and getDate() reports null, the same outcome as typing that date. setDate() fires no dateChange; an action such as today fires it with null.

html
<div class="row">
  <div class="col-lg-5">
    <div id="myDatePickerFooterToday">
      <template data-coreui-template="footer">
        <button type="button" class="btn-ghost theme-danger btn-sm me-auto" data-coreui-picker-action="today">Today</button>
        <button type="button" class="btn-solid theme-primary btn-sm" data-coreui-picker-action="close">OK</button>
      </template>
    </div>
  </div>
</div>
js
const myDatePickerFooterToday = document.getElementById('myDatePickerFooterToday')

const datePickerFooterToday = new coreui.DatePicker(myDatePickerFooterToday, {
  locale: 'en-US',
  maxDate: new Date(new Date().getFullYear(), new Date().getMonth(), 0)
})

Weeks

Add data-coreui-selection-type="week" to select week numbers, and data-coreui-show-week-number="true" to show them in the grid. The field renders the native <input type="week"> presentation — a fixed, non-editable localized label followed by the week and year sections (Week 29, 2026, Tydzień 29, 2026, …). The year is the ISO week-numbering year, which can differ from the calendar year around January 1st, and the reported Date is the Monday of the selected week. Prefer the ISO wire format in the field? Set data-coreui-format="yyyy-Www".

html
<div class="row">
  <div class="col-lg-5">
    <div
      data-coreui-locale="en-US"
      data-coreui-selection-type="week"
      data-coreui-show-week-number="true"
      data-coreui-date-picker></div>
  </div>
</div>

Months

Add data-coreui-selection-type="month" to select months. The field mask narrows to month and year automatically.

html
<div class="row">
  <div class="col-lg-5">
    <div
      data-coreui-locale="en-US"
      data-coreui-selection-type="month"
      data-coreui-date-picker></div>
  </div>
</div>

Quarters

Add data-coreui-selection-type="quarter" to select quarters — the equivalent of the <input type="quarter"> that HTML never shipped. The field mask narrows to a quarter and year section (Q4 2026), and the reported Date is the first day of the selected quarter.

html
<div class="row">
  <div class="col-lg-5">
    <div
      data-coreui-locale="en-US"
      data-coreui-selection-type="quarter"
      data-coreui-date-picker></div>
  </div>
</div>

Years

Add data-coreui-selection-type="year" to select years. The field mask narrows to the year automatically.

html
<div class="row">
  <div class="col-lg-5">
    <div
      data-coreui-locale="en-US"
      data-coreui-selection-type="year"
      data-coreui-date-picker></div>
  </div>
</div>

With time

Set timepicker to add the time half. The field switches to a date and time mask, the panel grows a row of time selects under the calendar, and picking a day no longer closes the panel — the two halves own different parts of one value, so the interaction is not over until the user says so. While no time is picked, or the field holds a time a list does not offer, each select stands on a -- placeholder, so every enabled option, the first allowed one included, sets the time.

html
<div class="row">
  <div class="col-lg-5">
    <div data-coreui-locale="en-US" data-coreui-timepicker="true" data-coreui-date-picker></div>
  </div>
</div>

hours, minutes and seconds narrow what the time half offers, and are ignored without timepicker. Hours are 0 to 23 in every locale; on a 12-hour clock an hour that only one period allows sets that period. The four ariaSelect*Label options name its selects, and with the flag on the field actions call themselves “Clear date and time” and “Toggle calendar and time selection” unless the page passed its own label.

minDate and maxDate can carry a time of day. Their day stays selectable in the calendar and the time selects disable the hours, minutes and seconds before minDate or after maxDate on that day. A time the panel sets beyond a limit, such as the current time when the day of minDate is picked, moves to the limit; a typed time beyond it marks the field invalid. Until a date is picked, the time selects work on today moved into the limits. A limit without a time of day covers its whole day.

html
<div class="row">
  <div class="col-lg-5">
    <div data-coreui-locale="en-US" data-coreui-timepicker="true" data-coreui-min-date="2026-10-15T09:30" data-coreui-max-date="2026-10-30T17:00" data-coreui-date-picker></div>
  </div>
</div>

With form field

Wrap the control in .form-field to lay it out with its label and description, and to get validation feedback in the right place.

Delivery date
We deliver on working days only.
html
<div class="form-field">
  <span class="form-label" id="datePickerDeliveryDateLabel">Delivery date</span>
  <div data-coreui-locale="en-US" data-coreui-date-picker aria-labelledby="datePickerDeliveryDateLabel"></div>
  <div class="form-text">We deliver on working days only.</div>
</div>

Composing the markup

The picker is built from other components — the date input field, the calendar and a popup — held together by the date it owns. The markup is where you choose the parts: mark an element with a role attribute and the picker adopts it instead of building its own; leave a part out and it is generated as in the examples above.

RoleElementWhat the picker does with it
data-coreui-picker-fielda divHosts the date input’s sections. Without it the field is generated (and wrapped in .form-floating when floatingLabel is set).
data-coreui-picker-togglea button or any elementOpens and closes the calendar. Gets aria-expanded and aria-haspopup, and aria-controls while the calendar is open, and ariaPickerLabel as its name unless it has one or shows text of its own.
data-coreui-picker-cleanera buttonClears the value. Gets ariaCleanerLabel as its name unless it has one or shows text of its own.

A decorative <svg> inside an adopted button is marked aria-hidden for you. So a trigger can be a regular button with its own text and icon, the way a combobox has one:

html
<div data-coreui-locale="en-US" data-coreui-date-picker>
  <div data-coreui-picker-field></div>
  <button type="button" class="btn btn-subtle btn-sm" data-coreui-picker-cleaner>
    <svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 3l10 10M13 3L3 13"/></svg>
  </button>
  <button type="button" class="btn btn-subtle btn-sm" data-coreui-picker-toggle>
    <svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1.5" y="2.5" width="13" height="12" rx="1.5"/><path d="M1.5 6.5h13M5 1v3M11 1v3"/></svg>
    Pick a date
  </button>
</div>

The cleanerIcon and pickerIcon options only feed the generated buttons; with your own markup the icon is whatever you put in the HTML, and nothing is sanitized because nothing is rendered from a string.

Floating labels

The date picker supports floating labels: set floatingLabel and the component renders the label itself — no wrapper markup needed — and the text also becomes the field’s accessible name. The mask placeholders stay hidden while the label rests inside the field; focusing any part of the field — the sections or the picker button — floats the label and reveals the mask.

html
<div data-coreui-locale="en-US" data-coreui-floating-label="Delivery date" data-coreui-date-picker></div>

Add .form-floating-always to the component element to keep the label always floating with the mask showing.

Sizing

Set heights using the data-coreui-size attribute like data-coreui-size="lg" and data-coreui-size="sm".

html
<div class="row mb-4">
  <div class="col-lg-5">
    <div data-coreui-locale="en-US" data-coreui-size="lg" data-coreui-date-picker></div>
  </div>
</div>
<div class="row">
  <div class="col-lg-5">
    <div data-coreui-locale="en-US" data-coreui-size="sm" data-coreui-date-picker></div>
  </div>
</div>

Disabled

Add the data-coreui-disabled="true" attribute to give the picker a grayed out appearance and remove pointer events.

html
<div class="row">
  <div class="col-lg-5">
    <div data-coreui-disabled="true" data-coreui-locale="en-US" data-coreui-date-picker></div>
  </div>
</div>

Disabled dates

The disabledDates option excludes specific dates — single Dates, [start, end] ranges, or a matcher function. The calendar grays them out and the field refuses them: a completed date that hits a disabled entry gets the .is-invalid class and the picker reports null, and submits no date, until it is corrected. In week, month, quarter and year selection the calendar and the field judge the whole period: it is refused only when it lies wholly before minDate or after maxDate, or disabledDates covers every day of it that the limits leave. In week selection the limits count as whole weeks, so for a week that is all seven days.

html
<div class="row">
  <div class="col-lg-5">
    <div id="myDatePickerDisabledDates"></div>
  </div>
</div>
js
const myDatePickerDisabledDates = document.getElementById('myDatePickerDisabledDates')

const datePickerDisabledDates = new coreui.DatePicker(myDatePickerDisabledDates, {
  locale: 'en-US',
  date: new Date(2026, 6, 10),
  disabledDates: [
    [new Date(2026, 6, 4), new Date(2026, 6, 7)],
    new Date(2026, 6, 16)
  ],
  minDate: new Date(2026, 6, 1),
  maxDate: new Date(2026, 6, 31)
})

myDatePickerDisabledDates.addEventListener('dateChange.coreui.date-picker', event => {
  console.log('dateChange', event.date)
})

Disabling weekends

Pass a function to disable every date that matches a rule.

html
<div class="row">
  <div class="col-lg-5">
    <div id="myDatePickerWeekends"></div>
  </div>
</div>
js
const myDatePickerWeekends = document.getElementById('myDatePickerWeekends')

const datePickerWeekends = new coreui.DatePicker(myDatePickerWeekends, {
  locale: 'en-US',
  disabledDates: date => date.getDay() === 0 || date.getDay() === 6
})

Validation

The picker shows its validation state on the frame and on the sections of its field, which the message after the picker describes, the way the form plugin does for a native field. The field keeps its value in a <textarea> laid invisibly over it, so a required picker that is empty, incomplete or holds a rejected date blocks the submit, as the date input does.

You give a state with the validationState option ('valid', 'invalid' or a state of $form-validation-states, with invalid and valid as deprecated aliases) or with an .is-invalid or .is-valid class written on the picker element, the server-side route, which is read once, when the picker starts; the option wins over the class. A given state is a verdict on the date it was given for, so the next change of the date the user makes drops it: a day picked in the calendar, a time picked with timepicker, the cleaner, a footer button that changes the date through the context, or typing in the field. An action that leaves the date as it is keeps the state. A change your own code makes, with setDate(), clear(), reset() or today(), keeps it. setValidationState() sets it again without rebuilding the picker. A class or an aria-invalid written on the picker element after it starts is not read. While the given state is 'invalid', the picker blocks the submit with the text of the message after it, or Invalid value. when there is none. A native form reset puts back the date the picker started with and drops the state.

We have no delivery slots left on this date.
html
<div class="row">
  <div class="col-lg-5 form-field">
    <div class="is-invalid" data-coreui-date-picker data-coreui-locale="en-US" data-coreui-date="2026-07-14"></div>
    <div class="invalid-feedback">We have no delivery slots left on this date.</div>
  </div>
</div>

Locale

Auto

The locale drives both layers: the section order and separators of the field, and the month/weekday names of the calendar. By default it comes from the browser.

html
<div class="row">
  <div class="col-lg-5">
    <div data-coreui-date-picker></div>
  </div>
</div>

Chinese

html
<div class="row">
  <div class="col-lg-5">
    <div data-coreui-locale="zh-CN" data-coreui-date-picker></div>
  </div>
</div>

Japanese

html
<div class="row">
  <div class="col-lg-5">
    <div data-coreui-locale="ja" data-coreui-date-picker></div>
  </div>
</div>

Korean

html
<div class="row">
  <div class="col-lg-5">
    <div data-coreui-locale="ko" data-coreui-date-picker></div>
  </div>
</div>

Right to left support

RTL support is built-in and can be explicitly controlled through the $enable-rtl variable in Sass. The popup flips its anchoring side with the document direction.

Hebrew

html
<div class="row" dir="rtl">
  <div class="col-lg-5">
    <div data-coreui-locale="he-IL" data-coreui-date-picker></div>
  </div>
</div>

Persian

The date picker is Gregorian: with a locale whose own calendar has different months, like Persian, its calendar shows the Gregorian months in that locale’s language and digits, and its field holds Gregorian numbers.

html
<div class="row" dir="rtl">
  <div class="col-lg-5">
    <div data-coreui-locale="fa-IR" data-coreui-date-picker></div>
  </div>
</div>

Custom formats

The mask of the field defines the format. Set it with data-coreui-format (or the format option), which the field forwards from the shell. The parser accepts both dayjs/moment-style tokens (DD, MM, YYYY, YY) and date-fns/Unicode-style tokens (dd, MM, yyyy, yy), plus ww for ISO week numbers and q (numeric) or QQQ (Q1–Q4) for quarters; any other character becomes a literal separator.

html
<div class="row">
  <div class="col-sm-6 col-lg-5 mb-4 mb-sm-0">
    <label class="form-label" id="datePickerDdMMYyyyLabel">dd.MM.yyyy</label>
    <div data-coreui-format="dd.MM.yyyy" data-coreui-locale="en-US" data-coreui-date-picker aria-labelledby="datePickerDdMMYyyyLabel"></div>
  </div>
  <div class="col-sm-6 col-lg-5">
    <label class="form-label" id="datePickerYYYYMMDDLabel">YYYY-MM-DD</label>
    <div data-coreui-format="YYYY-MM-DD" data-coreui-locale="en-US" data-coreui-date-picker aria-labelledby="datePickerYYYYMMDDLabel"></div>
  </div>
</div>

A format does not have to include every section — a month-and-year mask reports the first day of the month.

html
<div class="row">
  <div class="col-lg-5">
    <div data-coreui-format="MMMM yyyy" data-coreui-locale="en-US" data-coreui-date-picker></div>
  </div>
</div>

Accessibility

The field is a date input — a group of spinbutton sections — so its own arrow keys belong to the value, and the popup opens on the native control’s dropdown keys. Focus enters the calendar on the selected date, else on today or on the minDate / maxDate today falls outside of, also when that date cannot be picked: it is announced as unavailable and the arrows move on from it. In week selection it enters on the row of that week.

Clicking a label that names the field, through aria-labelledby or a <label for> pointing at the element, moves focus to the first empty section of the field.

An aria-label, aria-labelledby or aria-describedby on the picker element moves to the field, so a screen reader announces the name and the description when focus enters it, and the picker element gets them back on dispose. Point aria-labelledby at the visible label, as the examples on this page do; it names the field over floatingLabel and an ariaLabel in inputOptions, the way the browser ranks names. A data-coreui-picker-field you wrote that already has an aria-label or aria-labelledby keeps its own name, and its own aria-describedby comes first, followed by the one that moves.

While the picker shows an invalid state, the sections of its field carry aria-invalid="true" and are described by the error message after the picker, see Validation. An aria-invalid written on the picker element is not read; give the state with validationState or with the class.

The toggle’s name ends with the date the picker holds, written in full (Toggle calendar, Friday, August 14, 2026, with the time after it with timepicker), so a screen reader reads the picked date when the popup closes and focus goes back to the toggle. A live message would be lost at that moment, so the calendar does not announce a pick that closes the popup onto that toggle. With timepicker the popup stays open, and the calendar announces the pick through ariaSelectedDateAnnouncement; it does the same without a toggle (pickerIcon: false) and with a data-coreui-picker-toggle that has a name or text of its own, which keeps it unchanged.

Keyboard support

KeyAction
Alt (Option on macOS) + Arrow Down / F4Open the calendar popup and move focus into the grid
EscapeClose the popup and return focus to the field or the toggle that opened it
Tab / Shift + TabMove within the open calendar; the field is outside the dialog and stays out of the cycle
Arrow Right / Arrow LeftMove focus to the next / previous day; crossing the edge of the grid moves to the adjacent month
Arrow Down / Arrow UpMove focus to the same day of the next / previous week
Home / EndMove focus to the first / last day of the week inside minDate / maxDate, into the previous / next month when the week starts or ends there
Page Down / Page UpMove focus to the same day of the next / previous month, keeping the day number when the target month is long enough; a series of presses keeps the day it started from, so January 31 goes to February 28 and then March 31, also across a stop at minDate / maxDate; any other key or a click starts a new series
Shift + Page Down / Shift + Page UpMove focus to the same day of the next / previous year, keeping the day of a series the same way
Enter / SpaceSelect the focused date, and close the popup unless timepicker is on, where the time half still has to be set; nothing happens on a date that cannot be picked

In the calendar grid, a key from the table pressed with Alt, Ctrl or Cmd is neither handled nor cancelled: focus stays where it is and the key stays free for the browser and assistive technology.

Usage

Via data attributes

Add data-coreui-date-picker to a div element.

<div data-coreui-date-picker></div>

Inside it, data-coreui-picker-field, data-coreui-picker-toggle and data-coreui-picker-cleaner name the parts you supply yourself — see Composing the markup.

Via JavaScript

<div class="my-date-picker"></div>
const datePickerElementList = Array.prototype.slice.call(document.querySelectorAll('.my-date-picker'))
const datePickerList = datePickerElementList.map(datePickerEl => {
  return new coreui.DatePicker(datePickerEl)
})

Options

Options can be passed via data attributes or JavaScript. For data attributes, append the option name to data-coreui-, converting “camelCase” to “kebab-case” — write data-coreui-selection-type="week", not data-coreui-selectionType="week".

Options belonging to the inner calendar, date input or, with timepicker, the time columns of the time picker are forwarded by name, so their data attributes work on the picker element too. The calendar’s startDate and the time columns’ hourCycle, onChange and time are set by the picker itself.

PropertyTypeDefault
allowListobjectSVGAllowlist

Allowlist used when sanitizing the icon markup.

ariaCleanerLabelstring'Clear date'

Accessible label of the cleaner button. 'Clear date and time' with timepicker while the default is kept.

ariaLabelstring'Date input'

Accessible name of the field. It lands on the role="group" container the sections sit in, so it names the whole control rather than one part of it.

ariaPickerLabelstring'Toggle calendar'

Accessible label of the picker button, followed by the date the picker holds. 'Toggle calendar and time selection' with timepicker while the default is kept.

ariaPopupLabelstring'Calendar'

Accessible name of the calendar panel, which opens as a role="dialog". 'Calendar and time selection' with timepicker while the default is kept.

calendarOptionsobject{}

Programmatic escape hatch for calendar options.

cleanerbooleantrue

Renders a button that clears the value. It appears only once the field holds one.

cleanerIconstringcross SVG

Inline SVG of the generated cleaner (sanitized). Not used for a data-coreui-picker-cleaner you wrote.

containerstring, element, falsefalse

Teleport target for the dropdown. Inside a modal dialog it goes into the dialog instead, because the rest of the page is inert.

datedate, string, nullnull

Initial value.

disabledbooleanfalse

Disables the field and the popup.

floatingLabelstring, nullnull

Renders a floating label over the field, which also becomes its accessible name. Not rendered for a data-coreui-picker-field you wrote, which only takes the name, and not even that when the field carries an aria-label of its own. An aria-label or aria-labelledby on the picker element names the field instead.

formatfunction, string, nullnull

Mask layout of the field: null derives it from the locale (or, for week, month, quarter and year selection, uses the matching default mask), a string is parsed as format tokens, a function receives the locale and returns the section layout.

inputOptionsobject{}

Programmatic escape hatch for date input options, except validationState, which the picker owns.

invalidbooleanfalse

Deprecated alias of validationState: 'invalid', ignored once validationState is set.

localestringbrowser locale

Drives section order, separators, and calendar labels.

maxDatedate, string, nullnull

Upper bound, enforced by both layers. With timepicker, a time of day bounds the time on that day, and a date without one covers its whole day.

minDatedate, string, nullnull

Lower bound, enforced by both layers. With timepicker, a time of day bounds the time on that day.

monthNamesarray, nullnull

Twelve custom month names for text month sections (MMM/MMMM).

namestring, nullnull

Name of the generated value input. Without it the field is not submitted with the form.

pickerIconstring, booleantrue

The button that opens the calendar: true for the built-in icon, an inline SVG string for your own (sanitized), false for no button. Not used for a data-coreui-picker-toggle you wrote. Without either the popup has no pointer opener and nothing reports the expanded state, though the field keeps the keyboard openers below.

sanitizebooleantrue

Sanitize the icon markup.

sanitizeFnfunction, nullnull

Custom sanitizer.

secondsarray, boolean, functiontrue

Seconds offered by the time half. Ignored without timepicker.

selectionOptionsobject{}

Programmatic escape hatch for the time half, except onChange, time and, when the field shows hours, hourCycle, which the picker owns.

selectionTypestring'day'

What the picker selects: 'day', 'week', 'month', 'quarter' or 'year'. The calendar and the field mask follow it.

sizestring, nullnull

'sm' or 'lg'.

timepickerbooleanfalse

Add a time half to the panel and switch the field to a date and time mask.

validbooleanfalse

Deprecated alias of validationState: 'valid', ignored once validationState is set.

validationStatestring, nullnull

The validation state the picker shows: 'valid', 'invalid' or a state of $form-validation-states. 'invalid' also blocks the submit. The next change the user makes clears it, see Validation.

Methods

MethodDescription
clearClears the value.
disposeDestroys the picker and its inner components.
getContextReturns the slot context (see below).
getDateReturns the current value (Date or null).
getInstanceStatic — returns the instance for a DOM element.
getOrCreateInstanceStatic — returns the instance, creating it if needed.
hideCloses the popup.
resetRestores the initial value.
setValidationState(state)Gives the picker a validation state ('valid', 'invalid' or a state added to $form-validation-states), or drops it with null. The value stays. See Validation.
setDateSets the value without firing dateChange. The open calendar, the time selection and the name of the toggle follow it.
showOpens the popup.
todaySets today’s date.
toggleToggles the popup.

Slot context

Projected regions (and any custom code) drive the picker through a small imperative contract instead of configuration:

const picker = coreui.DatePicker.getOrCreateInstance(element)
const context = picker.getContext()

context.today()          // set today's date
context.setDate(date)    // set any date
context.clear()          // clear the value
context.reset()          // restore the initial value
context.close()          // close the popup
context.date             // current value (Date | null)
context.disabled         // disabled state

Events

EventDescription
dateChange.coreui.date-pickerFired when the value changes — from the calendar, the field, or a context action. event.date is the validated Date, or null; event.formattedDate is the same value in the shape of selectionType (2026-01, 2026W03, 2026Q1, 2026, or the Date itself for day), with the year in at least four digits (0026-01).
hidden.coreui.date-pickerFired once the popup has finished its exit transition and left the page. Skipped when the popup reopens first.
hide.coreui.date-pickerFired immediately when hide is called — by the API, the indicator, Escape or an outside click. Call event.preventDefault() to keep the popup open.
show.coreui.date-pickerFired immediately when show is called. Call event.preventDefault() to keep the popup closed.
shown.coreui.date-pickerFired once the popup has finished its entry transition. Skipped when the popup closes first.

The field’s own events (dateChange.coreui.date-input, errorChange.coreui.date-input) still bubble from the inner element — the shell does not duplicate them. dateChange.coreui.date-picker fires before the field’s errorChange for the same change, whether it comes from the calendar, the field or an action, so a date an errorChange listener sets replaces the one just reported.

const myDatePicker = document.getElementById('myDatePicker')
myDatePicker.addEventListener('dateChange.coreui.date-picker', event => {
  console.log(event.date)
})

Customizing

CSS variables

The field is a .form-control-group on the picker element itself, so its frame, the cleaner and the toggle read the --cui-control-* and --cui-control-group-* tokens. Set them on the picker element to restyle one field.

The picker declares its local CSS variables on its panel, .date-picker-popup, for real-time customization. Values are set via Sass, so Sass customization is supported too.

scss
$date-picker-tokens: defaults(
  (
    --cui-date-picker-ranges-padding-y: .5rem,
    --cui-date-picker-ranges-padding-x: .5rem,
    --cui-date-picker-ranges-gap: .25rem,
    --cui-date-picker-ranges-border-width: var(--cui-border-width),
    --cui-date-picker-ranges-border-color: var(--cui-border-color),
    --cui-date-picker-timepickers-border-width: var(--cui-border-width),
    --cui-date-picker-timepickers-border-color: var(--cui-border-color),
    --cui-date-picker-footer-padding-y: .5rem,
    --cui-date-picker-footer-padding-x: .5rem,
    --cui-date-picker-footer-gap: .5rem,
    --cui-date-picker-footer-border-width: var(--cui-border-width),
    --cui-date-picker-footer-border-color: var(--cui-border-color),
  ),
  $date-picker-tokens
);

The time half declares its own on .date-picker-time-body, so it can be restyled without touching the panel around it.

scss
$date-picker-time-tokens: defaults(
  (
    --cui-date-picker-time-body-padding-y: var(--cui-spacer-3),
    --cui-date-picker-time-body-padding-x: var(--cui-spacer-3),
    --cui-date-picker-time-icon-width: 2.5rem,
    --cui-date-picker-time-icon-size: 1rem,
    --cui-date-picker-time-icon-color: var(--cui-fg-3),
    --cui-date-picker-time-select-border-radius: var(--cui-radius-4),
    --cui-date-picker-time-select-font-size: var(--cui-btn-input-sm-font-size),
    --cui-date-picker-time-select-color: var(--cui-btn-input-fg),
    --cui-date-picker-time-select-padding-y: var(--cui-btn-input-sm-padding-y),
    --cui-date-picker-time-select-padding-x: var(--cui-btn-input-sm-padding-x),
    --cui-date-picker-time-select-disabled-color: var(--cui-fg-body),
  ),
  $date-picker-time-tokens
);

The panel is a .popup: its surface, border, shadow, stacking and fade come from the tokens every popup panel shares with the other pickers, Combobox, Autocomplete and Multi Select. .popup declares them itself, so set them on .date-picker-popup to restyle this panel or on .popup for all of them; a value set on an ancestor or on :root does not reach it.

scss
$popup-tokens: () !default;
$popup-tokens: defaults(
  (
    --cui-popup-zindex: var(--cui-z-dropdown),
    --cui-popup-bg: var(--cui-bg-body),
    --cui-popup-border-width: var(--cui-border-width),
    --cui-popup-border-color: var(--cui-border-color),
    --cui-popup-border-radius: var(--cui-radius-7),
    --cui-popup-box-shadow: var(--cui-box-shadow),
    --cui-popup-transition-duration: .15s,
    --cui-popup-transition-timing: var(--cui-transition-timing-overlay),
  ),
  $popup-tokens
);
scss
$calendar-tokens: defaults(
  (
    --cui-calendar-table-margin: .5rem,
    --cui-calendar-table-cell-size: 2.75rem,
    --cui-calendar-nav-padding-y: .5rem,
    --cui-calendar-nav-padding-x: .5rem,
    --cui-calendar-nav-border-color: var(--cui-border-color),
    --cui-calendar-nav-border: var(--cui-border-width) solid var(--cui-calendar-nav-border-color),
    --cui-calendar-nav-btn-padding-x: .5rem,
    --cui-calendar-nav-btn-padding-y: .25rem,
    --cui-calendar-nav-btn-font-size: var(--cui-font-size-sm),
    --cui-calendar-nav-btn-bg: transparent,
    --cui-calendar-nav-btn-border-width: 1px,
    --cui-calendar-nav-btn-border-color: transparent,
    --cui-calendar-nav-btn-border-radius: var(--cui-radius-4),
    --cui-calendar-nav-btn-hover-bg: transparent,
    --cui-calendar-nav-btn-hover-border-color: transparent,
    --cui-calendar-nav-btn-focus-border-color: transparent,
    --cui-calendar-nav-date-font-weight: 600,
    --cui-calendar-nav-date-color: var(--cui-fg-body),
    --cui-calendar-nav-date-hover-color: var(--cui-primary-base),
    --cui-calendar-nav-icon-width: 1rem,
    --cui-calendar-nav-icon-height: 1rem,
    --cui-calendar-nav-icon-color: var(--cui-fg-3),
    --cui-calendar-nav-icon-hover-color: var(--cui-fg-body),
    --cui-calendar-cell-header-inner-font-weight: 600,
    --cui-calendar-cell-header-inner-color: var(--cui-fg-3),
    --cui-calendar-cell-week-number-font-weight: 600,
    --cui-calendar-cell-week-number-color: var(--cui-fg-2),
    --cui-calendar-cell-border-radius: var(--cui-radius-4),
    --cui-calendar-cell-hover-color: var(--cui-fg-body),
    --cui-calendar-cell-hover-bg: var(--cui-bg-2),
    --cui-calendar-cell-disabled-color: var(--cui-fg-4),
    --cui-calendar-cell-selected-color: var(--cui-primary-contrast),
    --cui-calendar-cell-selected-bg: var(--cui-primary-base),
    --cui-calendar-cell-selected-hover-color: var(--cui-calendar-cell-selected-color),
    --cui-calendar-cell-selected-hover-bg: oklch(from var(--cui-calendar-cell-selected-bg) calc(l * var(--cui-theme-hover-lightness)) calc(c * var(--cui-theme-hover-chroma)) h),
    --cui-calendar-cell-selected-adjacent-color: var(--cui-primary-fg-emphasis),
    --cui-calendar-cell-selected-adjacent-bg: var(--cui-primary-bg-subtle),
    --cui-calendar-cell-selected-adjacent-hover-color: var(--cui-calendar-cell-selected-adjacent-color),
    --cui-calendar-cell-selected-adjacent-hover-bg: var(--cui-primary-bg-muted),
    --cui-calendar-cell-range-bg: var(--cui-primary-bg-subtle),
    --cui-calendar-cell-range-hover-border-color: var(--cui-primary-base),
    --cui-calendar-cell-today-color: var(--cui-danger-base),
  ),
  $calendar-tokens
);