Collapsible

An interactive component which expands and collapses a panel.

Stimulus: shadcn--collapsible
collapsible/default
<%= render Shadcn::Collapsible.new(class_name: "w-full max-w-md space-y-2") do |collapsible| %>
  <% collapsible.with_trigger(variant: :outline, class_name: "w-full justify-between") do %>
    <span>@peduarte starred 3 repositories</span>
    <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 shrink-0 transition-transform duration-200 group-data-[state=open]:rotate-180" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="m6 9 6 6 6-6"></path>
    </svg>
  <% end %>

  <% collapsible.with_body do %>
    <div class="rounded-md border px-4 py-3 text-sm">
      @radix-ui/primitives
    </div>
  <% end %>
<% end %>

Installation

Add the component to your project:

rails generate shadcn:add collapsible

Usage

<div class="w-full max-w-md space-y-2">
  <%= render Shadcn::Collapsible.new do |collapsible| %>
    <% collapsible.with_trigger(variant: :outline) do %>
      Toggle
    <% end %>
    <% collapsible.with_body do %>
      <div class="rounded-md border px-4 py-3 text-sm">
        This is the collapsible content. Click the button above to toggle visibility.
      </div>
    <% end %>
  <% end %>
</div>

Examples

Basic

A simple collapsible with a trigger button and content area.

<div class="w-full max-w-md space-y-2">
  <%= render Shadcn::Collapsible.new do |collapsible| %>
    <% collapsible.with_trigger(variant: :outline) do %>
      Toggle
    <% end %>
    <% collapsible.with_body do %>
      <div class="rounded-md border px-4 py-3 text-sm">
        This is the collapsible content. Click the button above to toggle visibility.
      </div>
    <% end %>
  <% end %>
</div>

Initially Open

Set open: true to start with content expanded.

This content is visible by default because the open parameter is set to true.
<div class="w-full max-w-md space-y-2">
  <%= render Shadcn::Collapsible.new(open: true) do |collapsible| %>
    <% collapsible.with_trigger(variant: :outline, class_name: "w-full justify-between") do %>
      <span>Toggle Details</span>
      <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 shrink-0 transition-transform duration-200 group-data-[state=open]:rotate-180" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="m6 9 6 6 6-6"></path>
    </svg>
    <% end %>
    <% collapsible.with_body do %>
      <div class="rounded-md border px-4 py-3 text-sm">
        This content is visible by default because the <code class="rounded bg-muted px-1 py-0.5 font-mono text-sm">open</code> parameter is set to <code class="rounded bg-muted px-1 py-0.5 font-mono text-sm">true</code>.
      </div>
    <% end %>
  <% end %>
</div>

Disabled

Prevent toggling by setting disabled: true.

<div class="w-full max-w-md space-y-2">
  <%= render Shadcn::Collapsible.new(disabled: true) do |collapsible| %>
    <% collapsible.with_trigger(variant: :outline, class_name: "w-full justify-between") do %>
      <span>Disabled Collapsible</span>
      <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 shrink-0 transition-transform duration-200 group-data-[state=open]:rotate-180" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="m6 9 6 6 6-6"></path>
    </svg>
    <% end %>
    <% collapsible.with_body do %>
      <div class="rounded-md border px-4 py-3 text-sm">
        This content cannot be toggled because the collapsible is disabled.
      </div>
    <% end %>
  <% end %>
</div>

With Smooth Animation

The collapsible includes smooth height animations by default, powered by the Stimulus controller.

<div class="w-full max-w-md space-y-2">
  <%= render Shadcn::Collapsible.new do |collapsible| %>
    <% collapsible.with_trigger(variant: :outline) do %>
      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-2" aria-hidden="true">
        <path d="M8 3H5a2 2 0 0 0-2 2v3"></path>
        <path d="M21 8V5a2 2 0 0 0-2-2h-3"></path>
        <path d="M3 16v3a2 2 0 0 0 2 2h3"></path>
        <path d="M16 21h3a2 2 0 0 0 2-2v-3"></path>
      </svg>
      Show Details
    <% end %>
    <% collapsible.with_body do %>
      <div class="rounded-md border px-4 py-3 text-sm space-y-2">
        <p>The animation automatically adjusts to the content height.</p>
        <p>This creates a smooth, natural expand/collapse effect.</p>
        <p>The 200ms transition provides responsive feedback without feeling sluggish.</p>
      </div>
    <% end %>
  <% end %>
</div>

Settings Panel

A practical example showing a collapsible settings panel.

Account Settings

Manage your account preferences

<%= render Shadcn::Card.new(class_name: "w-full max-w-md") do |card| %>
  <% card.with_header do |header| %>
    <% header.with_title { "Account Settings" } %>
    <% header.with_description { "Manage your account preferences" } %>
  <% end %>
  <% card.with_content do %>
    <div class="space-y-4">
      <%= render Shadcn::Collapsible.new do |collapsible| %>
        <% collapsible.with_trigger(
          variant: nil,
          size: nil,
          class_name: "group flex w-full items-center justify-between rounded-md p-2 text-left transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
        ) do %>
          <span class="space-y-0.5">
            <span class="block text-sm font-medium">Notification Preferences</span>
            <span class="block text-sm text-muted-foreground">Configure how you receive notifications</span>
          </span>
          <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 shrink-0 transition-transform duration-200 group-data-[state=open]:rotate-180" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="m6 9 6 6 6-6"></path>
    </svg>
        <% end %>
        <% collapsible.with_body do %>
          <div class="pt-4 space-y-3">
            <div class="flex items-center justify-between">
              <span class="text-sm">Email notifications</span>
              <%= render Shadcn::Checkbox.new(id: "email-notif") %>
            </div>
            <div class="flex items-center justify-between">
              <span class="text-sm">Push notifications</span>
              <%= render Shadcn::Checkbox.new(id: "push-notif") %>
            </div>
            <div class="flex items-center justify-between">
              <span class="text-sm">SMS notifications</span>
              <%= render Shadcn::Checkbox.new(id: "sms-notif") %>
            </div>
          </div>
        <% end %>
      <% end %>
    </div>
  <% end %>
<% end %>

Nested Content

Collapsible works with complex nested content and other components.

<div class="w-full max-w-md space-y-2">
  <%= render Shadcn::Collapsible.new do |collapsible| %>
    <% collapsible.with_trigger(variant: :ghost, size: :sm, class_name: "w-full justify-between") do %>
      <span class="text-sm font-semibold">Project Files</span>
      <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 shrink-0 transition-transform duration-200 group-data-[state=open]:rotate-180" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
      <path d="m6 9 6 6 6-6"></path>
    </svg>
    <% end %>
    <% collapsible.with_body do %>
      <div class="rounded-md border divide-y">
        <div class="px-4 py-3 text-sm flex items-center justify-between">
          <span class="font-mono">src/components/</span>
          <%= render Shadcn::Badge.new(variant: :secondary) { "Directory" } %>
        </div>
        <div class="px-4 py-3 text-sm flex items-center justify-between">
          <span class="font-mono">README.md</span>
          <%= render Shadcn::Badge.new(variant: :outline) { "File" } %>
        </div>
        <div class="px-4 py-3 text-sm flex items-center justify-between">
          <span class="font-mono">package.json</span>
          <%= render Shadcn::Badge.new(variant: :outline) { "File" } %>
        </div>
      </div>
    <% end %>
  <% end %>
</div>

API Reference

Collapsible

Collapsible Props

Prop Type Default Description
open Boolean false Whether the collapsible starts in the expanded state
disabled Boolean false When true, prevents toggling the collapsible state
class_name String nil Additional CSS classes to apply to the container

CollapsibleTrigger

CollapsibleTrigger Props

Prop Type Default Description
variant Symbol, nil :ghost Button style variant from Shadcn::Button, or nil for custom styling
size Symbol, nil :sm Button size from Shadcn::Button, or nil for custom sizing
class_name String nil Additional CSS classes to apply to the trigger button

Slots

Slot Description
with_trigger Renders the button that toggles the collapsible. Pass trigger options such as variant:, size:, and class_name:.
with_body The collapsible content that is shown or hidden when toggled.

Stimulus Controller

This component requires JavaScript. The Stimulus controller shadcn--collapsible provides interactivity.

Manages the collapsible state and handles smooth expand/collapse animations with automatic height calculation.

Installation

Add to your config/importmap.rb:

pin "shadcn", to: "index.esm.js"

Then in your app/javascript/controllers/index.js:

import { Application } from "@hotwired/stimulus"
import { registerShadcnControllers } from "shadcn"

const application = Application.start()
registerShadcnControllers(application)

Or import just this controller:

import CollapsibleController from "shadcn-rails-stimulus/controllers/collapsible_controller"

application.register("shadcn--collapsible", CollapsibleController)

Targets

Name Description
trigger The button that toggles the state
content The content area that expands/collapses

Values

Name Type Default Description
open Boolean false Current open/closed state of the collapsible
disabled Boolean false Whether the collapsible is disabled

Actions

Action Description
toggle Toggles between open and closed states (respects disabled)
open Opens the collapsible (respects disabled)
close Closes the collapsible

TypeScript

Type definitions are included. Import types as needed:

import type { CollapsibleController } from "shadcn-rails-stimulus"

Accessibility

  • The trigger renders as a semantic button.
  • The trigger includes aria-expanded and keeps it synchronized as the collapsible opens and closes.
  • Trigger and content use data-state attributes to track open and closed states.
  • Content is hidden with the hidden attribute when collapsed.
  • Disabled state prevents interaction and renders a disabled trigger button.

Best Practices:

  • Use concise, descriptive trigger labels.
  • Provide visual indicators (icons, arrows) that show the current state
  • Ensure sufficient color contrast for all states