Skip to main content

Menu Button

Sidebar-style button with an avatar and title/subtitle that opens a dropdown panel, for workspace or account switchers. Server-rendered ERB with a small Stimulus controller.

Usage

components/menu_button renders the trigger and yields its id, which you pass to components/dropdown — the panel that pairs with it — so the trigger and panel are wired together with aria-labelledby.

<%= render "components/menu_button",
  title: "Acme Inc",
  subtitle: "Enterprise",
  text_icon: "A",
  submenu: true do |menu_id| %>
  <%= render "components/dropdown", id: menu_id, side: :bottom do %>
    <%= render "components/dropdown_menu/item", href: settings_path do %>Settings<% end %>
    <%= render "components/dropdown_menu/item", href: session_path, method: :delete do %>Sign out<% end %>
  <% end %>
<% end %>

The panel has no item styling of its own; dropdown_menu/item, dropdown_menu/label and dropdown_menu/separator are styled wherever they appear, so they work inside it. Keyboard navigation between items is a Dropdown Menu feature and is not provided here.

Examples

In the Sidebar Header

<%= render "components/sidebar/header" do %>
  <%= render "components/menu_button",
    title: "Máquina",
    subtitle: "Components",
    icon: "logo.svg",
    icon_classes: "size-8 rounded-md" do %>
  <% end %>
<% end %>

When the sidebar collapses to icons (collapsible: :icon), the trigger shrinks to a 2rem square.

Without a Panel

Pass an empty block when the button is purely decorative. The controller only opens when a components/dropdown panel is present.

<%= render "components/menu_button", title: "Acme Inc", text_icon: "A" do %><% end %>

Placement

The panel opens on side: (:top by default) and flips to the other side when it would run past the viewport and the opposite side has room. align: :start anchors its left edge to the trigger, align: :end its right edge. The panel is a fixed 14rem wide.

API Reference

Menu Button

Renders <ul data-component="menu-button"> containing one <li> that carries the menu-button controller, the trigger <button>, and the block.

Parameter Type Default Description
title String "" Primary text; also the image alt
subtitle String nil Secondary text
icon String nil Image path/URL for the avatar (image_tag)
text_icon String nil Text for the avatar square, used when icon is blank
icon_classes String "" Classes for the image or the text-icon span
submenu Boolean false Shows a trailing up/down chevron
id String "menu-button-#{title.parameterize}" Base id ("menu-button-menu" when title is blank). The trigger gets #{id}-trigger; the block receives id
css_classes String "" Additional CSS classes
html_options Hash {} Additional HTML attributes (on the <ul>)

Dropdown (components/dropdown)

Renders <div role="menu" hidden> with data-menu-button-part="content". It must be rendered inside the menu_button block.

Parameter Type Default Description
id String nil Pass the id yielded by menu_button. Sets the panel id to #{id}-content and aria-labelledby to #{id}-trigger
side Symbol :top :top or :bottom — a preference; see Placement above
align Symbol :start :start or :end
css_classes String "" Additional CSS classes
html_options Hash {} Additional HTML attributes

Stimulus Controller

menu-button, attached to the <li>.

Target Element
button The trigger
content The components/dropdown panel
Value Type Default Description
open Boolean false Current state
Action Description
toggle Bound on the trigger as menu-button#toggle
open / close Programmatic open and close

While open, the panel closes on outside click and Escape, and focus returns to the trigger. The controller keeps aria-expanded and data-state (open / closing / closed) in sync on the trigger and panel, and resets to closed before Turbo caches the page.