Web component
Combobox × Actual CSS
@lekoala/combobox loaded from CDN; the skin is pure Actual CSS.
Switch themes and watch the chips, picker, focus and states follow the
tokens — the combobox engine never changes.
Skills — multiple with removable badges
setDefaultChipClass("badge primary") renders each chip as a real
.badge. Focus a chip and press Delete/Backspace to remove it.
Autocomplete — a native input that looks like one
An input + datalist sits next to a plain Actual
.input — same height, radius, border, focus and typography
(search="includes" gives an accent-insensitive substring match).
Single select — progressive enhancement
The native <select> stays the value owner. A separate unnamed
filter input drives the picker; the placeholder option is never an offer.
Optgroups + fuzzy search
Order-preserving subsequence matching: type bnn and Banana surfaces.
Group headers and catalogue order come straight from the native
<optgroup>s.
Duplicate values are distinct choices
Three options share value="2". Each is its own chip; the native
<select> remains the source of truth and submits the serialized
values below.
Submitted values:
[]
States
Disabled, required and invalid are reflected from the native source — submit the
form to see the required field turn --danger.
Theme bridge
This page adds no Actual CSS package, no compatibility layer and no JS.
Fewer than sixty lines of CSS map the combobox --cb-* tokens onto the
Actual visible vocabulary.
.actual-combobox {
--cb-bg: var(--control-default-bg, var(--surface));
--cb-color: var(--text);
--cb-border-color: var(--form-invalid-border, var(--border));
--cb-border-radius: var(--radius);
--cb-focus-color: var(--focus);
--cb-muted-color: var(--text-muted);
--cb-error-color: var(--danger);
--cb-control-height: var(--control-size);
--cb-option-min-height: var(--control-size);
}
.actual-combobox :is(.cb-text-control:focus-visible, .cb-control:has(.cb-input:focus-visible)) {
border-color: var(--form-invalid-border, var(--cb-border-color));
outline: var(--focus-ring-width) solid var(--form-invalid-border, var(--focus));
outline-offset: calc(var(--focus-ring-width) * -1);
box-shadow: var(--control-shadow, none);
}
.actual-combobox :is(.cb-text-control[aria-invalid="true"], .cb-control:has([aria-invalid="true"])) {
--form-invalid-border: var(--danger);
}
:where(.actual-combobox) combo-box {
border-radius: var(--cb-border-radius);
}
.actual-combobox :is(.cb-control, .cb-text-control) {
border-width: var(--border-width);
border-radius: inherit;
box-shadow: var(--control-shadow, none);
}
.actual-combobox :is(.cb-control, .cb-text-control):hover {
border-color: var(--form-invalid-border, var(--control-hover-border, var(--text-subtle)));
}
.cb-popover {
--cb-picker-bg: var(--surface-raised);
border-color: var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow-popout);
}
.cb-option:is(:hover, [data-active]) {
background: var(--hover-overlay);
color: var(--text);
}
.cb-option[aria-selected="true"] {
background: color-mix(in oklab, var(--surface) var(--soft-bg-mix), var(--primary));
}