Combobox
A searchable select — type to narrow the options, then pick one.
fandry-combobox
Example
Link to section ExampleCustom colors and parts
Link to section Custom colors and partsThe same component with a few --fd-* tokens set around it and its parts restyled. The code is under Parts below.
Props
Link to section Props| Prop | Type | Default | Description |
|---|---|---|---|
label | string | '' | Visible label. |
placeholder | string | '' | Shown when nothing is selected or typed. |
help-text | string | '' | Helper text below the field. |
error-text | string | '' | An error shown under the field: the field is invalid while it has one (aria-invalid, the invalid state), and a screen reader hears it when it appears and when the field is focused. |
name | string | '' | Exposed as data-name on the input. |
options | { label, value, description?, group?, keywords?, disabled? }[] | [] | The options. Search matches label, description, keywords and group; a prefix in the label ranks first. |
value | string | '' | Selected value. Listen for change (detail is the new value). |
disabled | boolean | false | Disables the field. |
required | boolean | false | Marks the field required (asterisk + aria-required). |
element-props | Record<string, unknown> | {} | Spread onto the native input (e.g. { tabIndex: 2 }); keys the component controls are ignored with a warning. |
empty (slot) | slot | 'No results' | Replaces the message shown when nothing matches. |
label · help-text · error-text (slots) | slot | the props | Markup in place of the label, help text or error text. Shown even without the matching prop. |
Parts
Link to section PartsStyle the elements inside the component from your own CSS with ::part(). Every fandry component renders in native shadow DOM, so parts work on LWR and Salesforce alike. For colors, spacing, radius and type across many components, set the --fd-* tokens instead. The code for the "Custom colors and parts" example above:
| Part | Description |
|---|---|
base | The outermost element. |
label | The label. |
required | The required-field asterisk. |
control | The box the field draws: a text field's border, a select's button, a checkbox's box, a radio's circle, a switch's track. |
input | The native <input>. |
chevron | The dropdown arrow. |
panel | The surface that opens: a dropdown's list, a popover, a tooltip, a dialog, a loading overlay's box. |
listbox | The list of options. |
group | A group of options. |
group-label | A group's heading. |
option | One option. |
option-label | An option's label. |
option-description | An option's second line. |
empty | What shows when there is nothing to list. |
help-text | The help text under the field. |
error-text | The error under the field. In the page even while empty, so style it only for a field that is invalid. |
States are extra names on a part while the state is on. Name both to style one state, e.g.fandry-checkbox::part(control checked).
| State | Description |
|---|---|
disabled | A control, option or item that cannot be used. |
invalid | A field that has error text. |
selected | The chosen option, a selected table row, or the selected tab. |
active | The option the keyboard or pointer is on. |
<!-- template -->
<div class="brand narrow">
<fandry-combobox
label="Category"
placeholder="Search categories"
options={options}
value={value}
onchange={handleChange}
></fandry-combobox>
</div>
/* css */
/* Scoped to this demo. Set the same --fd-* tokens on :root to theme
every fandry-* component on the site. */
.brand {
--fd-radius-md: 0.75rem;
--fd-border-focus: 160 84% 36%;
--fd-ring-color: 160 84% 36%;
--fd-bg-muted: 160 40% 94%;
}
fandry-combobox::part(control) {
background: hsl(160 40% 98%);
font-weight: 600;
}
fandry-combobox::part(group-label) {
color: hsl(160 84% 26%);
letter-spacing: 0.08em;
text-transform: uppercase;
}
fandry-combobox::part(option-description) {
font-style: italic;
}
Code
Link to section Code<fandry-combobox
label="Framework"
placeholder="Search frameworks"
options={frameworkOptions}
value={value}
onchange={handleChange}
></fandry-combobox>
Example: Custom rows and empty state
Link to section Example: Custom rows and empty stateCode
<!-- template -->
<fandry-combobox label="Plan" options={planOptions}
value={value} onchange={handleChange}>
<span slot="empty">No plan matches.</span>
</fandry-combobox>
// component
import PlanRow from 'my/planRow'; // your LWC: @api icon, label, hint
planOptions = [
{
label: 'Free', value: 'free', component: PlanRow,
componentProps: { icon: '🌱', label: 'Free' }
},
{
label: 'Pro', value: 'pro', component: PlanRow,
componentProps: { icon: '💎', label: 'Pro', hint: 'Popular' }
}
];
// The component draws only the inside of the row; the row keeps its
// highlight, hover and click. Search still matches on `label`
// (and keywords/description), so give every item a real one.
Example: Build on the base class: own template, async search
Link to section Example: Build on the base class: own template, async searchCode
<!-- template: your own markup, bound to the inherited handlers -->
<input role="combobox" aria-expanded="true"
aria-controls="people"
aria-activedescendant={activeDescendant}
oninput={handleInput}
onkeydown={handleInputKeydown} />
<ul id="people" role="listbox"
onmousedown={handleListboxMouseDown}>
<template for:each={renderGroups} for:item="group">
<template for:each={group.options} for:item="option">
<li key={option.id} id={option.id} class={option.classes}
role="option" data-option-id={option.id}
onclick={handleOptionClick}
onmousemove={handleOptionMouseMove}>
{option.label}
</li>
</template>
</template>
</ul>
// component
import FdSearchState from 'fandry/searchState';
export default class PeopleSearch extends FdSearchState {
results = [];
// what to list
protected get source() { return this.results; }
// the server already filtered
protected filterItems(items) { return items; }
// what picking one does
protected commit(item) { this.picked = item.label; }
// start the search
protected setQuery(value) {
super.setQuery(value);
fetchPeople(value).then((people) => (this.results = people));
}
}
// Keep the input and the options in the same template: aria-activedescendant
// can't point across a shadow boundary.