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
The chips re-derive the .badge soft recipe from the shared
--soft-* tokens. 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(--surface);
--cb-color: var(--text);
--cb-border-color: var(--border);
--cb-border-radius: var(--radius);
--cb-focus-color: var(--focus);
--cb-muted-color: var(--text-muted);
--cb-error-color: var(--danger);
--cb-chip-bg: transparent;
--cb-control-height: var(--control-size);
--cb-option-min-height: var(--control-size);
}
.actual-combobox .cb-control:focus-within,
.actual-combobox .cb-text-control:focus {
outline: 2px solid transparent;
box-shadow: var(--focus-ring-shadow);
}
.actual-combobox .cb-chip-remove:focus-visible {
outline: 2px solid transparent;
box-shadow: 0 0 0 2px var(--focus-ring);
}
.actual-combobox .cb-chip {
min-block-size: var(--badge-size);
padding-inline: 0.55em;
border: var(--border-width) solid
color-mix(in oklab, var(--surface) var(--soft-border-mix), var(--primary));
border-radius: var(--radius-full);
background: color-mix(in oklab, var(--surface) var(--soft-bg-mix), var(--primary));
color: color-mix(in oklab, var(--primary) var(--soft-fg-mix), var(--text));
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
}
.actual-combobox .cb-chip-remove {
color: color-mix(in oklab, var(--primary) var(--soft-fg-mix), var(--text));
}
.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));
}