Detent web components
By Shane Logsdon

Overlays & menus

Command Menu

Implemented

Command palette grouped by category

detent-command-menu

Port status
Catalog status
implemented
Origin
upstream
Phase
3
Wave
phase-3-overlays
Group
Overlays & menus
Search components and docs (⌘K)

7.1 KB gzip for the class module · inlines the shared spring

Variants

⌘K / Ctrl+K toggles the palette. Arrow keys move the active row with wrapping, Enter activates it, Escape closes and returns focus to the trigger.

No command selected yet.

Recorded states

  • open recorded state; no setter

Usage

Import

import '/components/command-menu/define.js';

// or register it yourself
import { DetentCommandMenu } from '/components/command-menu/command-menu.js';

Attributes

  • is-open

Properties

  • items
  • isOpen
  • onClose
  • onNavigateDocs
  • onSelectComponent
  • query
  • activeIndex

Slots

No slots are recorded for this component.

Events

  • detent-close — {}
  • detent-select-component — {}
  • detent-navigate-docs — {}

Example

Runnable example: site/examples/command-menu.ts. The snippet below is generated from the catalogued attributes.

<detent-command-menu
  is-open
></detent-command-menu>

Property-only values

const element = document.querySelector('detent-command-menu');
if (element !== null) {
  // onClose: () => void
  element.onClose = () => {};
}

Source

The pinned upstream source for this row:

  • src/components/ui/CommandMenu.tsx sha256 a50beed1444e…

Native files

The native module and its artifacts:

  • src/components/command-menu/command-menu.ts
  • src/components/command-menu/styles.css
  • src/components/command-menu/define.ts
  • docs/components/command-menu.md
  • site/examples/command-menu.ts
  • tests/browser/command-menu.spec.ts
  • evidence/components/command-menu.md

API

Boolean attributes follow HTML presence semantics: present means true, absent means false, and disabled="false" still means disabled.

Attributes

Attributes
AttributePropertyTypeDefaultNotes
is-openisOpenbooleanfalsePresence semantics: present means true, absent means false.

Properties

Properties
PropertyTypeDefaultNotes
items——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
isOpenbooleanfalseProperty-only: assign from script; not accepted as an attribute.
onClose() => voidRequiredProperty-only: assign from script; not accepted as an attribute.
onNavigateDocs——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
onSelectComponent——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
query——Recorded in the catalog without a top-level type; pinned from source when the row is ported.
activeIndex——Recorded in the catalog without a top-level type; pinned from source when the row is ported.

Events

Events
EventUpstream propPayload typeNotes
detent-closeonClose() => voidComposed, bubbles; fires only for a user action.
detent-select-componentonSelectComponent(id: string) => voidComposed, bubbles; fires only for a user action.
detent-navigate-docsonNavigateDocs(topicId?: string) => voidComposed, bubbles; fires only for a user action.

Slots

No slots are recorded for this component.

CSS customisation

  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • items is consumer data. Upstream built its rows from the site's generated catalog index plus onSelect closures; the native component takes plain-data rows and reports the selection target through id/topicId (PC-3.1, PC-6.3).
  • ⌘K / Ctrl+K toggles. Upstream's document listener only _closed_ an open palette; the host page opened it. A palette with no trigger slot would be unreachable for a keyboard-only user, so the shortcut toggles (PC-12.6 accessibility correction).
  • Top layer instead of a fixed overlay. Upstream used fixed inset-0 divs; the native port uses <dialog>.showModal() so no ancestor stacking or overflow can clip it and the page behind is inert (PC-9.8, PC-12.4).
  • One combobox, no roving buttons. Upstream put role="option" on focusable <button> elements while tracking selection by index; the native rows are non-focusable options driven by aria-activedescendant (PC-12.1).
  • The palette is theme-driven in both schemes. Upstream's surface colours were hard-coded dark hexes (#141414, #1F1F1F, #FAFAFA); the native palette reads --detent-color-* tokens with light and dark values (PC-9.5).
  • Spring, not AnimatePresence. The entrance/exit is the shared spring helper with the recorded springSnappy physics; the per-item CornerDownLeft icon is replaced by a text ↵ mark (PC-10.2, PC-11.5).
  • onClose is optional. Upstream typed it as required; the native port keeps it as an optional hook and always emits detent-close (PC-6.2).
  • Page scroll is locked while open, which upstream did not do; without it the background scrolls behind a modal dialog.
  • className is replaced by documented parts and custom properties.

What it does

It opens as a real modal dialog in the browser's top layer, so no ancestor transform, overflow or z-index can clip or mislayer it.

Upstream behaviour
  • Global hotkey listener (⌘K / Ctrl+K)
  • Arrow key navigation with wrapping
  • Category badges and action shortcuts
Differences from upstream
  • React callback props become typed composed CustomEvents; programmatic state assignment emits none.
  • Framer Motion springs/AnimatePresence ported to CSS transitions, WAAPI or the shared rAF spring helper.
  • `lucide-react` icon components replaced with local SVG assets (ISC notice retained).
  • React state/hooks replaced by element-owned state with pre-upgrade property preservation.
  • items is consumer data. Upstream built its rows from the site's generated catalog index plus onSelect closures; the native component takes plain-data rows and reports the selection target through id/topicId (PC-3.1, PC-6.3).
  • ⌘K / Ctrl+K toggles. Upstream's document listener only _closed_ an open palette; the host page opened it. A palette with no trigger slot would be unreachable for a keyboard-only user, so the shortcut toggles (PC-12.6 accessibility correction).
  • Top layer instead of a fixed overlay. Upstream used fixed inset-0 divs; the native port uses <dialog>.showModal() so no ancestor stacking or overflow can clip it and the page behind is inert (PC-9.8, PC-12.4).
  • One combobox, no roving buttons. Upstream put role="option" on focusable <button> elements while tracking selection by index; the native rows are non-focusable options driven by aria-activedescendant (PC-12.1).
  • The palette is theme-driven in both schemes. Upstream's surface colours were hard-coded dark hexes (#141414, #1F1F1F, #FAFAFA); the native palette reads --detent-color-* tokens with light and dark values (PC-9.5).
  • Spring, not AnimatePresence. The entrance/exit is the shared spring helper with the recorded springSnappy physics; the per-item CornerDownLeft icon is replaced by a text ↵ mark (PC-10.2, PC-11.5).
  • onClose is optional. Upstream typed it as required; the native port keeps it as an optional hook and always emits detent-close (PC-6.2).
  • Page scroll is locked while open, which upstream did not do; without it the background scrolls behind a modal dialog.
  • className is replaced by documented parts and custom properties.
Previous: Combobox Next: Cursor Follower

Docs

Components

Project