# Command palette

A search-first panel of commands: type to filter, arrow keys to move, Enter to run, with groups under small labels and key hints at the foot.

Block: `smbk-command-palette`. Status: candidate. Figma: the Figma file, https://www.figma.com/community/file/1688324308358870630. Desktop and mobile: differ by presentation only.

## Options

| Axis | Values | Default | Meaning |
|---|---|---|---|

Parts: `smbk-command-palette__search`, `smbk-command-palette__search-icon`, `smbk-command-palette__input`, `smbk-command-palette__clear`, `smbk-command-palette__filters`, `smbk-command-palette__body`, `smbk-command-palette__group`, `smbk-command-palette__group-header`, `smbk-command-palette__group-label`, `smbk-command-palette__list`, `smbk-command-palette__item`, `smbk-command-palette__item-icon`, `smbk-command-palette__item-label`, `smbk-command-palette__item-text`, `smbk-command-palette__item-description`, `smbk-command-palette__item-meta`, `smbk-command-palette__status`, `smbk-command-palette__empty`, `smbk-command-palette__footer`, `smbk-command-palette__hints`, `smbk-command-palette__hint`, `smbk-command-palette__hint-text`, `smbk-command-palette__dialog`, `smbk-command-palette__glide`

## Markup

```html
<div class="smbk-command-palette">
  <div class="smbk-command-palette__search">
    <i
      class="ph ph-magnifying-glass smbk-icon smbk-icon--size-md smbk-command-palette__search-icon"
      aria-hidden="true"
    ></i>
    <input
      type="search"
      class="smbk-command-palette__input"
      placeholder="Type a command or search…"
      aria-label="Search commands"
      role="combobox"
      aria-expanded="true"
      aria-autocomplete="list"
      aria-controls="command-palette-1-list-0 command-palette-1-list-1"
      aria-activedescendant="command-palette-1-option-0"
      value=""
    >
    <button type="button" class="smbk-command-palette__clear" aria-label="Clear search">
      <i class="ph ph-x smbk-icon smbk-icon--size-md" aria-hidden="true"></i>
    </button>
  </div>
  <hr class="smbk-divider smbk-divider--orientation-horizontal"/>
  <p class="smbk-command-palette__status" role="status"></p>
  <div class="smbk-command-palette__body" data-scrollbar="">
    <div class="smbk-command-palette__group">
      <div class="smbk-command-palette__group-header">
        <span
          class="smbk-label smbk-label--tone-subtle smbk-label--type-small smbk-command-palette__group-label"
          id="command-palette-1-group-0"
        >
          <span class="smbk-label__text">Design system</span>
        </span>
        <button
          type="button"
          class="smbk-btn smbk-btn--tone-ghost smbk-btn--size-sm"
          aria-label="See all Design system"
        >
          <span class="smbk-btn__label">See all</span>
        </button>
      </div>
      <div
        id="command-palette-1-list-0"
        role="listbox"
        aria-labelledby="command-palette-1-group-0"
        class="smbk-command-palette__list"
      >
        <div
          id="command-palette-1-option-0"
          role="option"
          data-index="0"
          aria-selected="true"
          class="smbk-command-palette__item"
        >
          <i
            class="ph ph-diamonds-four smbk-icon smbk-icon--size-md smbk-command-palette__item-icon"
            aria-hidden="true"
          ></i>
          <span class="smbk-command-palette__item-label" data-label="Button">Button</span>
          <span class="smbk-label smbk-label--tone-subtle smbk-label--type-default smbk-command-palette__item-meta">
            <span class="smbk-label__text">Components</span>
          </span>
          <kbd class="smbk-kbd smbk-kbd--size-md" aria-hidden="true">⌘</kbd>
        </div>
        <div
          id="command-palette-1-option-1"
          role="option"
          data-index="1"
          aria-selected="false"
          class="smbk-command-palette__item"
        >
          <i
            class="ph ph-diamonds-four smbk-icon smbk-icon--size-md smbk-command-palette__item-icon"
            aria-hidden="true"
          ></i>
          <span class="smbk-command-palette__item-label" data-label="Header">Header</span>
          <span class="smbk-label smbk-label--tone-subtle smbk-label--type-default smbk-command-palette__item-meta">
            <span class="smbk-label__text">Components</span>
          </span>
          <kbd class="smbk-kbd smbk-kbd--size-md" aria-hidden="true">⌘</kbd>
        </div>
        <div
          id="command-palette-1-option-2"
          role="option"
          data-index="2"
          aria-selected="false"
          class="smbk-command-palette__item"
        >
          <i
            class="ph ph-diamonds-four smbk-icon smbk-icon--size-md smbk-command-palette__item-icon"
            aria-hidden="true"
          ></i>
          <span class="smbk-command-palette__item-label" data-label="Colors">Colors</span>
          <span class="smbk-label smbk-label--tone-subtle smbk-label--type-default smbk-command-palette__item-meta">
            <span class="smbk-label__text">Foundation</span>
          </span>
          <kbd class="smbk-kbd smbk-kbd--size-md" aria-hidden="true">⌘</kbd>
        </div>
      </div>
    </div>
    <div class="smbk-command-palette__group">
      <div class="smbk-command-palette__group-header">
        <span
          class="smbk-label smbk-label--tone-subtle smbk-label--type-small smbk-command-palette__group-label"
          id="command-palette-1-group-1"
        >
          <span class="smbk-label__text">Getting started</span>
        </span>
        <button
          type="button"
          class="smbk-btn smbk-btn--tone-ghost smbk-btn--size-sm"
          aria-label="See all Getting started"
        >
          <span class="smbk-btn__label">See all</span>
        </button>
      </div>
      <div
        id="command-palette-1-list-1"
        role="listbox"
        aria-labelledby="command-palette-1-group-1"
        class="smbk-command-palette__list"
      >
        <div
          id="command-palette-1-option-3"
          role="option"
          data-index="3"
          aria-selected="false"
          class="smbk-command-palette__item"
        >
          <i
            class="ph ph-diamonds-four smbk-icon smbk-icon--size-md smbk-command-palette__item-icon"
            aria-hidden="true"
          ></i>
          <span class="smbk-command-palette__item-label" data-label="Guides">Guides</span>
          <span class="smbk-label smbk-label--tone-subtle smbk-label--type-default smbk-command-palette__item-meta">
            <span class="smbk-label__text">Documentation</span>
          </span>
          <kbd class="smbk-kbd smbk-kbd--size-md" aria-hidden="true">⌘</kbd>
        </div>
        <div
          id="command-palette-1-option-4"
          role="option"
          data-index="4"
          aria-selected="false"
          class="smbk-command-palette__item"
        >
          <i
            class="ph ph-diamonds-four smbk-icon smbk-icon--size-md smbk-command-palette__item-icon"
            aria-hidden="true"
          ></i>
          <span
            class="smbk-command-palette__item-label"
            data-label="Implementation"
          >Implementation</span>
          <span class="smbk-label smbk-label--tone-subtle smbk-label--type-default smbk-command-palette__item-meta">
            <span class="smbk-label__text">Documentation</span>
          </span>
          <kbd class="smbk-kbd smbk-kbd--size-md" aria-hidden="true">⌘</kbd>
        </div>
      </div>
    </div>
  </div>
  <div class="smbk-command-palette__footer" aria-hidden="true">
    <hr class="smbk-divider smbk-divider--orientation-horizontal"/>
    <div class="smbk-command-palette__hints">
      <span class="smbk-command-palette__hint">
        <kbd class="smbk-kbd smbk-kbd--size-md">↑</kbd>
        <kbd class="smbk-kbd smbk-kbd--size-md">↓</kbd>
        <span class="smbk-command-palette__hint-text">Navigate</span>
      </span>
      <span class="smbk-command-palette__hint">
        <kbd class="smbk-kbd smbk-kbd--size-md">↵</kbd>
        <span class="smbk-command-palette__hint-text">Select</span>
      </span>
      <span class="smbk-command-palette__hint">
        <kbd class="smbk-kbd smbk-kbd--size-md">esc</kbd>
        <span class="smbk-command-palette__hint-text">Close</span>
      </span>
    </div>
  </div>
</div>
```

## React

```tsx
import { CommandPalette } from './design-system/components/command-palette/command-palette.js';

<CommandPalette groups={[{ label: "Design system", onSeeAll: () => {}, commands: [{ id: "button", label: "Button", icon: "DiamondsFour", meta: "Components", shortcut: "⌘" }, { id: "header", label: "Header", icon: "DiamondsFour", meta: "Components", shortcut: "⌘" }, { id: "colors", label: "Colors", icon: "DiamondsFour", meta: "Foundation", shortcut: "⌘" }] }, { label: "Getting started", onSeeAll: () => {}, commands: [{ id: "guides", label: "Guides", icon: "DiamondsFour", meta: "Documentation", shortcut: "⌘" }, { id: "implementation", label: "Implementation", icon: "DiamondsFour", meta: "Documentation", shortcut: "⌘" }] }]} />
```

## Stylesheet

```css
/* smbk-command-palette — Command palette. Figma "Command Palette" 4336:22996, built from
 * "CommandHeader" 4333:22019, "CommandBody" 4336:22171, "CommandItem" 4333:21975 and
 * "CommandFooter" 4336:22098.
 * A search row over groups of commands, each group under a small label with an
 * optional "See all" button, and a footer of key hints. The pieces Figma
 * builds from other components are those components here: the chips, the
 * labels, the See all button, the keycaps and the dividers. Shown in place,
 * or over the page in a native modal dialog on the scrim. */

.smbk-command-palette {
  box-sizing: border-box;
  position: relative;
  display: flex;
  flex-direction: column;
  width: var(--smbk-size-command-palette-width);
  max-width: 100%;
  max-height: 100%;
  overflow: hidden;
  border: var(--smbk-border-width-hairline) solid var(--smbk-color-border-rim);
  border-radius: var(--smbk-radius-container-md);
  background: var(--smbk-color-surface-default);
  color: var(--smbk-color-text-default);
  box-shadow: var(--smbk-elevation-overlay);
}

/* ---- the search row ---- */

.smbk-command-palette__search {
  display: flex;
  flex-shrink: 0;
  align-items: center;
  gap: var(--smbk-space-inline-md);
  height: var(--smbk-size-target-comfortable);
  padding: 0 var(--smbk-space-inset-xl);
  border-radius: calc(var(--smbk-radius-container-md) - var(--smbk-border-width-hairline)) calc(var(--smbk-radius-container-md) - var(--smbk-border-width-hairline)) 0 0;
}

/* The search box is where the keyboard is, so it keeps a focus mark: the focus ring just inside the row's
   edge, following the panel's corners. With no results it is the only mark there is. */
.smbk-command-palette__search:has(> .smbk-command-palette__input:focus-visible) {
  outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
  outline-offset: calc(-1 * var(--smbk-focus-ring-width));
}

.smbk-command-palette__search-icon {
  flex-shrink: 0;
  color: var(--smbk-color-text-subtlest);
  font-size: var(--smbk-size-icon-md);
}

.smbk-command-palette__input {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--smbk-color-text-default);
  font-family: var(--smbk-typography-body-md-font-family);
  font-size: var(--smbk-typography-body-md-font-size);
  font-weight: var(--smbk-typography-body-md-font-weight);
  line-height: var(--smbk-typography-body-md-line-height);
  letter-spacing: var(--smbk-typography-body-md-letter-spacing);
  /* The search row draws the focus ring around the field instead (above). */
  outline: none;
  appearance: none;
}

.smbk-command-palette__input::placeholder {
  color: var(--smbk-color-text-subtle);
  opacity: 1;
}

.smbk-command-palette__input::-webkit-search-cancel-button,
.smbk-command-palette__input::-webkit-search-decoration {
  appearance: none;
}

/* The cross in a box big enough to press, the glyph at the medium icon size in its middle. */
.smbk-command-palette__clear {
  box-sizing: border-box;
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  justify-content: center;
  width: var(--smbk-size-target-min);
  height: var(--smbk-size-target-min);
  padding: 0;
  border: 0;
  background: none;
  color: var(--smbk-color-text-subtlest);
  font-size: var(--smbk-size-icon-md);
  cursor: pointer;
  appearance: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

.smbk-command-palette__clear:focus-visible {
  outline: var(--smbk-focus-ring-width) solid var(--smbk-color-border-focus);
  outline-offset: var(--smbk-focus-ring-offset);
  border-radius: var(--smbk-radius-full);
}

/* ---- the chips row: the filters in force, each removable ---- */

.smbk-command-palette__filters {
  display: flex;
  flex-shrink: 0;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--smbk-space-inline-md);
  padding: var(--smbk-space-inset-sm) var(--smbk-space-inset-xl);
}

/* ---- the body: groups of commands, scrolling when the page limits the height ---- */

.smbk-command-palette__body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--smbk-space-stack-xs);
  min-height: 0;
  padding: var(--smbk-space-inset-md) var(--smbk-space-inset-xs);
  overflow: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--smbk-color-border-subtle) transparent;
}

.smbk-command-palette__group {
  display: flex;
  flex-direction: column;
}

/* The group's label and its See all button share a row the small control height; the label is set in from
   the side as the commands are, so it lines up with their icons. */
.smbk-command-palette__group-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--smbk-space-inline-md);
  min-height: var(--smbk-size-control-sm);
}

.smbk-command-palette__group-label {
  padding: 0 var(--smbk-space-inset-lg);
}

.smbk-command-palette__list {
  display: flex;
  flex-direction: column;
}

.smbk-command-palette__item {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  gap: var(--smbk-space-inline-md);
  height: var(--smbk-size-control-md);
  padding: 0 var(--smbk-space-inset-lg);
  border-radius: var(--smbk-radius-control-sm);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
}

/* Under the pointer: Figma's Hover state. With the wrapper the pointer's fill is the glide part instead
   (below), so a command's own hover is drawn only in markup without it. */
@media (hover: hover) {
  .smbk-command-palette__item:hover:where(:not([data-glide] *)) {
    background: var(--smbk-color-surface-canvas);
  }
}

/* The highlighted command, the one Enter runs, reached by arrow key or by pointer: Figma's Highlighted
   state, the sunken fill and its label one weight step bolder, so it shows without color too. Drawn only
   while the search box has focus, where the keys are: a palette shown on a page that nobody is using
   marks no command, as nothing else at rest looks focused. */
.smbk-command-palette:has(.smbk-command-palette__input:focus) .smbk-command-palette__item[aria-selected="true"] {
  background: var(--smbk-color-surface-sunken);
}

.smbk-command-palette:has(.smbk-command-palette__input:focus) .smbk-command-palette__item[aria-selected="true"] .smbk-command-palette__item-label {
  font-family: var(--smbk-typography-body-md-medium-font-family);
  font-weight: var(--smbk-typography-body-md-medium-font-weight);
}

.smbk-command-palette__item[aria-disabled="true"] {
  color: var(--smbk-color-text-disabled);
  cursor: not-allowed;
}

/* ---- the glide: with the wrapper, one fill slides to the command under the mouse (glide.ts) instead of
   each command lighting up on its own, so a pointer in the gap between groups or in the padding still
   lights the nearest command; the command it lights is the highlighted one. The wrapper adds it as the
   body's first child and marks the body data-glide ("lit" while it shows); the groups are lifted over it.
   It is Figma's Hover fill, and the Highlighted fill while the search box has focus, which the lit command
   then does not draw again; its label keeps the bolder weight. It fades in where it lands and out as the
   pointer leaves, and slides with the feedback purpose; under reduced motion it lands at once and only
   fades. A touch screen draws none. ---- */

:where(.smbk-command-palette__body[data-glide]) {
  position: relative;
}

:where(.smbk-command-palette__body[data-glide] > :not(.smbk-command-palette__glide)) {
  position: relative;
}

.smbk-command-palette__glide {
  position: absolute;
  top: 0;
  left: 0;
  box-sizing: border-box;
  border-radius: var(--smbk-radius-control-sm);
  background: var(--smbk-color-surface-canvas);
  opacity: 0;
  pointer-events: none;
  transition-property: translate, width, height, opacity;
  transition-duration: var(--smbk-motion-feedback-duration);
  transition-timing-function: var(--smbk-motion-feedback-easing);
}

.smbk-command-palette:has(.smbk-command-palette__input:focus) .smbk-command-palette__glide {
  background: var(--smbk-color-surface-sunken);
}

.smbk-command-palette__body[data-glide="lit"] > .smbk-command-palette__glide {
  opacity: 1;
}

.smbk-command-palette .smbk-command-palette__body[data-glide="lit"] .smbk-command-palette__item[aria-selected="true"] {
  background: transparent;
}

.smbk-command-palette__glide[data-instant] {
  transition-property: opacity;
}

@media (prefers-reduced-motion: reduce) {
  .smbk-command-palette__glide {
    transition-property: opacity;
  }
}

@media (hover: none) {
  .smbk-command-palette__glide {
    display: none;
  }
}

.smbk-command-palette__item-icon {
  flex-shrink: 0;
  color: var(--smbk-color-text-subtlest);
  font-size: var(--smbk-size-icon-md);
}

.smbk-command-palette__item-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  font-family: var(--smbk-typography-body-md-font-family);
  font-size: var(--smbk-typography-body-md-font-size);
  font-weight: var(--smbk-typography-body-md-font-weight);
  line-height: var(--smbk-typography-body-md-line-height);
  letter-spacing: var(--smbk-typography-body-md-letter-spacing);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The label is always as wide as its bolder self: an invisible copy of its words (`data-label`, which the wrapper
   writes) at the highlighted command's weight lies under it with no height, so a label that turns bold as the
   arrow keys move never pushes anything beside it. The copy is generated content, hidden, with empty words for
   assistive technology, so the label is read once. */
.smbk-command-palette__item-label[data-label]::after {
  content: attr(data-label);
  content: attr(data-label) / "";
  display: block;
  height: 0;
  overflow: hidden;
  visibility: hidden;
  font-family: var(--smbk-typography-body-md-medium-font-family);
  font-weight: var(--smbk-typography-body-md-medium-font-weight);
  pointer-events: none;
  user-select: none;
}

.smbk-command-palette__item-meta,
.smbk-command-palette__item .smbk-kbd {
  flex-shrink: 0;
}

/* A command with a description: the label over a second line in Body SM and the subtle text color,
   the item growing to hold both (Figma's Description toggle). Words marked as matching the search
   show one weight bolder, never by color alone. */
.smbk-command-palette__item:has(> .smbk-command-palette__item-text) {
  height: auto;
  min-height: var(--smbk-size-control-md);
  padding-block: var(--smbk-space-inset-sm);
}

.smbk-command-palette__item-text {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: var(--smbk-space-stack-xs);
  min-width: 0;
}

.smbk-command-palette__item-description {
  overflow: hidden;
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-body-sm-font-family);
  font-size: var(--smbk-typography-body-sm-font-size);
  font-weight: var(--smbk-typography-body-sm-font-weight);
  line-height: var(--smbk-typography-body-sm-line-height);
  letter-spacing: var(--smbk-typography-body-sm-letter-spacing);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.smbk-command-palette__item-description mark {
  background: none;
  color: inherit;
  font-family: var(--smbk-typography-body-sm-medium-font-family);
  font-weight: var(--smbk-typography-body-sm-medium-font-weight);
}

/* The number of results, for screen readers only: announced as the query changes. */
/* .smbk-command-palette__status: hidden from view, kept for screen readers and the keyboard, by the shared rule in visually-hidden/visually-hidden.css. */

.smbk-command-palette__empty {
  margin: 0;
  padding: var(--smbk-space-inset-lg);
  color: var(--smbk-color-text-subtle);
  font-family: var(--smbk-typography-body-md-font-family);
  font-size: var(--smbk-typography-body-md-font-size);
  font-weight: var(--smbk-typography-body-md-font-weight);
  line-height: var(--smbk-typography-body-md-line-height);
  letter-spacing: var(--smbk-typography-body-md-letter-spacing);
}

/* ---- the footer: key hints on the canvas surface ---- */

.smbk-command-palette__footer {
  flex-shrink: 0;
}

.smbk-command-palette__hints {
  display: flex;
  align-items: center;
  gap: var(--smbk-space-inline-lg);
  height: var(--smbk-size-control-sm);
  padding: 0 var(--smbk-space-inset-xl);
  background: var(--smbk-color-surface-canvas);
}

.smbk-command-palette__hint {
  display: inline-flex;
  align-items: center;
  gap: var(--smbk-space-inline-sm);
  color: var(--smbk-color-text-subtlest);
  font-family: var(--smbk-typography-body-xs-font-family);
  font-size: var(--smbk-typography-body-xs-font-size);
  font-weight: var(--smbk-typography-body-xs-font-weight);
  line-height: var(--smbk-typography-body-xs-line-height);
  letter-spacing: var(--smbk-typography-body-xs-letter-spacing);
  white-space: nowrap;
}

/* ---- over the page: a native modal dialog on the scrim, the panel near the top, centered ---- */

.smbk-command-palette__dialog {
  box-sizing: border-box;
  width: 100%;
  max-width: none;
  height: 100%;
  max-height: none;
  margin: 0;
  padding: var(--smbk-space-stack-4xl) var(--smbk-space-inset-xl);
  border: 0;
  background: transparent;
  /* Placed here, not with the open state, so the panel keeps its place while the dialog fades out. */
  align-items: flex-start;
  justify-content: center;
}

.smbk-command-palette__dialog[open] {
  display: flex;
}

/* ---- over the page: the scrim, the motion (the palette fades in and rises a spacing step into place over a scrim
   that fades in, and fades out dropping a smaller step, as the modal does; under reduced motion it fades in place) and
   the page held still behind it are the dialogs' own, shared with the modal and the drawer: dialog/dialog.css. In
   place, it does not move. ---- */

/* ---- touch: the search box, the clear cross, the See all buttons and every command take invisible press areas ----
   On a touch screen the search box reaches over the search row's full height, the comfortable target, with padding
   that takes the press and margins that give the room back. The clear cross takes an invisible area, centred on it,
   out to the target, toward the box only halfway into the row's gap. Each command takes an area out to the target,
   except where two touch, and toward a group's See all button, which touches the first command; a See all button
   reaches only halfway into the gap to the group above. Nothing drawn moves. */

@media (pointer: coarse) {
  .smbk-command-palette__input {
    padding-block: max(0%, (var(--smbk-size-target-comfortable) - 1lh) / 2);
    margin-block: min(0%, (1lh - var(--smbk-size-target-comfortable)) / 2);
  }

  .smbk-command-palette__clear,
  .smbk-command-palette__item {
    position: relative;
  }

  .smbk-command-palette__clear::after,
  .smbk-command-palette__item::after {
    content: '';
    position: absolute;
    inset: min(0%, (100% - var(--smbk-size-target-comfortable)) / 2);
  }

  .smbk-command-palette__clear::after {
    inset-inline-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-md) / -2);
  }

  .smbk-command-palette__item + .smbk-command-palette__item::after,
  .smbk-command-palette__group-header:has(.smbk-btn) + .smbk-command-palette__list > .smbk-command-palette__item:first-child::after {
    inset-block-start: 0;
  }

  .smbk-command-palette__item:has(+ .smbk-command-palette__item)::after {
    inset-block-end: 0;
  }

  .smbk-command-palette__group-header .smbk-btn::after {
    inset-block-end: 0;
  }

  /* The filter chips' remove crosses reach only halfway into their row's padding above and below, and halfway into
     its gap between chips. */
  .smbk-command-palette__filters .smbk-chip__remove::after {
    inset-block: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inset-sm) / -2);
    inset-inline: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-inline-md) / -2);
  }

  .smbk-command-palette__group + .smbk-command-palette__group .smbk-command-palette__group-header .smbk-btn::after {
    inset-block-start: max(min(0%, (100% - var(--smbk-size-target-comfortable)) / 2), var(--smbk-space-stack-xs) / -2);
  }
}

/* ---- high contrast (forced colors) ---- */
/* In Windows high-contrast mode the browser drops fills and shadows; this puts the meaning back with the
   user's own colors. Nothing here applies outside that mode. */

@media (forced-colors: active) {
  /* The highlighted command, by pointer or arrow key. A key cap in it keeps its own Canvas cap,
     so its key stays CanvasText. */
  .smbk-command-palette:has(.smbk-command-palette__input:focus) .smbk-command-palette__item[aria-selected="true"] {
    forced-color-adjust: none;
    background: Highlight;
    color: HighlightText;
  }

  @media (hover: hover) {
    .smbk-command-palette__item:hover {
      forced-color-adjust: none;
      background: Highlight;
      color: HighlightText;
    }
  }

  .smbk-command-palette:has(.smbk-command-palette__input:focus) .smbk-command-palette__item[aria-selected="true"] :is(.smbk-command-palette__item-icon, .smbk-command-palette__item-meta) {
    color: inherit;
  }

  @media (hover: hover) {
    .smbk-command-palette__item:hover :is(.smbk-command-palette__item-icon, .smbk-command-palette__item-meta) {
      color: inherit;
    }
  }

  .smbk-command-palette:has(.smbk-command-palette__input:focus) .smbk-command-palette__item[aria-selected="true"] .smbk-kbd {
    forced-color-adjust: auto;
    color: CanvasText;
  }

  @media (hover: hover) {
    .smbk-command-palette__item:hover .smbk-kbd {
      forced-color-adjust: auto;
      color: CanvasText;
    }
  }

  .smbk-command-palette__item[aria-disabled="true"] {
    color: GrayText;
  }
}
```

## Tokens it reads

```json
{
  "color": {
    "overlay": {
      "scrim": {
        "$type": "color",
        "$value": "{{color.alpha.black.50}}",
        "$description": "Dimmed backdrop behind dialogs and drawers."
      }
    },
    "border": {
      "rim": {
        "$type": "color",
        "$value": "{{color.alpha.white.90}}",
        "$description": "The light rim that makes a surface look lit: fields, chosen pills, cards."
      },
      "focus": {
        "$type": "color",
        "$value": "{{color.red.600}}",
        "$description": "Keyboard focus ring on controls."
      },
      "subtle": {
        "$type": "color",
        "$value": "{{color.grey.300}}",
        "$description": "Dividers and quiet edges."
      }
    },
    "surface": {
      "default": {
        "$type": "color",
        "$value": "{{color.grey.100}}",
        "$description": "Standard surface: cards, panels, the header."
      },
      "canvas": {
        "$type": "color",
        "$value": "{{color.grey.200}}",
        "$description": "Page background."
      },
      "sunken": {
        "$type": "color",
        "$value": "{{color.alpha.grey-800.5}}",
        "$description": "Recessed wells: fields, tracks, code. See-through, so a well always shows one step below what it sits on: the page, a card or a panel."
      }
    },
    "text": {
      "default": {
        "$type": "color",
        "$value": "{{color.grey.900}}",
        "$description": "Main text."
      },
      "subtlest": {
        "$type": "color",
        "$value": "{{color.grey.600}}",
        "$description": "Quietest text: hints, captions, placeholders."
      },
      "subtle": {
        "$type": "color",
        "$value": "{{color.grey.700}}",
        "$description": "Secondary text: descriptions, metadata."
      },
      "disabled": {
        "$type": "color",
        "$value": "{{color.grey.500}}",
        "$description": "Text in unavailable controls."
      }
    }
  },
  "motion": {
    "exit": {
      "duration": {
        "$type": "duration",
        "$value": "{{duration.200}}"
      },
      "easing": {
        "$type": "cubicBezier",
        "$value": "{{easing.expo-in}}"
      }
    },
    "enter": {
      "duration": {
        "$type": "duration",
        "$value": "{{duration.320}}"
      },
      "easing": {
        "$type": "cubicBezier",
        "$value": "{{easing.expo-out}}"
      }
    },
    "feedback": {
      "duration": {
        "$type": "duration",
        "$value": "{{duration.150}}"
      },
      "easing": {
        "$type": "cubicBezier",
        "$value": "{{easing.expo-out}}"
      }
    }
  },
  "space": {
    "8": {
      "$type": "dimension",
      "$value": "8px",
      "$description": "Raw space step, 8 px. Only for fixed distances inside a component; spacing that follows density uses space/inset, stack or inline."
    },
    "16": {
      "$type": "dimension",
      "$value": "16px",
      "$description": "Raw space step, 16 px. Only for fixed distances inside a component; spacing that follows density uses space/inset, stack or inline."
    },
    "inline": {
      "md": {
        "$type": "dimension",
        "$value": "{{space.8}}",
        "$description": "Gap between things side by side, medium."
      },
      "lg": {
        "$type": "dimension",
        "$value": "{{space.12}}",
        "$description": "Gap between things side by side, large."
      },
      "sm": {
        "$type": "dimension",
        "$value": "{{space.6}}",
        "$description": "Gap between things side by side, small."
      }
    },
    "inset": {
      "xl": {
        "$type": "dimension",
        "$value": "{{space.16}}",
        "$description": "Padding inside a component, extra large."
      },
      "sm": {
        "$type": "dimension",
        "$value": "{{space.6}}",
        "$description": "Padding inside a component, small."
      },
      "md": {
        "$type": "dimension",
        "$value": "{{space.8}}",
        "$description": "Padding inside a component, medium."
      },
      "xs": {
        "$type": "dimension",
        "$value": "{{space.4}}",
        "$description": "Padding inside a component, extra small."
      },
      "lg": {
        "$type": "dimension",
        "$value": "{{space.12}}",
        "$description": "Padding inside a component, large."
      }
    },
    "stack": {
      "xs": {
        "$type": "dimension",
        "$value": "{{space.4}}",
        "$description": "Gap between things stacked one above another, extra small."
      },
      "4xl": {
        "$type": "dimension",
        "$value": "{{space.48}}",
        "$description": "Gap between things stacked one above another, 4× large."
      }
    }
  },
  "size": {
    "command-palette": {
      "width": {
        "$type": "dimension",
        "$value": "640px",
        "$description": "Width of the command palette. A fixed frame width in Figma with no variable behind it; it should become one."
      }
    },
    "target": {
      "comfortable": {
        "$type": "dimension",
        "$value": "{{size.44}}",
        "$description": "Comfortable touch target (44)."
      },
      "min": {
        "$type": "dimension",
        "$value": "{{size.24}}",
        "$description": "Smallest tap area allowed (24, WCAG 2.2)."
      }
    },
    "icon": {
      "md": {
        "$type": "dimension",
        "$value": "{{size.20}}",
        "$description": "Icon box, medium."
      }
    },
    "control": {
      "sm": {
        "$type": "dimension",
        "$value": "{{size.32}}",
        "$description": "Height of a small control: 32 comfortable, 28 compact. Dashboard recipe: use compact density; 40 for the page’s one main action and for forms; 32 for toolbars and filters, the size used most; 28 for actions inside table rows and cards. On phones switch to comfortable density, or give small controls an invisible 44 px tap area (size/target/comfortable)."
      },
      "md": {
        "$type": "dimension",
        "$value": "{{size.40}}",
        "$description": "Height of a medium control, where controls insert by default: 40 comfortable, 32 compact. Dashboard recipe: use compact density; 40 for the page’s one main action and for forms; 32 for toolbars and filters, the size used most; 28 for actions inside table rows and cards. On phones switch to comfortable density, or give small controls an invisible 44 px tap area (size/target/comfortable)."
      }
    }
  },
  "border-width": {
    "hairline": {
      "$type": "dimension",
      "$value": "{{border-width.1}}",
      "$description": "Thin lines: dividers and almost every edge."
    }
  },
  "radius": {
    "container": {
      "md": {
        "$type": "dimension",
        "$value": "{{radius.12}}",
        "$description": "Corners of cards, panels and dialogs, medium."
      }
    },
    "full": {
      "$type": "dimension",
      "$value": "{{radius.9999}}",
      "$description": "Capsule: pills, chips, switches."
    },
    "control": {
      "sm": {
        "$type": "dimension",
        "$value": "{{radius.4}}",
        "$description": "Corners of controls, small."
      }
    }
  },
  "elevation": {
    "overlay": {
      "$type": "shadow",
      "$value": {
        "light": "0px 12px 16px 2px #373d431a, 0px 2px 8px 0px #373d4326",
        "dark": "0px 12px 16px 2px #00000026, 0px 2px 8px 0px #00000033"
      }
    }
  },
  "focus": {
    "ring": {
      "width": {
        "$type": "dimension",
        "$value": "{{border-width.2}}",
        "$description": "Thickness of the focus ring."
      },
      "offset": {
        "$type": "dimension",
        "$value": "{{space.2}}",
        "$description": "Gap between a control and its focus ring."
      }
    }
  },
  "typography": {
    "body-md": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "16px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      }
    },
    "body-md-medium": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "16px",
        "fontWeight": 500,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      },
      "$description": "Body MD one weight step bolder, for the current item, so the current item is not shown by colour alone."
    },
    "body-sm": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "14px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      }
    },
    "body-sm-medium": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "14px",
        "fontWeight": 500,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      },
      "$description": "Body SM one weight step bolder, for the current item, so the current item is not shown by colour alone."
    },
    "body-xs": {
      "$type": "typography",
      "$value": {
        "fontFamily": "Geist, Geist Fallback, system-ui, sans-serif",
        "fontSize": "12px",
        "fontWeight": 400,
        "lineHeight": 1.5,
        "letterSpacing": "0px"
      }
    }
  }
}
```

## Purpose

A search-first panel for people who know what they want: type a few letters, move with the arrow keys, press Enter. It gathers pages and actions into groups under short small labels, so a large product can be reached from the keyboard without clicking through menus. It is not the page's main navigation; it is the quick way round it.

## Anatomy

- The block, a raised panel with a hairline edge and the highest resting shadow.
- The search part: the search icon part, the input part and the clear part, a cross in a box big enough to press, which empties the query.
- A divider, then the filters part when filters are in force: removable outline chips, small.
- The body part: one group part per group, each a group header part (the group label part, a subtle small label, and an optional "See all" ghost button) over a list part of item parts.
- Each item part: an optional item icon part, the item label part, an optional item meta part (a subtle label naming the category) and an optional keycap with the command's own shortcut. A command with a description puts the label over the item description part, a second line in smaller subtle text, inside the item text part.
- The status part, heard and not seen: the number of commands shown, announced as the query changes.
- The empty part, shown instead of the groups when nothing matches.
- The footer part: a divider over the hints part, key hints on the canvas surface for moving, choosing and closing.
- The dialog part, when the palette is open over the page: a native modal dialog on the scrim, holding the panel near the top of the window.

## When to use

- A product or documentation site with many pages or actions, where keyboard users want one place to reach them all.
- Behind the page's usual shortcut for it, Cmd+K or Ctrl+K.

## When not to use

- As the only way to reach something. Keep the visible navigation; the palette is a shortcut past it.
- For choosing a value in a form. Use the [select](select.md).
- For a short menu of actions on one thing. Use the [dropdown](dropdown.md).

## Behavior

Typing filters the commands by their label, category and description, and groups with nothing left disappear; when nothing matches, the empty text shows. The first command is highlighted, and the arrow keys move the highlight through every visible command, across groups, wrapping at the ends. The highlighted command, whether reached by arrow or by pointer, takes the sunken surface and its label turns one weight bolder, so it shows without color too.
With a mouse, the highlight is one fill that slides from command to command with the pointer, and lights the nearest command when the pointer is between groups or in the padding, where a click runs it; it fades in where the pointer enters, lands without sliding when the reader's system asks for less motion, and steps aside while the keys move. Only the keys scroll the list to the highlight, so the list never moves under a resting pointer.
Every label keeps the room of its bolder self, so nothing beside it moves as the highlight passes (markup written by hand repeats the label's words in its `data-label` attribute).
Enter or a click runs it; Escape asks to close the palette. The search box carries the focus ring just inside its edge while it has focus, so the keyboard's place shows even when nothing matches. The clear control empties the query and returns focus to it. A group's "See all" button calls the page. The page can filter the commands itself instead, for example against a server, by turning the palette's own filtering off.

Shown in place, the palette is just the panel. Given `open` or `defaultOpen`, it sits over the page in a native modal dialog on the scrim: focus is held inside, the page behind holds still, keeping its scrollbar's room so nothing shifts sideways, and Escape or a click on the scrim asks to close it. Given `open`, the page holds the open state and closes the palette when it asks; given only `defaultOpen`, the palette holds its own and closes itself. Asking to close calls `onOpenChange(false)`. A click on the panel never asks, nor does a press that starts on the panel and ends on the scrim. The page decides what opens it, usually Cmd+K or Ctrl+K, and closes it again after running a command. Over the page it fades in and rises a spacing step into place over a scrim that fades in, and fades out a little faster as it closes, as the modal does; under reduced motion it still fades in and out, in place, without rising or dropping. In place, it does not move.

The palette sets no height of its own. When the page limits its height, the body scrolls.

## Accessibility

### What it does for you

- Focus stays in the search control throughout. It is a combo box whose listboxes are the groups, each named by its label; the highlighted command is announced through the active descendant, so arrowing never moves focus.
- Commands are options; a disabled one is marked so and cannot be run.
- A description is part of its command's text, read after the label. Words marked as matching the search show one weight bolder, never by color alone.
- The "See all" buttons are real buttons, reachable with Tab, named after their group.
- The keycaps and the footer hints are pictures of keys and are hidden from assistive technology; the combo box pattern already tells a screen reader how to move and choose.
- As the query changes, a status announces how many commands are shown ("12 results"), or the empty text when nothing matches. Nothing is announced before the reader types.
- Over the page, the dialog is named "Command palette" unless given another name, and the browser returns focus to where it was when the dialog closes.

### What you must do

- Give every word it says by itself, such as the clear control's name, the status words, the See all buttons and the key hints, in the page's language; each keeps its English unless the page gives its own.
- Close the palette when it asks to, after Escape or a click on the scrim, and decide what opens it, usually Cmd+K or Ctrl+K.

### How to test

- Open it with the page's shortcut, type, and move with the arrow keys: focus stays in the search box and each highlighted command is read aloud.
- Press Enter on a command to run it, and Escape to ask the page to close the palette. Focus returns to where it was.
- With a screen reader, type a query: the number of results is announced, or the empty text when nothing matches.
- With text at 200% and the page 320 pixels wide, the panel stays whole and its body scrolls.
- In a forced-color mode, the highlighted command and the search box's focus ring are still visible.

### Keyboard

| Key | What it does |
|---|---|
| Down Arrow | Moves the highlight to the next command, across groups, wrapping at the end. |
| Up Arrow | Moves the highlight to the previous command, across groups, wrapping at the start. |
| Enter | Runs the highlighted command. |
| Escape | Asks the page to close the palette. |
| Tab | Moves focus on to the controls after the search box, such as a "See all" button. |

## Usage rules

### Do

- Group commands by what they are: pages, actions, recent.
- Give a command a shortcut keycap only when that shortcut really runs it.
- Keep the unfiltered list short; show recent items first and let typing reach the rest.

### Don't

- Put a command in two groups.
- Change what Enter does from "run the highlighted command".
