DropdownMenu

Displays a menu to the user -- such as a set of actions or functions -- triggered by a button.

Quick Start

<%= kui(:dropdown_menu) do %>
  <%= kui(:dropdown_menu, :trigger) do %>
    <%= kui(:button, variant: :outline) { "Open" } %>
  <% end %>
  <%= kui(:dropdown_menu, :content) do %>
    <%= kui(:dropdown_menu, :label) { "My Account" } %>
    <%= kui(:dropdown_menu, :separator) %>
    <%= kui(:dropdown_menu, :group) do %>
      <%= kui(:dropdown_menu, :item) { "Profile" } %>
      <%= kui(:dropdown_menu, :item) { "Settings" } %>
    <% end %>
  <% end %>
<% end %>

Locals

Local Type Default
variant: :default | :destructive :default
inset: Boolean false
disabled: Boolean false
href: String | nil nil
method: :delete | :post | :put | :patch | nil nil
turbo: Boolean false
form: Hash {}
css_classes: String ""
**component_options Hash {}
Local Type Default
checked: Boolean false
disabled: Boolean false
css_classes: String ""
**component_options Hash {}
Local Type Default
value: String (required)
checked: Boolean false
disabled: Boolean false
css_classes: String ""
**component_options Hash {}
Local Type Default
inset: Boolean false
css_classes: String ""
**component_options Hash {}
Local Type Default
inset: Boolean false
css_classes: String ""
**component_options Hash {}
Local Type Default
value: String nil
css_classes: String ""
**component_options Hash {}

Sub-parts

Part Usage Purpose
:trigger kui(:dropdown_menu, :trigger) Opens/closes the menu
:content kui(:dropdown_menu, :content) The floating menu panel
:item kui(:dropdown_menu, :item) Standard menu item
:checkbox_item kui(:dropdown_menu, :checkbox_item) Toggle item with checkbox
:radio_group kui(:dropdown_menu, :radio_group) Radio group container
:radio_item kui(:dropdown_menu, :radio_item) Radio selection item
:label kui(:dropdown_menu, :label) Section header label
:separator kui(:dropdown_menu, :separator) Visual divider
:shortcut kui(:dropdown_menu, :shortcut) Keyboard shortcut hint
:group kui(:dropdown_menu, :group) Semantic grouping
:sub kui(:dropdown_menu, :sub) Sub-menu wrapper
:sub_trigger kui(:dropdown_menu, :sub_trigger) Opens nested sub-menu
:sub_content kui(:dropdown_menu, :sub_content) Nested sub-menu panel

All sub-parts accept css_classes: and **component_options.

Anatomy

DropdownMenu
├── Trigger
├── Content
│   ├── Group
│   │   ├── Label
│   │   ├── Item
│   │   │   └── Shortcut
│   │   ├── CheckboxItem
│   │   ├── RadioGroup
│   │   │   └── RadioItem
│   │   └── Sub
│   │       ├── SubTrigger
│   │       └── SubContent
│   │           └── (nested items)
│   └── Separator

Usage

With Icons

<%= kui(:dropdown_menu, :item) do %>
  <%= kiso_icon("user") %>
  Profile
<% end %>

With Shortcuts

<%= kui(:dropdown_menu, :item) do %>
  Settings
  <%= kui(:dropdown_menu, :shortcut) { "⌘S" } %>
<% end %>

Destructive Item

Use variant: :destructive for irreversible actions like delete.

<%= kui(:dropdown_menu, :item, variant: :destructive) do %>
  <%= kiso_icon("trash") %>
  Delete
<% end %>

Checkbox Items

<%= kui(:dropdown_menu, :checkbox_item, checked: true) do %>
  Status Bar
<% end %>
<%= kui(:dropdown_menu, :checkbox_item, checked: false, disabled: true) do %>
  Activity Bar
<% end %>

Radio Group

<%= kui(:dropdown_menu, :radio_group, value: "bottom") do %>
  <%= kui(:dropdown_menu, :radio_item, value: "top") { "Top" } %>
  <%= kui(:dropdown_menu, :radio_item, value: "bottom", checked: true) { "Bottom" } %>
  <%= kui(:dropdown_menu, :radio_item, value: "right") { "Right" } %>
<% end %>
<%= kui(:dropdown_menu, :sub) do %>
  <%= kui(:dropdown_menu, :sub_trigger) { "More options" } %>
  <%= kui(:dropdown_menu, :sub_content) do %>
    <%= kui(:dropdown_menu, :item) { "Option A" } %>
    <%= kui(:dropdown_menu, :item) { "Option B" } %>
  <% end %>
<% end %>

Smart Tag (Item)

Items render as <button type="button"> by default. When href: is present, the item renders as <a>. When method: is also present (non-GET), it renders via Rails button_to.

<%# Link item — renders <a> %>
<%= kui(:dropdown_menu, :item, href: profile_path) do %>
  <%= kiso_icon("user") %>
  Profile
<% end %>

<%# Form submission — renders button_to %>
<%= kui(:dropdown_menu, :item, href: archive_path(@room), method: :patch) do %>
  <%= kiso_icon("archive") %>
  Archive
<% end %>

<%# Destructive form action %>
<%= kui(:dropdown_menu, :item, href: room_path(@room), method: :delete, variant: :destructive) do %>
  <%= kiso_icon("trash-2") %>
  Delete
<% end %>

Disabled Items

<%= kui(:dropdown_menu, :item, disabled: true) { "API" } %>

Theme

DropdownMenuContent = ClassVariants.build(
  base: "bg-background text-foreground z-50 min-w-32 max-h-80
         overflow-x-hidden overflow-y-auto rounded-md
         ring ring-inset ring-border p-1 shadow-md"
)

DropdownMenuItem = ClassVariants.build(
  base: "relative flex cursor-default items-center gap-2 rounded-sm
         px-2 py-1.5 text-sm outline-none select-none
         data-[highlighted]:bg-elevated data-[highlighted]:text-foreground
         data-[disabled]:pointer-events-none data-[disabled]:opacity-50 ...",
  variants: {
    variant: {
      default: "",
      destructive: "text-error data-[highlighted]:bg-error/10 ..."
    }
  }
)

Stimulus

The dropdown menu uses the kiso--dropdown-menu Stimulus controller for:

  • Open/close on trigger click
  • Keyboard navigation (arrow keys, Enter, Space, Escape)
  • Focus management (highlights move through items)
  • Sub-menu open on hover or right-arrow, close on left-arrow or Escape
  • Checkbox item toggle on click
  • Radio group exclusive selection
  • Click outside dismisses
  • Type-ahead character search
  • Deferred close on item select (actions on items fire before menu hides)

Register the controller via KisoUi.start(application) in your Stimulus setup.

Accessibility

  • Uses role="menu" on content, role="menuitem" on items
  • role="menuitemcheckbox" and role="menuitemradio" for toggle items
  • aria-haspopup="menu" and aria-expanded on the trigger
  • aria-checked on checkbox and radio items
  • aria-disabled on disabled items
  • Keyboard navigation: Arrow keys, Enter/Space to select, Escape to close
  • Focus is trapped within the menu when open