Collapsible Rails Components
Show and hide content sections with smooth animations. Perfect for expanding cards, showing additional details, or creating interactive content areas that users can toggle on demand.
Installation
1. Stimulus Controller Setup
Start by adding the following controller to your project:
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["content", "collapsedIcon", "expandedIcon"];
static values = {
open: Boolean,
animated: { type: Boolean, default: true },
};
connect() {
this.isOpen = this.openValue;
this.updateDisplay({ animate: false });
}
toggle() {
this.isOpen = !this.isOpen;
this.updateDisplay();
}
updateDisplay({ animate = true } = {}) {
if (!this.hasContentTarget) return;
const shouldAnimate =
animate && this.animatedValue && !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const content = this.contentTarget;
if (this.isOpen) {
content.style.maxHeight = shouldAnimate ? content.scrollHeight + "px" : "none";
content.style.opacity = "1";
content.setAttribute("data-state", "open");
this.element.setAttribute("data-state", "open");
} else {
content.style.maxHeight = "0";
content.style.opacity = "0";
content.setAttribute("data-state", "closed");
this.element.setAttribute("data-state", "closed");
}
this.updateIconDisplay();
this.openValue = this.isOpen;
}
updateIconDisplay() {
if (!this.hasCollapsedIconTarget || !this.hasExpandedIconTarget) return;
const collapsedIcon = this.collapsedIconTarget;
const expandedIcon = this.expandedIconTarget;
collapsedIcon.style.transition = "none";
expandedIcon.style.transition = "none";
collapsedIcon.style.filter = "";
expandedIcon.style.filter = "";
if (this.isOpen) {
collapsedIcon.style.opacity = "0";
expandedIcon.style.opacity = "1";
} else {
collapsedIcon.style.opacity = "1";
expandedIcon.style.opacity = "0";
}
}
}
Examples
Basic Collapsible
A simple collapsible component that shows/hides content with smooth animations. Features dual icons that automatically swap between collapsed and expanded states.
Rails developer starred 3 repositories
<div class="flex w-fit justify-center">
<div data-controller="collapsible" data-collapsible-open-value="false" data-state="closed" class="w-[350px] space-y-2">
<!-- Header with toggle button -->
<div class="flex items-center justify-between space-x-4">
<h4 class="text-sm font-semibold">Rails developer starred 3 repositories</h4>
<button type="button" class="relative flex items-center justify-center gap-1.5 rounded-lg bg-transparent p-1.5 -sm font-medium whitespace-nowrap text-neutral-800 transition-all duration-100 ease-in-out select-none hover:bg-neutral-100 hover:text-neutral-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-50 dark:hover:bg-neutral-600/50 dark:hover:text-white dark:focus-visible:outline-neutral-200" data-action="click->collapsible#toggle">
<!-- Collapsed state icon (visible by default) -->
<svg data-collapsible-target="collapsedIcon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" class="absolute size-4.5" style="opacity: 0;">
<g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor">
<polyline points="12.5 6.25 9 2.75 5.5 6.25"></polyline>
<polyline points="12.5 11.75 9 15.25 5.5 11.75"></polyline>
</g>
</svg>
<!-- Expanded state icon (hidden by default) -->
<svg data-collapsible-target="expandedIcon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" class="size-4.5" style="opacity: 0;">
<g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor">
<polyline points="5.5 3.5 9 7 12.5 3.5"></polyline>
<polyline points="5.5 14.5 9 11 12.5 14.5"></polyline>
</g>
</svg>
<span class="sr-only">Toggle</span>
</button>
</div>
<!-- Always visible content -->
<div class="rounded-md border border-neutral-200 bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-700/50 px-4 py-3 font-mono text-sm">rails/rails</div>
<!-- Collapsible content -->
<div data-collapsible-target="content" data-state="closed" class="space-y-2 overflow-hidden transition-all duration-300 ease-in-out" style="max-height: 0; opacity: 0;">
<div class="rounded-md border border-neutral-200 bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-700/50 px-4 py-3 font-mono text-sm">hotwired/stimulus</div>
<div class="rounded-md border border-neutral-200 bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-700/50 px-4 py-3 font-mono text-sm">hotwired/turbo-rails</div>
</div>
</div>
</div>
Collapsible with plus/minus icon
The same collapsible structure using plus/minus toggle icons.
Rails developer starred 3 repositories
<div class="flex w-fit justify-center">
<div data-controller="collapsible" data-collapsible-open-value="false" data-state="closed" class="w-[350px] space-y-2">
<!-- Header with toggle button -->
<div class="flex items-center justify-between space-x-4">
<h4 class="text-sm font-semibold">Rails developer starred 3 repositories</h4>
<button type="button" class="relative flex items-center justify-center gap-1.5 rounded-lg bg-transparent p-1.5 -sm font-medium whitespace-nowrap text-neutral-800 transition-all duration-100 ease-in-out select-none hover:bg-neutral-100 hover:text-neutral-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:text-neutral-50 dark:hover:bg-neutral-600/50 dark:hover:text-white dark:focus-visible:outline-neutral-200" data-action="click->collapsible#toggle">
<!-- Collapsed state icon (visible by default) -->
<svg data-collapsible-target="collapsedIcon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" class="absolute size-4.5" style="opacity: 0;">
<g fill="currentColor">
<path d="M14.75,9.75H3.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75H14.75c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path>
<path d="M9,15.5c-.414,0-.75-.336-.75-.75V3.25c0-.414,.336-.75,.75-.75s.75,.336,.75,.75V14.75c0,.414-.336,.75-.75,.75Z"></path>
</g>
</svg>
<!-- Expanded state icon (hidden by default) -->
<svg data-collapsible-target="expandedIcon" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18" class="size-4.5" style="opacity: 0;">
<g fill="currentColor">
<path d="M14.75,9.75H3.25c-.414,0-.75-.336-.75-.75s.336-.75,.75-.75H14.75c.414,0,.75,.336,.75,.75s-.336,.75-.75,.75Z"></path>
</g>
</svg>
<span class="sr-only">Toggle</span>
</button>
</div>
<!-- Always visible content -->
<div class="rounded-md border border-neutral-200 bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-700/50 px-4 py-3 font-mono text-sm">rails/rails</div>
<!-- Collapsible content -->
<div data-collapsible-target="content" data-state="closed" class="space-y-2 overflow-hidden transition-all duration-300 ease-in-out" style="max-height: 0; opacity: 0;">
<div class="rounded-md border border-neutral-200 bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-700/50 px-4 py-3 font-mono text-sm">hotwired/stimulus</div>
<div class="rounded-md border border-neutral-200 bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-700/50 px-4 py-3 font-mono text-sm">hotwired/turbo-rails</div>
</div>
</div>
</div>
Configuration
The collapsible component is powered by a Stimulus controller that provides smooth animations and flexible configuration options for creating interactive content sections.
Controller Setup
Basic collapsible structure with required data attributes:
<div data-controller="collapsible" data-collapsible-open-value="false" data-collapsible-animated-value="true">
<div class="flex items-center justify-between">
<h4>Content Title</h4>
<button data-action="click->collapsible#toggle">
<svg data-collapsible-target="collapsedIcon"><!-- Icon for collapsed state --></svg>
<svg data-collapsible-target="expandedIcon"><!-- Icon for expanded state --></svg>
</button>
</div>
<div data-collapsible-target="content" class="overflow-hidden transition-all duration-300">
<!-- Collapsible content goes here -->
</div>
</div>
Configuration Values
Targets
Actions
Animation Features
- Smooth Height Transitions: Content expands and collapses with smooth max-height animations
- Opacity Fading: Content fades in/out during transitions for polished visual effects
- Icon Switching: Automatic instant switching between collapsed and expanded icon states
-
State Attributes: Automatic
data-stateattribute management for custom styling
Styling with CSS
The component automatically sets data-state attributes that you can use for custom styling:
-
Container:
data-state="open"ordata-state="closed" -
Content:
data-state="open"ordata-state="closed"
Usage Tips
-
CSS Transitions: Add
transition-all duration-300to the content target for smooth animations -
Overflow Hidden: Use
overflow-hiddenon the content target to prevent content from showing during animations -
Icon Positioning: Use
position: absoluteon the overlaid icon target to keep both icon states aligned -
Initial State: Set the initial
data-collapsible-open-valuebased on your design needs -
Disable Motion: Set
data-collapsible-animated-value="false"to disable content transitions