kai-menu
A cascading action menu you describe with data, not markup. Hand it a tree of items — actions, headings, separators, checkboxes, submenus — and it builds the dropdown, handles keyboard nav, and tells you what was picked.
- Shadow DOM
- JSON items tree
- Submenus + checkboxes
- Keyboard navigable
Preview
Section titled “Preview”Build the menu from its items tree, set in JavaScript — arrays can’t be HTML attributes. Each item is an action (id + label, optional icon/shortcut), a heading, a separator, a checkbox (add checked), or a submenu (nest more items). Selecting a leaf fires kai-select with its id; checkbox items also report the new checked value.
The trigger is the built-in button — point trigger-icon / trigger-label at it — or your own element in slot="trigger".
<kai-menu trigger-icon="plus" label="Add content"></kai-menu>
<script type="module"> import '@kitn.ai/ui/elements';
const menu = document.querySelector('kai-menu'); menu.items = [ { heading: true, label: 'Actions' }, { id: 'add-files', label: 'Add files or photos', icon: 'paperclip', shortcut: '⌘U' }, { id: 'add-github', label: 'Add from GitHub', icon: 'github' }, { separator: true }, { id: 'web-search', label: 'Web search', icon: 'globe', checked: true }, ];
menu.addEventListener('kai-select', (e) => { console.log(e.detail); // { id } — or { id, checked } for a checkbox item });</script>Examples
Section titled “Examples”The ”+” menu
Section titled “The ”+” menu”Headings group the list, a submenu nests deeper actions, a separator splits sections, and a checkbox tracks a toggle. This is the composer add-content menu.
Row overflow
Section titled “Row overflow”A flat set of actions behind a ⋯ button — the default trigger when you set neither trigger-icon nor trigger-label.
Select-style trigger
Section titled “Select-style trigger”Pair trigger-label with trigger-icon-trailing="chevron-down" for a control that reads like a select — current value on the left, chevron on the right.
The menu builds its own trigger from the trigger-* props. Slot your own element when you need full control of it:
| Slot | Mode | Purpose |
|---|---|---|
| trigger | replace | Your own trigger element; replaces the built-in button driven by the `trigger-icon` / `trigger-label` props. |
<kai-menu> <button slot="trigger" aria-label="Open menu">+</button></kai-menu>| Property | Type | Default | Notes |
|---|---|---|---|
| items | — | Tree of menu items. Set as a JS property — not an HTML attribute. | |
| placement | string | — | Optional placement hint (unused by the underlying Dropdown which always positions bottom-start, kept for future extension). |
| triggerIcon | string | — | Built-in trigger: leading icon (a named icon like `"plus"`, an image URL/data-URI, or text). Use this instead of slotting `slot="trigger"` for the common case — a slotted trigger overrides it. |
| triggerLabel | string | — | Built-in trigger: a text label (e.g. `"High"`). |
| triggerIconTrailing | string | — | Built-in trigger: a trailing icon (e.g. `"chevron-down"` for a select look). |
| label | string | — | Accessible name for an icon-only trigger (no visible label). |
| open | boolean | — | Drive/observe open state (Shoelace-style: settable + reflected to the `open` attribute, the menu still self-manages on click/keyboard). Set `el.open = true`, or `<kai-menu open>`; listen for `kai-open-change`. |
| defaultOpen | boolean | — | Initial open state on mount (uncontrolled seed). |
| disabled | boolean | — | Disable the trigger — click/keyboard and `show()` no longer open the menu. |
Events
Section titled “Events”| Event | Detail | Notes |
|---|---|---|
| kai-open-change | | The menu opened or closed (by click, keyboard, Escape, outside-click, or a method). |
| kai-select | | Fired when the user selects a leaf item. - Plain items: `{ id }`. - Checkbox items: `{ id, checked }` where `checked` is the NEW state. - Radio items: `{ id, radioGroup }` — the consumer marks `id` as the selected one in `radioGroup` and clears the others. |
Composed from
Section titled “Composed from”This element wraps these SolidJS components — reach for them directly when you need finer control than the props expose.