Overlays

ToolPicker

Dialog for choosing tools and skills from a searchable list grouped by source, with each tool's risk and starting level, a counter, a loading state and collapsing groups for long lists.

import { ToolPicker } from '@lostgradient/cinder';
overlaydialogselectionsearch
01

Overview

Reference usage

ToolPicker is a dialog for choosing tools and skills from a list grouped by source. Each row carries the human name, the identifier once in monospace, a one-line description and two trailing facts: a short word such as how risky the tool is (meta) and the level it starts at (detail). The level is read-only here; changing it happens wherever the chosen tools are listed once they are added. A skill follows the tools it uses, so it is listed with no checkbox (selectable: false) and says so in the trailing column.

The title's line carries a counter ("3 of 9 tools chosen"), a search field sits under it, and the list scrolls inside the dialog under sticky group headers ("Built in, 6", "GitHub, 3, none chosen", "Skills, 2, they follow the tools they use"). The picker edits a copy: Done writes the choices back to selected and calls onDone; Cancel, Escape and a click outside leave selected alone.

Usage

svelte
<script lang="ts">
  import { Button, ToolPicker } from '@lostgradient/cinder';
  import type { ToolPickerGroup, ToolPickerItem } from '@lostgradient/cinder';

  let open = $state(false);
  let selected = $state<string[]>(['lookup-order']);

  const groups: ToolPickerGroup[] = [
    { name: 'Built in' },
    { name: 'Skills', note: 'they follow the tools they use' },
  ];

  const items: ToolPickerItem[] = [
    {
      value: 'lookup-order',
      label: 'Lookup order',
      code: 'lookup-order',
      description: 'Looks an order up by its ID',
      group: 'Built in',
      meta: 'Reads',
      detail: 'Never (default)',
    },
    {
      value: 'issue-refund',
      label: 'Issue refund',
      code: 'issue-refund',
      description: 'Refunds an order to the original card',
      group: 'Built in',
      meta: 'Dangerous',
      detail: 'Always (default)',
    },
    {
      value: 'release-notes',
      label: 'Release notes',
      code: 'release-notes',
      suffix: 'Skill',
      description: 'Drafts release notes from merged pull requests',
      group: 'Skills',
      detail: 'Follows its tools',
      selectable: false,
    },
  ];
</script>

<Button onclick={() => (open = true)}>Add tools and skills</Button>
<ToolPicker bind:open bind:selected {items} {groups} />

States

  • Loading: loading keeps the title and a live search field and draws three skeleton rows after 300 ms, so a quick load never flashes a placeholder. Done waits until the list is there.
  • Filtered to nothing: the search keeps what was typed, and the list says No tools match “invoice” with "Try a shorter word." and one action, Clear search. noMatchTitle takes a string or a function of the query.
  • Forty or more tools: from collapseFrom selectable items (40 by default) the counter reads the total ("42 tools"; "42 tools, 3 chosen" once something is chosen), the group headers become buttons ("Built in 6 tools") and every group but the first starts collapsed. Searching opens every group that has a match.

Choices and order

selected holds item values. Done writes them in the order of items, followed by any value that no item names, so a choice the catalogue no longer offers is not silently dropped. A newly checked item's starting level is the consumer's to decide: give a dangerous tool a detail of "Always (default)" and the form that lists the chosen tools starts it there.

Live preview

Grouped tools and skills

Chosen: Lookup order, Issue refund, Triage refunds.

02

When to use

Use when
  • Letting a person choose several items that carry a name, an identifier, a description and trailing facts, from grouped sources.
  • The list can be long enough to need search and collapsing groups, and some entries are shown but cannot be chosen.
Avoid when
  • Choosing one item from a short list — use select or combobox instead.
  • Moving items between two lists the person keeps open — use transfer-list instead.
03

Examples

Grouped tools and skills

Tools group by source with their risk and the level they start at on each row; skills are listed with no checkbox, because they follow the tools they use. Done keeps the choices, Cancel drops them.

Loading

While the list loads, the title and a live search field stay, and three skeleton rows appear after 300 ms so a quick load never flashes a placeholder. Done waits until the list is there.

Forty or more tools

From forty selectable items the counter reads the total, and every group but the first starts collapsed under its sticky header. The list scrolls inside the dialog, and searching opens every group that has a match.

Filtered to nothing

When the search matches nothing the field keeps its text and the picker says so, with a hint and one action, Clear search. Open it on a search with the bindable query.

04

Props

Props for tool-picker
Name
Type
Default
Description
open bindable boolean false Whether the picker is open. Bindable. It closes itself on Done and Cancel, on Escape and on a click outside, and opens with the chosen items as selected says.
selected bindable string[] [] The values of the chosen items. Bindable. The picker works on a copy while it is open: Done writes it back (in the order of items, keeping any value no item names), and Cancel and dismissing leave it alone.
query bindable text '' The text in the search field. Bindable. It is cleared when the picker closes, so it opens empty unless the page sets it before opening, such as to open on a search that matches nothing.
items required ToolPickerItem[] Everything that can be listed, in the order it should read within each group.
groups ToolPickerGroup[] The groups, in the order they should appear, with a note for a group that cannot be chosen from.
title text 'Add tools and skills' Heading of the dialog.
itemNoun text 'tools' What the items are called, plural, in the counter, the group counts and the empty state ("tools"). The counter reads "3 of 9 tools chosen".
searchLabel text 'Search tools and skills' Accessible name and placeholder of the search field.
collapseFrom number 40 How many selectable items it takes to switch to the long-list layout: the counter reads the total ("42 tools"), and every group but the first starts collapsed under its sticky header.
loading boolean false Draw the loading state: three skeleton rows, after 300 ms, under the title and a live search field. Nothing can be chosen until it is false.
loadingLabel text 'Loading tools and skills' Announced politely when the loading placeholder appears.
noneChosenLabel text 'none chosen' Words on a group's header, after its count, when none of its items is chosen and something else is. Skills and other groups that cannot be chosen from show their note instead.
emptyMetaLabel text 'No risk word' Names the trailing column of an item with no meta, which draws an em dash.
emptyLabel text 'No tools to choose from.' The line shown when there is nothing at all to choose from (no search involved).
noMatchTitle text The heading of the empty state, given what was typed.
noMatchHint text 'Try a shorter word.' The line under the empty state's heading.
clearSearchLabel text 'Clear search' Label of the button in the empty state that clears the search.
doneLabel text 'Done' Label of the button that closes the picker and keeps the choices.
cancelLabel text 'Cancel' Label of the button that closes the picker and drops the choices made in it.
onDone (selected: string[]) => void Called with the chosen values when Done is pressed, after selected is updated.
onCancel () => void Called when the picker is closed without keeping its choices: Cancel, Escape or a click outside.
triggerRef HTMLElement | null null The element focus returns to when the picker closes, usually the button that opened it.