Create a time field with a popup. Users type the time into a section-based mask or pick it from the rolls, and the two stay in sync.
Example
Type into the masked field or pick from the roll. The two halves share one value, so typing and picking stay in sync.
<div class="row">
<div class="col-sm-6 col-lg-4 mb-4 mb-sm-0">
<div data-coreui-locale="en-US" data-coreui-time-picker></div>
</div>
<div class="col-sm-6 col-lg-4">
<div data-coreui-locale="en-US" data-coreui-time="14:30:00" data-coreui-time-picker></div>
</div>
</div>Theme variants
A .theme-* class on the picker colours the selected cell of every roll.
<div class="row">
<div class="col-lg-4">
<div class="theme-success" data-coreui-locale="en-US" data-coreui-time="14:30:00" data-coreui-time-picker></div>
</div>
</div>With footer
There is no footer option — project a <template data-coreui-template="footer">
child. Buttons act through data-coreui-picker-action (now, clear, reset,
close).
<div class="row">
<div class="col-lg-4">
<div data-coreui-locale="en-US" data-coreui-time-picker>
<template data-coreui-template="footer">
<button type="button" class="btn-ghost theme-danger btn-sm me-auto" data-coreui-picker-action="now">Now</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>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.
<div class="form-field">
<span class="form-label" id="timePickerMeetingTimeLabel">Meeting time</span>
<div data-coreui-locale="en-US" data-coreui-time-picker aria-labelledby="timePickerMeetingTimeLabel"></div>
<div class="form-text">Local time, in your own time zone.</div>
</div>Floating labels
The time 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.
<div data-coreui-locale="en-US" data-coreui-floating-label="Meeting time" data-coreui-time-picker></div>Add .form-floating-always to the component element to keep the label always floating with the mask showing.
Sizing
<div class="row mb-4">
<div class="col-lg-4">
<div data-coreui-locale="en-US" data-coreui-size="lg" data-coreui-time-picker></div>
</div>
</div>
<div class="row">
<div class="col-lg-4">
<div data-coreui-locale="en-US" data-coreui-size="sm" data-coreui-time-picker></div>
</div>
</div>Disabled
<div class="row">
<div class="col-lg-4">
<div data-coreui-disabled="true" data-coreui-locale="en-US" data-coreui-time-picker></div>
</div>
</div>Min and max times
Constrain the times the user can pick with minDate and maxDate, given as time strings like 09:30 or as dates, of which only the time of day counts. The panel disables the hours that hold no allowed time and, once an hour is chosen, the minutes and seconds outside the range, and a time picked in the panel that crosses a limit moves to it. A time typed in the field outside the range gets the .is-invalid class, the picker reports null and submits no time until the value is corrected. A maxDate at midnight covers the whole day; any other maxDate before the minDate leaves no time to pick. The limits also apply when you give them through inputOptions.
<div class="row">
<div class="col-lg-4">
<label class="form-label" id="timePickerOpeningHoursLabel">Opening hours (09:30–17:00)</label>
<div data-coreui-time-picker data-coreui-locale="en-GB" data-coreui-min-date="09:30" data-coreui-max-date="17:00" data-coreui-seconds="false" aria-labelledby="timePickerOpeningHoursLabel"></div>
</div>
</div>Validation
The picker takes its validation state the way the date picker does: the validationState option (with invalid and valid as deprecated aliases) or an .is-invalid or .is-valid class written on the picker element, read once when the picker starts. A given state drops on the next change of the time the user makes, a time picked in the panel, the cleaner, a footer button that changes the time through the context, or typing in the field, and stays through setTime(), clear(), reset() and now() called by your code. setValidationState() sets it again. While the given state is 'invalid', the sections of the field carry aria-invalid="true", are described by the message after the picker, and the picker blocks the submit with its text, or Invalid value. when there is none.
<div class="row">
<div class="col-lg-4 form-field">
<div class="is-invalid" data-coreui-time-picker data-coreui-locale="en-US" data-coreui-time="12:30:00"></div>
<div class="invalid-feedback">The room is taken at this time.</div>
</div>
</div>Custom formats
Disable minutes and seconds
<div class="row">
<div class="col-sm-6 col-lg-4 mb-4 mb-sm-0">
<label class="form-label" id="timePickerWithoutSecondsLabel">Without seconds</label>
<div data-coreui-locale="en-US" data-coreui-seconds="false" data-coreui-time-picker aria-labelledby="timePickerWithoutSecondsLabel"></div>
</div>
<div class="col-sm-6 col-lg-4">
<label class="form-label" id="timePickerHoursOnlyLabel">Hours only</label>
<div data-coreui-locale="en-US" data-coreui-minutes="false" data-coreui-seconds="false" data-coreui-time-picker aria-labelledby="timePickerHoursOnlyLabel"></div>
</div>
</div>Custom hours, minutes and seconds
Pass a list or a filter function through hours, minutes, and seconds. Hours are 0 to 23 in every locale. On a 12-hour clock the column still lists 1 to 12 next to AM/PM: picking an hour that only one period allows sets that period, and the period the picked hour does not allow is disabled, so the example below offers 8 AM to 4 PM.
const picker = new coreui.TimePicker(element, {
locale: 'en-US',
hours: [8, 9, 10, 11, 12, 13, 14, 15, 16],
minutes: minute => minute % 15 === 0,
seconds: false
})Locale
The locale decides whether the field and the time columns use a 12-hour or a 24-hour clock, and names the day periods in its own words, 오전 and 오후 in Korean. A format pins the clock in every locale, 'HH:mm' for 24 hours and 'hh:mm a' for 12.
Auto
<div class="row">
<div class="col-lg-4">
<div data-coreui-time-picker></div>
</div>
</div>Chinese
<div class="row">
<div class="col-lg-4">
<div data-coreui-locale="zh-CN" data-coreui-time-picker></div>
</div>
</div>Japanese
<div class="row">
<div class="col-lg-4">
<div data-coreui-locale="ja" data-coreui-time-picker></div>
</div>
</div>Korean
<div class="row">
<div class="col-lg-4">
<div data-coreui-locale="ko" data-coreui-time-picker></div>
</div>
</div>Right to left support
Hebrew
<div class="row" dir="rtl">
<div class="col-lg-4">
<div data-coreui-locale="he-IL" data-coreui-time-picker></div>
</div>
</div>Persian
<div class="row" dir="rtl">
<div class="col-lg-4">
<div data-coreui-locale="fa-IR" data-coreui-time-picker></div>
</div>
</div>Accessibility
The toggle button carries the panel’s state — aria-haspopup="dialog", aria-controls pointing at the popup while it is open, and aria-expanded tracking whether it is open — so a screen reader announces what the button does and whether the panel is showing. The panel itself is a dialog with aria-modal, so focus moves into it on open, stays there while it is open, and returns to the field when it closes. The field itself is a time input, a group of spinbutton sections, so its arrow keys belong to the value and the popup opens on the native dropdown keys.
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.
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.
Keyboard support
| Key | Action |
|---|---|
| Alt (Option on macOS) + Arrow Down / F4 | Open the time popup and move focus into it |
| Escape | Close the popup and return focus to the field |
| Tab / Shift + Tab | Move within the open panel; the time selection is one stop, so Tab steps over it rather than through it |
| Arrow Up / Arrow Down | Move through the focused hour, minute or second list |
| Arrow Left / Arrow Right | Move between the hour, minute, second and AM/PM parts |
| Home / End | First or last option of the focused list |
Usage
Via data attributes
<div data-coreui-time-picker></div>Via JavaScript
const pickerElementList = Array.prototype.slice.call(document.querySelectorAll('.my-time-picker'))
const pickerList = pickerElementList.map(pickerEl => {
return new coreui.TimePicker(pickerEl)
})Options
Options belonging to the inner time input are
forwarded by name, so their data attributes work on the picker element too. The
time input’s date and the roll’s hourCycle, onChange and time are set
by the picker itself.
| Property | Type | Default |
|---|---|---|
allowList | object | SVGAllowlist |
Allowlist used when sanitizing the icon markup. | ||
ariaCleanerLabel | string | 'Clear time' |
Accessible label of the cleaner button. | ||
ariaLabel | string | 'Time input' |
Accessible name of the field. It lands on the | ||
ariaPickerLabel | string | 'Toggle time selection' |
Accessible label of the picker button. | ||
ariaPopupLabel | string | 'Time selection' |
Accessible name of the time selection panel, which opens as a | ||
ariaSelectHoursLabel | string | 'Select hours' |
Accessible name of the hours column of the roll. | ||
ariaSelectMeridiemLabel | string | 'Select AM/PM' |
Accessible name of the AM/PM column of the roll. | ||
ariaSelectMinutesLabel | string | 'Select minutes' |
Accessible name of the minutes column of the roll. | ||
ariaSelectSecondsLabel | string | 'Select seconds' |
Accessible name of the seconds column of the roll. | ||
cleaner | boolean | true |
Renders a button that clears the value. It appears only once the field holds one. | ||
cleanerIcon | string | cross SVG |
Inline SVG rendered by the component (sanitized). | ||
container | string, element, false | false |
Teleport target for the dropdown. Inside a modal dialog it goes into the dialog instead, because the rest of the page is inert. | ||
disabled | boolean | false |
Disables the field and the popup. | ||
floatingLabel | string, null | null |
Renders a floating label over the field, which also becomes its accessible name. An | ||
hours | array, function, null | null |
Allowed hours, 0 to 23 in every locale — a list or a filter. | ||
inputOptions | object | {} |
Programmatic escape hatch for time input options, except | ||
invalid | boolean | false |
Deprecated alias of | ||
locale | string | browser locale |
Drives the mask and the 12/24-hour split. | ||
maxDate | date, string, null | null |
The latest time the user can pick, a time string or a date of which only the time of day counts. | ||
minDate | date, string, null | null |
The earliest time the user can pick, a time string or a date of which only the time of day counts. | ||
minutes | array, boolean, function | true |
Allowed minutes, or | ||
name | string, null | null |
Name of the generated value input. Without it the field is not submitted with the form. | ||
pickerIcon | string, boolean | true |
The button that opens the time selection: | ||
sanitize | boolean | true |
Sanitize the icon markup. | ||
sanitizeFn | function, null | null |
Custom sanitizer. | ||
seconds | array, boolean, function | true |
| ||
selectionOptions | object | {} |
Programmatic escape hatch for the selection body, except | ||
size | string, null | null |
| ||
time | date, string, null | null |
Initial value. | ||
valid | boolean | false |
Deprecated alias of | ||
validationState | string, null | null |
The validation state the picker shows: | ||
Methods
| Method | Description |
|---|---|
clear | Clears the value. |
dispose | Destroys the picker and its inner components. |
getContext | Returns the slot context. |
getTime | Returns the current value (Date or null). |
now | Sets the current time. |
reset | Restores 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. |
setTime | Sets the value without firing timeChange. The open panel follows it. |
show / hide / toggle | Popup control. |
Slot context
const context = picker.getContext()
context.now() // set the current time
context.setTime(date) // set any time
context.clear() // clear the value
context.reset() // restore the initial value
context.close() // close the popup
context.time // current value (Date | null)
context.disabled // disabled stateEvents
| Event | Description |
|---|---|
show/shown/hide/hidden.coreui.time-picker | Popup lifecycle. shown and hidden fire once the popup’s transition has ended; shown is skipped when the popup closes first, hidden when it reopens first. |
timeChange.coreui.time-picker | Fired once when the value changes, whether from the field, the panel or an action such as now(); setTime() does not fire it. event.time is the value the field accepted, the same as getTime(), or null when it refused the time. |
timeChange.coreui.time-picker fires before the field’s errorChange.coreui.time-input for the same change, so a time an errorChange listener sets replaces the one just reported.
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 panel’s tokens are declared on .time-picker-popup, which is the element that survives being teleported away from the field — set them there rather than on an ancestor.
$time-picker-tokens: defaults(
(
--cui-time-picker-footer-border-width: var(--cui-border-width),
--cui-time-picker-footer-border-color: var(--cui-border-color),
--cui-time-picker-footer-padding-y: .5rem,
--cui-time-picker-footer-padding-x: .5rem,
--cui-time-picker-footer-gap: .5rem,
--cui-time-picker-col-border-width: var(--cui-border-width),
--cui-time-picker-col-border-color: var(--cui-border-color),
--cui-time-picker-cell-width: 3rem,
--cui-time-picker-cell-padding-x: .5rem,
--cui-time-picker-cell-height: 2rem,
--cui-time-picker-cell-hover-color: var(--cui-fg-body),
--cui-time-picker-cell-hover-bg: var(--cui-bg-2),
--cui-time-picker-cell-disabled-color: var(--cui-fg-4),
--cui-time-picker-cell-selected-color: var(--cui-primary-contrast),
--cui-time-picker-cell-selected-bg: var(--cui-primary-base),
--cui-time-picker-cell-selected-hover-color: var(--cui-primary-contrast),
--cui-time-picker-cell-selected-hover-bg: oklch(from var(--cui-primary-base) calc(l * var(--cui-theme-hover-lightness)) calc(c * var(--cui-theme-hover-chroma)) h),
),
$time-picker-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 .time-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.
$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
);