Skip to content
kitn AI/UI

Menu

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

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>

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.

A flat set of actions behind a button — the default trigger when you set neither trigger-icon nor trigger-label.

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:

SlotModePurpose
replaceYour 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>
PropertyTypeDefaultNotes
itemsTree of menu items. Set as a JS property — not an HTML attribute.
placementOptional placement hint (unused by the underlying Dropdown which always positions bottom-start, kept for future extension).
triggerIconBuilt-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.
triggerLabelBuilt-in trigger: a text label (e.g. `"High"`).
triggerIconTrailingBuilt-in trigger: a trailing icon (e.g. `"chevron-down"` for a select look).
labelAccessible name for an icon-only trigger (no visible label).
openDrive/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`.
defaultOpenInitial open state on mount (uncontrolled seed).
disabledDisable the trigger — click/keyboard and `show()` no longer open the menu.
EventDetailNotes
The menu opened or closed (by click, keyboard, Escape, outside-click, or a method).
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.

This element wraps these SolidJS components — reach for them directly when you need finer control than the props expose.

DropdownDropdownTriggerDropdownContentDropdownItemDropdownSeparatorDropdownLabelDropdownCheckboxItemDropdownRadioItemDropdownSubDropdownSubTriggerDropdownSubContentKbd