Skip to content
milan.swiss Halbton v0.1.0
Concept project

Components

Select

A real <select>, restyled. This is the component where design systems most often trade working behaviour for a matching chevron.

Why it is a native element

Almost every design system replaces the select with a div-based listbox, because the native one cannot be fully styled — the option list is drawn by the operating system. What that trade gives up is substantial and mostly invisible until someone relies on it:

  • Type-ahead. Typing tic jumps to Ticino. Custom listboxes reimplement this badly or not at all.
  • The mobile picker. iOS and Android render a native wheel or sheet, which is faster and more reliable than any in-page dropdown.
  • Form integration. It submits, resets, and participates in constraint validation for free.
  • Every keyboard behaviour the platform already ships, including ones that differ between platforms and that a reimplementation will get wrong on at least one.

Halbton styles what it can — the closed control — and leaves the list to the platform. The chevron is a background image so the element stays a real <select>.

States

Affects the tax rate on the invoice.

Only one currency is available for this region.

When not to use it

  • Under about five options. Radio buttons show every choice without a click and are faster to answer.
  • Over about twenty. Scrolling a long native list is miserable; that is the case for a real combobox with search.
  • For yes/no. That is a checkbox.
  • As a navigation menu. A select that navigates on change traps keyboard users, who move through options with the arrow keys and would trigger every one on the way past.

Markup

select
<div class="ht-field">
  <label class="ht-field__label" for="region">Region</label>
  <select class="ht-select" id="region">
    <option>Zürich</option>
    <option>Bern</option>
    <option>Ticino</option>
  </select>
</div>

Keyboard

Entirely the platform's, which is the point. Space or Enter opens the list, arrows move, Enter commits, Escape cancels, and typing jumps. None of it is reimplemented here, so none of it can be reimplemented wrongly.

Accessibility

  • Always a <label for>. The same rule as Text field, for the same reasons.
  • No empty first option used as a label. If a placeholder is genuinely needed, use a disabled, selected option that reads like a prompt — and keep the real label above.
  • appearance: none removes the platform chevron, so the replacement has to carry enough contrast to be found. It uses currentColor, which follows the text tier and therefore the theme.