Select
MoleculeLabel + select + hint + error anatomy. appearance-none overrides the native dropdown style and renders a chevron icon.
<%- include('modules/ui/Select', {
label: 'Role',
placeholder: 'Select a role…',
options: [
{ value: 'admin', label: 'Admin' },
{ value: 'editor', label: 'Editor' },
{ value: 'viewer', label: 'Viewer' },
]
}) %>
Determines access level.
<%- include('modules/ui/Select', {
label: 'Role',
hint: 'Determines access level.',
value: 'editor',
options: ROLES
}) %>
Please select a plan.
<%- include('modules/ui/Select', {
label: 'Plan',
placeholder: 'Select a plan',
required: true,
error: 'Please select a plan.',
options: PLANS
}) %>
<%- include('modules/ui/Select', { label: 'Plan', disabled: true, value: 'editor', options: ROLES }) %>
<%
var _id = locals.id || 'select-' + Math.random().toString(36).substr(2, 9);
var _dis = !!locals.disabled;
var _req = !!locals.required;
var _opts = locals.options || [];
var _searchable = !!locals.searchable;
var _hasIcons = _opts.some(function (o) { return !!o.icon; });
var _useCombo = _searchable || _hasIcons;
var _val = (locals.value !== undefined && locals.value !== null) ? String(locals.value) : '';
var _hintId = (locals.hint && !locals.error) ? (_id + '-hint') : '';
var _errorId = locals.error ? (_id + '-error') : '';
var _describedBy = [_hintId, _errorId].filter(function (x) { return !!x; }).join(' ');
var _selected = null;
for (var i = 0; i < _opts.length; i++) {
if (String(_opts[i].value) === _val) { _selected = _opts[i]; break; }
}
%>
<% if (_useCombo) { %>
<%
var comboClass = 'flex items-center gap-2 w-full rounded-md border px-3 py-2 text-sm transition-colors cursor-pointer '
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus ';
comboClass += locals.error
? 'border-error ring-1 ring-error bg-error-subtle '
: 'border-border bg-surface-base ';
if (_dis) comboClass += 'opacity-50 cursor-not-allowed bg-surface-sunken ';
%>
aria-describedby="<%= _describedBy %>"<% } %>
aria-disabled="<%= _dis ? 'true' : 'false' %>"
aria-required="<%= _req ? 'true' : 'false' %>"
aria-invalid="<%= locals.error ? 'true' : 'false' %>"
data-testid="select-<%= _id %>"
data-combobox-trigger
class="<%= comboClass %>"
>
<% if (_selected && _selected.icon) { %>
<%- _selected.icon %>
<% } %>
<%= _selected ? _selected.label : (locals.placeholder || 'Select…') %>
<% if (_hintId) { %><%= locals.hint %>
<% } %>
<% if (_errorId) { %><%= locals.error %>
<% } %>
<% } else { %>
<%
var baseClass = 'block w-full rounded-md border px-3 py-2 text-sm transition-colors appearance-none '
+ 'bg-surface-base text-text-primary '
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-focus focus-visible:border-border-focus '
+ 'disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-surface-sunken ';
baseClass += locals.error
? 'border-error ring-1 ring-error bg-error-subtle'
: 'border-border';
%>
<% if (_hintId) { %><%= locals.hint %>
<% } %>
<% if (_errorId) { %><%= locals.error %>
<% } %>
<% } %>