Skip to content

Select

hc-select styles a native <select> element. The underlying element keeps every native behaviour — keyboard navigation, form submission, the OS native picker on mobile, screen-reader semantics — while the closed state is fully restyled to match the rest of the HC form-control family.

Works in every browser that supports <select> (every browser). The expanded dropdown stays browser-native, which keeps the experience consistent with the user’s platform.

A modern follow-up — appearance: base-select — will eventually let you style the open picker too without giving up the native control. See Native-forward: styling the open picker below for the current status and how to opt in today.

Pair with hc-field to get a visible label and helper text on top of the select.

data-variant accepts success, warning, and error for non-default border colors. The default (omitted) variant uses the neutral border.

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, so data-density="compact" shrinks them consistently with the other controls.

Use the native disabled attribute when the control should not be interactive at all. aria-disabled="true" is the focusable variant for the rare case the control must remain in the tab order.

hc-select participates in form submissions like any native <select>. For instant filters, listen to change:

<select class="hc-select"
name="status"
data-hx-get="/items"
data-hx-trigger="change"
data-hx-target="#items"
data-hx-include="this">
<option value="">Any status</option>
<option value="open">Open</option>
<option value="closed">Closed</option>
</select>

For server-side validation feedback, return the select with aria-invalid="true" and a paired hc-field message.

  • Always associate the select with a label. The easiest pattern is to put it inside an hc-field with a <label for>. Use aria-label only when no visible label is appropriate (e.g. a toolbar select).
  • The first option is the visible default — use a placeholder like <option value="">Choose…</option> when nothing is preselected so the empty value is distinguishable from a real choice on the server.
  • The native <select> already handles Enter / Space / Arrow keys. Do not add custom keyboard handlers; you would only conflict with the browser’s built-in behaviour.
  • The chevron is drawn via background-image with a hardcoded neutral stroke color, mirroring the convention used by hc-checkbox / hc-radio. It is purely decorative — screen readers do not announce it.

Component tokens (in component.tokens.json):

Token pathPurpose
select.height / padding-xSizing (inherits density via --hc-control-*).
select.radius / font-size / bg / fg / borderSurface, text, and resting border.
select.focus-borderBorder when focused.
select.error-border / success-border / warning-borderVariant borders.
select.disabled-bgBackground when disabled.
select.chevron-sizeChevron icon dimension.
select.sm.* / lg.*Dedicated sm / lg overrides.
Show the generated CSS variables
--hc-select-height | -padding-x | -radius | -font-size
--hc-select-bg | -fg | -border
--hc-select-focus-border | -error-border | -success-border | -warning-border
--hc-select-disabled-bg
--hc-select-chevron-size
--hc-select-sm-height | -sm-padding-x | -sm-font-size
--hc-select-lg-height | -lg-padding-x | -lg-font-size
--hc-color-focus-ring (inherited from data-color)

A native <select> gives you keyboard navigation, form participation, the OS picker on mobile, and screen-reader semantics for free — but historically the open dropdown could not be styled. The customizable <select> work changes that: opting an element into appearance: base-select lets you style the picker — including the ::picker(select) pseudo-element and each <option> — while keeping a real native control. The picker renders in the top layer (like a popover) and can be placed with CSS anchor positioning. See the Open UI explainer for the full surface.

It is fully progressive: a browser that doesn’t understand appearance: base-select simply ignores it and keeps the native dropdown. If you want the customizable path on supporting browsers today, opt in per-instance:

<select class="hc-select" name="country" style="appearance: base-select">
<button type="button">
<selectedcontent></selectedcontent>
</button>
<option value="">Choose a country…</option>
<option value="jp">Japan</option>
</select>

Need a fully styled dropdown that works everywhere right now? Use hc-combobox — the JS escape hatch that renders an ARIA listbox in light DOM. HC tracks the customizable-<select> rollout and will add a first-class, declarative opt-in to hc-select once the feature reaches Baseline, so the native path can replace the JS one where styling the open picker is the only reason to reach for hc-combobox.

  • Input — text and textarea sibling sharing the same control scale.
  • Field — label + helper text wrapper.
  • Combobox — JS-backed listbox when you need to style the open dropdown today.
  • Checkbox / Radio — boolean / single-pick form controls.