ComboBox
MoleculeSearchable autocomplete single-select with keyboard navigation, described options, and a clearable button.
Start typing to filter.
<%- include('modules/ui/ComboBox', {
label: 'Country',
placeholder: 'Search countries…',
hint: 'Start typing to filter.',
options: [
{ value: 'tr', label: 'Türkiye', description: 'TR · +90' },
{ value: 'de', label: 'Germany', description: 'DE · +49' },
{ value: 'us', label: 'United States', description: 'US · +1' },
]
}) %>
<%- include('modules/ui/ComboBox', {
label: 'Country',
value: 'tr',
options: COUNTRIES
}) %>
- No results found.
<%- include('modules/ui/ComboBox', {
label: 'Framework',
value: 'next',
options: FRAMEWORKS
}) %>
Please select a country.
<%- include('modules/ui/ComboBox', {
label: 'Country',
required: true,
error: 'Please select a country.',
options: COUNTRIES
}) %>
- No results found.
Debounced 300ms, AbortController cancels in-flight, 5-min cache.
<%- include('modules/ui/ComboBox', {
id: 'cb-async',
label: 'Search',
options: [],
placeholder: 'Type to search…',
hint: 'Debounced 300ms, AbortController cancels in-flight.'
}) %>
<%- include('modules/ui/ComboBox', {
label: 'Country',
value: 'de',
disabled: true,
options: COUNTRIES
}) %>
<%
// ComboBox (split entrypoint) — mirrors NextJS modules/ui/ComboBox/index.tsx.
// Includes _trigger + _listbox partials and emits an init script that wires
// filter.js / async.js / keyboard.js helpers.
var _id = locals.id || 'combobox-' + Math.random().toString(36).substr(2, 9);
var _label = locals.label || '';
var _options = locals.options || [];
var _value = locals.value || '';
var _placeholder = locals.placeholder || 'Search or select...';
var _hint = locals.hint || '';
var _error = locals.error || '';
var _dis = !!locals.disabled;
var _req = !!locals.required;
var _clearable = locals.clearable !== false;
var _noResultsText = locals.noResultsText || 'No results found.';
var _className = locals.className || '';
var hintId = _hint ? (_id + '-hint') : '';
var errorId = _error ? (_id + '-error') : '';
var describedBy = [hintId, errorId].filter(Boolean).join(' ');
var listboxId = _id + '-listbox';
var labelId = _id + '-label';
var inputId = _id + '-input';
var selectedOption = null;
for (var i = 0; i < _options.length; i++) {
if (_options[i].value === _value) { selectedOption = _options[i]; break; }
}
var selectedLabel = selectedOption ? selectedOption.label : '';
var rootStateClass = _error
? 'border-error ring-1 ring-error bg-error-subtle'
: 'border-border';
var rootDisabledClass = _dis ? ' cursor-not-allowed bg-surface-sunken opacity-50' : '';
%>
<%- include('./partials/_trigger', {
_id: _id,
_value: _value,
_placeholder: _placeholder,
_dis: _dis,
_req: _req,
_error: _error,
_clearable: _clearable,
hintId: hintId,
errorId: errorId,
describedBy: describedBy,
listboxId: listboxId,
labelId: labelId,
inputId: inputId,
selectedLabel: selectedLabel,
rootStateClass: rootStateClass,
rootDisabledClass: rootDisabledClass
}) %>
<%- include('./partials/_listbox', {
_id: _id,
_options: _options,
_value: _value,
listboxId: listboxId,
_noResultsText: _noResultsText
}) %>
<% if (_hint && !_error) { %><%= _hint %>
<% } %>
<% if (_error) { %><%= _error %>
<% } %>
<%
// Inline the helper scripts on the first include so consumers don't have to
// remember to