Dropdown Menu
Stable Stimulus:senren--dropdown-menu
contextual actions on rows
Usage example #
Copy this ERB into a Rails view after installing the component. The snippet below is the same code used by the live preview above.
app/views/.../dropdown_menu_example.html.erb
<div class="flex w-full justify-center">
<%= render Senren::DropdownMenuComponent.new do |menu| %>
<% menu.with_trigger do %>
<%= render Senren::ButtonComponent.new(variant: :secondary) do %>
<span>Open menu</span>
<svg aria-hidden="true" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="ml-2 h-4 w-4 transition-transform duration-150 data-[state=open]:rotate-180" data-state="closed" data-senren-chevron>
<path d="m5 8 5 5 5-5"></path>
</svg>
<% end %>
<% end %>
<% menu.with_item(href: "#") { "Profile" } %>
<% menu.with_item(href: "#") { "Settings" } %>
<% menu.with_item(href: "#") { "Billing" } %>
<% menu.with_item(href: "#", destructive: true) { "Sign out" } %>
<% end %>
</div>
Install this component #
Copy the official component into your app
This component requires Stimulus. Keep --client so the controller is copied with the ViewComponent.
Terminal
bin/rails senren:add dropdown_menu --client
Create a custom component with the same conventions
Use this when you need an app-specific component that follows Senren's ViewComponent and Stimulus structure. --client is required for this behavior.
Terminal
bin/rails generate senren:component dropdown_menu --client
Dependencies are resolved by senren:add:
button.
At a glance #
Category
Overlays
Class name
Senren::DropdownMenuComponent
Stimulus
senren--dropdown-menu
Variants
default
Depends on
button
Pairs with
button, table, top_nav
Source #
app/components/senren/dropdown_menu_component.html.erb
<div data-controller="senren--dropdown-menu" class="relative inline-block" data-state="closed" data-senren-component="dropdown_menu">
<div class="inline-flex cursor-pointer" data-state="closed" aria-haspopup="menu" aria-expanded="false" data-senren--dropdown-menu-target="trigger" data-action="click->senren--dropdown-menu#toggle keydown->senren--dropdown-menu#onTriggerKey">
<%= trigger %>
</div>
<div data-senren--dropdown-menu-target="menu" role="menu" hidden
class="absolute right-0 z-50 mt-2 w-56 rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-popover))] text-[hsl(var(--senren-popover-foreground))] p-1 shadow-md">
<% items.each do |it| %>
<%= it %>
<% end %>
</div>
</div>
app/components/senren/dropdown_menu_component.rb
# frozen_string_literal: true
module Senren
class DropdownMenuComponent < BaseComponent
renders_one :trigger
renders_many :items, 'ItemTag'
VARIANTS = { default: '' }.freeze
SIZES = { md: '' }.freeze
def initialize(class_name: nil, **html)
super(variant: :default, size: :md, class_name: class_name, **html)
end
class ItemTag < ViewComponent::Base
def initialize(href: nil, method: nil, destructive: false, **opts)
@href = href
@method = method
@destructive = destructive
@opts = opts
end
def call
klass = 'block w-full text-left px-3 py-2 text-sm rounded-sm hover:bg-[hsl(var(--senren-accent))] focus:bg-[hsl(var(--senren-accent))] outline-none cursor-pointer'
klass += ' text-[hsl(var(--senren-destructive))]' if @destructive
if @href
link_to(content, @href, role: 'menuitem', method: @method, class: klass,
data: { action: 'click->senren--dropdown-menu#close keydown->senren--dropdown-menu#onItemKey' })
else
tag.button(content, type: 'button', role: 'menuitem', class: klass,
data: { action: 'click->senren--dropdown-menu#close keydown->senren--dropdown-menu#onItemKey' }, **@opts)
end
end
end
end
end
AI agent rules #
Use for
- +contextual actions on rows
- +account menus
Avoid
- -primary navigation - use top_nav or sidebar
Accessibility #
- role=menu with role=menuitem children.
- Arrow keys to navigate; Escape to close; type-ahead optional.