Accordion
Stable Stimulus:senren--accordion
FAQ
Add the gem, run senren:install, then copy components with senren:add.
No. Senren is Rails-native: ViewComponent, Turbo, Stimulus, and Tailwind.
The host app owns copied components and can edit them directly.
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/.../accordion_example.html.erb
<div class="w-full max-w-lg">
<%= render Senren::AccordionComponent.new(items: [
{ id: "install", title: "How do I install Senren?", content: "Add the gem, run senren:install, then copy components with senren:add." },
{ id: "runtime", title: "Does Senren use React?", content: "No. Senren is Rails-native: ViewComponent, Turbo, Stimulus, and Tailwind." },
{ id: "ownership", title: "Who owns copied code?", content: "The host app owns copied components and can edit them directly." }
]) %>
</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 accordion --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 accordion --client
At a glance #
Category
Navigation
Class name
Senren::AccordionComponent
Stimulus
senren--accordion
Variants
single, multiple
Depends on
—
Pairs with
card
Source #
app/components/senren/accordion_component.html.erb
<div <%= tag.attributes(**root_attrs("divide-y divide-[hsl(var(--senren-border))] rounded-(--senren-radius) border border-[hsl(var(--senren-border))] bg-[hsl(var(--senren-background))]", data: { controller: "senren--accordion", "senren--accordion-multiple-value": multiple? })) %>>
<% items.each_with_index do |item, index| %>
<% expanded = open_item?(item, index) %>
<section>
<h3>
<button type="button" class="flex w-full cursor-pointer items-center justify-between gap-4 px-4 py-3 text-left text-sm font-medium text-[hsl(var(--senren-foreground))] transition-colors hover:bg-[hsl(var(--senren-muted)/0.5)]" aria-expanded="<%= expanded %>" aria-controls="<%= item[:id] %>-panel" data-senren--accordion-target="trigger" data-panel-id="<%= item[:id] %>" data-action="click->senren--accordion#toggle">
<span><%= item[:title] %></span>
<span aria-hidden="true" class="text-[hsl(var(--senren-muted-foreground))]">+</span>
</button>
</h3>
<div id="<%= item[:id] %>-panel" data-senren--accordion-target="panel" data-panel-id="<%= item[:id] %>" <%= "hidden" unless expanded %> class="px-4 pb-4 text-sm leading-6 text-[hsl(var(--senren-muted-foreground))]">
<%= item[:content].presence || content %>
</div>
</section>
<% end %>
</div>
app/components/senren/accordion_component.rb
module Senren
class AccordionComponent < BaseComponent
VARIANTS = { single: '', multiple: '' }.freeze
SIZES = { md: '' }.freeze
def initialize(items: [], variant: :single, open: nil, class_name: nil, **html)
super(variant: variant, size: :md, class_name: class_name, **html)
@items = normalize_items(items)
@open = Array(open).map(&:to_s)
end
attr_reader :items, :open
def multiple? = variant == :multiple
def open_item?(item, index)
open.include?(item[:id]) || (open.empty? && index.zero?)
end
private
def normalize_items(items)
Array(items).map.with_index do |item, index|
source = item.is_a?(Hash) ? item : { title: item.to_s }
title = source[:title] || source['title'] || "Section #{index + 1}"
id = (source[:id] || source['id'] || title.to_s.parameterize).to_s
{ id: id, title: title, content: source[:content] || source['content'] }
end
end
end
end
AI agent rules #
Use for
- +FAQ
- +collapsible content sections
Avoid
- -primary navigation
Accessibility #
- Buttons toggle aria-expanded; panels use aria-hidden.