Skip to content

Date picker

hc-datepicker styles a native <input> whose type is one of date, datetime-local, month, or time. The native input retains every accessible behaviour — keyboard navigation, locale-aware rendering, the OS-native calendar / time picker on mobile, form submission, min / max / step validation — and only the closed-state chrome is replaced via appearance: none and an embedded SVG icon.

If you need a fully-bespoke calendar UI (custom highlighting, preset ranges, multi-month view), reach for hc-calendar — and for a date form field with a styled dropdown grid, use the blessed input + popover + calendar pattern. The native input is the right default for the vast majority of business forms.

type="time" swaps the trailing icon to a clock; the other types all use a calendar glyph.

Same axes as hc-input / hc-selectdata-variant accepts success, warning, and error for border-color cues.

Set aria-invalid="true" together with data-variant="error" so assistive tech also recognises the field as invalid.

data-size accepts sm, md (default), and lg. Sizes follow the same --hc-control-* scale as hc-button / hc-input / hc-select, so data-density="compact" shrinks consistently.

Use the native disabled attribute. The control dims and stops accepting input; the value still submits if you instead use readonly.

A native <input type="date"> is single-value. For ranges, place two inputs adjacent to each other and link their constraints in form validation.

<fieldset class="hc-field">
<legend>Date range</legend>
<input class="hc-datepicker" type="date" name="from" aria-label="From"
id="range-from" max="2027-12-31">
<input class="hc-datepicker" type="date" name="to" aria-label="To"
id="range-to" min="2026-01-01">
</fieldset>
<script>
// Keep the two ends consistent.
const from = document.getElementById('range-from');
const to = document.getElementById('range-to');
from.addEventListener('change', () => { if (from.value) to.min = from.value; });
to.addEventListener('change', () => { if (to.value) from.max = to.value; });
</script>

The input participates in form submissions like any native input. For instant filtering on date change:

<input class="hc-datepicker"
type="date"
name="due"
data-hx-get="/tasks"
data-hx-trigger="change"
data-hx-target="#tasks">
  • Always associate the input with a label — wrap it in hc-field with a <label for>, use aria-label, or use aria-labelledby.
  • The native input handles keyboard fully: ← / → moves between year / month / day spinners, ↑ / ↓ changes the active spinner. Do not add custom keyboard handlers; they conflict with the built-in behaviour.
  • The icon is purely decorative — screen readers ignore the background-image.
  • For business apps with localisation, the browser handles date rendering in the user’s locale automatically. The submitted value is always ISO 8601 (yyyy-mm-dd) regardless of display locale.

Component tokens (in component.tokens.json):

Token pathPurpose
datepicker.height / padding-xSizing (inherits density via --hc-control-*).
datepicker.radius / font-size / bg / fg / borderSurface, text, resting border.
datepicker.focus-borderBorder when focused.
datepicker.error-border / success-border / warning-borderVariant borders.
datepicker.disabled-bgBackground when disabled.
datepicker.icon-sizeCalendar / clock icon dimension.
datepicker.sm.* / lg.*Dedicated sm / lg overrides.
Show the generated CSS variables
--hc-datepicker-height | -padding-x | -radius | -font-size
--hc-datepicker-bg | -fg | -border
--hc-datepicker-focus-border | -error-border | -success-border | -warning-border
--hc-datepicker-disabled-bg
--hc-datepicker-icon-size
--hc-datepicker-sm-height | -sm-padding-x | -sm-font-size
--hc-datepicker-lg-height | -lg-padding-x | -lg-font-size
--hc-color-focus-ring (inherited from data-color)
  • Input — text and textarea sibling sharing the same control scale.
  • Select — styled native <select> with the same chevron architecture.
  • Field — label + helper text wrapper.