Fandry UI
Overlays & Data

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 Example

Custom colors and parts

Link to section Custom colors and parts

The 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
PropTypeDefaultDescription
openbooleanfalseWhether the palette is shown. Listen for toggle (detail is the new state) and update it.
labelstring''Accessible name of the palette.
aria-labelstring—The same name, set the standard way. Wins over label.
placeholderstring''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 Parts

Style 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:

PartDescription
backdropThe dimmed layer behind a modal panel, or a loading overlay's cover.
panelThe surface that opens: a dropdown's list, a popover, a tooltip, a dialog, a loading overlay's box.
searchThe wrapper of the search input.
inputThe native <input>.
listboxThe list of options.
groupA group of options.
group-labelA group's heading.
optionOne option.
option-labelAn option's label.
option-descriptionAn option's second line.
emptyWhat 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).

StateDescription
selectedThe chosen option, a selected table row, or the selected tab.
activeThe option the keyboard or pointer is on.
disabledA 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 state

Code

<!-- 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 search

Code

<!-- 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.
​