Command
A command palette — a modal search box over a list of actions. Generic: it reports the chosen value and leaves what it does (and the Cmd+K/Ctrl+K shortcut) to you.
fandry-command
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 |
|---|---|---|---|
open | boolean | false | Whether the palette is shown. Listen for toggle (detail is the new state) and update it. |
label | string | '' | Accessible name of the palette. |
aria-label | string | — | The same name, set the standard way. Wins over label. |
placeholder | string | '' | Hint shown in the empty search box. |
messages | { results(count) } | {} | Replaces what a screen reader hears as the list narrows ("3 results"), e.g. to translate it. "No results" is the empty slot. |
items | { label, value, description?, group?, keywords?, disabled? }[] | [] | The commands. Ungrouped items list first; grouped items sit under their heading. |
select (event) | CustomEvent<{ value }> | — | Fired when an item is picked; the palette then closes itself. |
empty (slot) | slot | 'No results found' | Replaces the message shown when nothing matches. |
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 |
|---|---|
backdrop | The dimmed layer behind a modal panel, or a loading overlay's cover. |
panel | The surface that opens: a dropdown's list, a popover, a tooltip, a dialog, a loading overlay's box. |
search | The wrapper of the search input. |
input | The native <input>. |
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. |
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 |
|---|---|
selected | The chosen option, a selected table row, or the selected tab. |
active | The option the keyboard or pointer is on. |
disabled | A control, option or item that cannot be used. |
<!-- template -->
<div class="brand row">
<fandry-button variant="secondary" onclick={handleOpen}>Search the shop</fandry-button>
<fandry-command
label="Search the shop"
placeholder="Search products and pages…"
items={items}
open={isOpen}
ontoggle={handleToggle}
></fandry-command>
</div>
/* css */
/* Scoped to this demo. Set the same --fd-* tokens on :root to theme
every fandry-* component on the site. */
.brand {
--fd-radius-lg: 1.25rem;
--fd-backdrop: 160 40% 10%;
--fd-backdrop-opacity: 0.45;
--fd-bg-muted: 160 40% 94%;
}
fandry-command::part(search) {
border-bottom: 2px solid hsl(160 84% 26%);
}
fandry-command::part(group-label) {
color: hsl(160 84% 26%);
letter-spacing: 0.08em;
text-transform: uppercase;
}
fandry-command::part(option-description) {
font-style: italic;
}
Code
Link to section Code<fandry-command
label="Command palette"
placeholder="Type a command or search…"
items={items}
open={isOpen}
ontoggle={handleToggle}
onselect={handleSelect}
></fandry-command>
// component
handleToggle(event) { this.isOpen = event.detail; }
handleSelect(event) { run(event.detail.value); }
Example: Custom rows and empty state
Link to section Example: Custom rows and empty stateCode
<!-- template -->
<fandry-command label="Command palette" items={items}
open={isOpen} ontoggle={handleToggle} onselect={handleSelect}>
<span slot="empty">Nothing to run for that.</span>
</fandry-command>
// component
import CommandRow from 'my/commandRow'; // your LWC: @api icon, label, hint
items = [
{
label: 'New file', value: 'new-file', group: 'File',
component: CommandRow,
componentProps: { icon: '📄', label: 'New file', hint: '⌘N' }
},
{
label: 'Toggle theme', value: 'toggle-theme', group: 'View',
component: CommandRow,
componentProps: { icon: '🌗', label: 'Toggle theme' }
}
];
// 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.