Collapse
Material Design 3-inspired expandable/collapsible component with smooth animations
Collapse
Expand And Collapse Animation
Native details.collapse animates its content height in browsers supporting
::details-content and interpolate-size: allow-keywords. Controlled panels
animate when the application synchronizes .collapse-open/.show, hidden,
inert, and ARIA. Set inert immediately on closing and return focus to the
trigger if focus was inside. Discrete display transitions retain the closing
visual until the height transition finishes, without retaining keyboard access.
Reduced motion and older browsers use immediate disclosure. CSS does not update
ARIA or manage focus.
Animated native disclosure
Delivery details
Track your delivery and review its destination.
Review delivery<details class="collapse collapse-bordered"><summary class="collapse-trigger">Delivery details</summary><div class="collapse-content"><div class="collapse-inner"><p>Track your delivery and review its destination.</p><a href="#">Review delivery</a></div></div></details>Native And Controlled State
Native Collapse
Shipping information
Ships in two days.
<details class="collapse collapse-card">
<summary class="collapse-trigger">Shipping information</summary>
<div class="collapse-content"><p>Ships in two days.</p></div>
</details>details[open] is the native authority, even when compatibility classes conflict.
Native exclusive groups can share name; unsupported browsers remain independent.
Applications own any required exclusive-group fallback. Independent items omit name.
For non-details markup, .collapse-closed wins over .collapse-open and legacy
.show. Remove contradictory classes in your controller. Fade, slide and
horizontal variants use the same state contract; no outer item opens a nested
item. CSS does not synchronize accessibility or focus.
<div class="collapse collapse-closed" id="demo-controlled-collapse">
<button class="collapse-trigger" aria-expanded="false" aria-controls="demo-collapse-panel">Shipping</button>
<div class="collapse-content" id="demo-collapse-panel" hidden inert><p>Ships in two days.</p></div>
</div>
function setExpanded(item, expanded) {
const trigger = item.querySelector(':scope > .collapse-trigger');
const panel = item.querySelector(':scope > .collapse-content');
if (!expanded && panel.contains(document.activeElement)) trigger.focus();
trigger.setAttribute('aria-expanded', String(expanded));
item.classList.toggle('collapse-open', expanded);
item.classList.toggle('collapse-closed', !expanded);
item.classList.remove('show');
panel.hidden = !expanded;
panel.inert = !expanded;
}
const item = document.getElementById('demo-controlled-collapse');
item.querySelector(':scope > .collapse-trigger').addEventListener('click', () => {
const expanded = item.querySelector(':scope > .collapse-trigger').getAttribute('aria-expanded') !== 'true';
setExpanded(item, expanded);
});
Bind semantic button clicks to this setter. Controlled exclusive groups close direct siblings through the same setter. Initialize closed panels hidden/inert and restore focus before hiding an active descendant; CSS clipping alone is not sufficient. The style demos below use native disclosure; framework examples implement the controlled contract.
The Collapse component allows users to show and hide content sections with smooth animations. It’s perfect for FAQs, navigation menus, and content-heavy interfaces where space management is crucial.
Basic Usage
Basic Collapse
Click to toggle
This is the collapsible content that can be shown or hidden.
<details class="collapse" open="">
<summary class="collapse-trigger">
<span>Click to toggle</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>This is the collapsible content that can be shown or hidden.</p>
</div>
</details>States
Open State
Native disclosure uses the open attribute. Controlled markup uses collapse-open with the controller contract above:
Open State
Expanded Section
This content is visible by default.
<details class="collapse" open="">
<summary class="collapse-trigger">
<span>Expanded Section</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>This content is visible by default.</p>
</div>
</details>Closed State
Omit open for a closed native disclosure. Controlled markup uses collapse-closed and hidden/inert:
Closed State
Collapsed Section
This content is hidden by default.
<details class="collapse">
<summary class="collapse-trigger">
<span>Collapsed Section</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>This content is hidden by default.</p>
</div>
</details>Variants
Bordered Variant
Add a border to the collapse component:
Bordered Variant
Bordered Collapse
Content with a border around it.
<details class="collapse collapse-bordered">
<summary class="collapse-trigger">
<span>Bordered Collapse</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Content with a border around it.</p>
</div>
</details>Card Variant
Card variant with shadow and border:
Card Variant
Card Style Collapse
This collapse has a card appearance with shadow.
<details class="collapse collapse-card">
<summary class="collapse-trigger">
<span>Card Style Collapse</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>This collapse has a card appearance with shadow.</p>
</div>
</details>Ghost Variant
Minimal styling for a clean look:
Ghost Variant
Ghost Collapse
Minimal styling with transparent background.
<details class="collapse collapse-ghost">
<summary class="collapse-trigger">
<span>Ghost Collapse</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Minimal styling with transparent background.</p>
</div>
</details>Color Variants
Primary
Primary Color
Primary Collapse
Content with primary color theme.
<details class="collapse collapse-primary">
<summary class="collapse-trigger">
<span>Primary Collapse</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Content with primary color theme.</p>
</div>
</details>Secondary
Secondary Color
Secondary Collapse
Content with secondary color theme.
<details class="collapse collapse-secondary">
<summary class="collapse-trigger">
<span>Secondary Collapse</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Content with secondary color theme.</p>
</div>
</details>Tertiary
Tertiary Color
Tertiary Collapse
Content with tertiary color theme.
<details class="collapse collapse-tertiary">
<summary class="collapse-trigger">
<span>Tertiary Collapse</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Content with tertiary color theme.</p>
</div>
</details>Sizes
Small
Small Size
Small Collapse
Compact size for space-constrained layouts.
<details class="collapse collapse-sm">
<summary class="collapse-trigger">
<span>Small Collapse</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Compact size for space-constrained layouts.</p>
</div>
</details>Medium (Default)
Medium Size (Default)
Medium Collapse (Default)
Standard size for most use cases.
<details class="collapse">
<summary class="collapse-trigger">
<span>Medium Collapse (Default)</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Standard size for most use cases.</p>
</div>
</details>Large
Large Size
Large Collapse
Larger size for emphasis and better touch targets.
<details class="collapse collapse-lg">
<summary class="collapse-trigger">
<span>Large Collapse</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Larger size for emphasis and better touch targets.</p>
</div>
</details>Accordion Groups
Create an exclusive native group with a shared name. Older browsers without grouped-details support remain independent; an application can provide the fallback:
Accordion Group
Section 1
First section content is expanded by default.
Section 2
Second section content is collapsed.
Section 3
Third section content is collapsed.
<div class="collapse-group">
<details class="collapse" open="" name="demo-collapse-exclusive">
<summary class="collapse-trigger">
<span>Section 1</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>First section content is expanded by default.</p>
</div>
</details>
<details class="collapse" name="demo-collapse-exclusive">
<summary class="collapse-trigger">
<span>Section 2</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Second section content is collapsed.</p>
</div>
</details>
<details class="collapse" name="demo-collapse-exclusive">
<summary class="collapse-trigger">
<span>Section 3</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Third section content is collapsed.</p>
</div>
</details>
</div>Animation Variants
Fade Animation
Smooth fade in/out effect:
Fade Animation
Fade Animation
Content fades in and out smoothly.
<details class="collapse collapse-fade">
<summary class="collapse-trigger">
<span>Fade Animation</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Content fades in and out smoothly.</p>
</div>
</details>Slide Animation
Slide with transform effect:
Slide Animation
<details class="collapse collapse-slide">
<summary class="collapse-trigger">
<span>Slide Animation</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Content slides in from above with a transform.</p>
</div>
</details>Additional Features
With Divider
Add a divider between trigger and content:
With Divider
With Divider
Content separated by a divider line.
<details class="collapse collapse-divider">
<summary class="collapse-trigger">
<span>With Divider</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Content separated by a divider line.</p>
</div>
</details>Disabled State
Prevent interaction with the collapse:
Disabled State
This collapse cannot be toggled.
<div class="collapse collapse-disabled collapse-closed">
<button class="collapse-trigger" disabled="" aria-expanded="false">
<span>Disabled Collapse</span>
<span class="collapse-icon">▼</span>
</button>
<div class="collapse-content" hidden="" inert="">
<p>This collapse cannot be toggled.</p>
</div>
</div>Loading State
Show a loading indicator:
Loading State
Loading Content
Content is being loaded...
<details class="collapse collapse-loading">
<summary class="collapse-trigger">
<span>Loading Content</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Content is being loaded...</p>
</div>
</details>Nested Collapse
Collapse components can be nested within each other:
Nested Collapse
Parent Section
Parent content with nested collapse below:
Nested Section
Nested collapse content with reduced padding.
<details class="collapse" open="">
<summary class="collapse-trigger">
<span>Parent Section</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Parent content with nested collapse below:</p>
<details class="collapse">
<summary class="collapse-trigger">
<span>Nested Section</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Nested collapse content with reduced padding.</p>
</div>
</details>
</div>
</details>JavaScript Integration
This controlled example runs an application controller. The complete setter in Native And Controlled State synchronizes classes, ARIA, hidden/inert and focus; importing Collapse CSS does not attach handlers.
This panel is synchronized by an application controller.
Controller contractBest Practices
Accessibility
- Use native
<summary>triggers, or semantic<button>triggers with an application controller - Provide clear, descriptive labels
- Ensure keyboard navigation support (Enter/Space to toggle)
- Native details supplies disclosure semantics; controlled triggers must synchronize ARIA
Accessible Collapse
Accessible Collapse
Content with proper ARIA attributes.
<details class="collapse">
<summary class="collapse-trigger">
<span>Accessible Collapse</span>
<span class="collapse-icon" aria-hidden="true">▼</span>
</summary>
<div class="collapse-content" id="collapse-content-1">
<p>Content with proper ARIA attributes.</p>
</div>
</details>Performance
- Keep content lightweight for smooth animations
- Avoid heavy computations during expand/collapse
- Use CSS transforms for better performance
- Consider lazy loading content for large datasets
UX Guidelines
- Clear Visual Feedback: Use icons to indicate expanded/collapsed state
- Smooth Animations: Default 300ms ease-in-out provides good balance
- Touch Targets: Ensure triggers are at least 44×44px for mobile
- Context Preservation: Consider scroll position when toggling
Framework Examples
React
import { useId, useState } from 'react';
interface CollapseProps {
title: string;
children: React.ReactNode;
defaultOpen?: boolean;
variant?: 'default' | 'bordered' | 'card' | 'ghost';
color?: 'default' | 'primary' | 'secondary' | 'tertiary';
size?: 'sm' | 'md' | 'lg';
}
export function Collapse({
title,
children,
defaultOpen = false,
variant = 'default',
color = 'default',
size = 'md'
}: CollapseProps) {
const [isOpen, setIsOpen] = useState(defaultOpen);
const panelId = useId();
const classes = [
'collapse',
isOpen ? 'collapse-open' : 'collapse-closed',
variant !== 'default' ? `collapse-${variant}` : '',
color !== 'default' ? `collapse-${color}` : '',
size !== 'md' ? `collapse-${size}` : ''
].filter(Boolean).join(' ');
return (
<div className={classes}>
<button
className="collapse-trigger"
onClick={() => setIsOpen(!isOpen)}
aria-expanded={isOpen}
aria-controls={panelId}
>
<span>{title}</span>
<span className="collapse-icon">▼</span>
</button>
<div className="collapse-content" id={panelId} hidden={!isOpen} inert={!isOpen}>
{children}
</div>
</div>
);
}
// Usage
<Collapse title="FAQ Item" variant="bordered" color="primary">
<p>Answer to the frequently asked question.</p>
</Collapse>
Vue
<template>
<div :class="collapseClasses">
<button
class="collapse-trigger"
@click="toggle"
:aria-expanded="isOpen"
:aria-controls="panelId"
>
<span>{{ title }}</span>
<span class="collapse-icon">▼</span>
</button>
<div class="collapse-content" :id="panelId" :hidden="!isOpen" :inert="!isOpen">
<slot />
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, useId } from 'vue';
interface Props {
title: string;
defaultOpen?: boolean;
variant?: 'default' | 'bordered' | 'card' | 'ghost';
color?: 'default' | 'primary' | 'secondary' | 'tertiary';
size?: 'sm' | 'md' | 'lg';
}
const props = withDefaults(defineProps<Props>(), {
defaultOpen: false,
variant: 'default',
color: 'default',
size: 'md'
});
const isOpen = ref(props.defaultOpen);
const panelId = useId(); // Vue 3.5+; older applications must supply a unique ID.
const collapseClasses = computed(() => [
'collapse',
isOpen.value ? 'collapse-open' : 'collapse-closed',
props.variant !== 'default' ? `collapse-${props.variant}` : '',
props.color !== 'default' ? `collapse-${props.color}` : '',
props.size !== 'md' ? `collapse-${props.size}` : ''
].filter(Boolean).join(' '));
const toggle = () => {
isOpen.value = !isOpen.value;
};
</script>
<!-- Usage -->
<Collapse title="FAQ Item" variant="bordered" color="primary">
<p>Answer to the frequently asked question.</p>
</Collapse>
Svelte
<script lang="ts">
export let title: string;
export let panelId: string; // Supply a unique ID per instance.
export let defaultOpen = false;
export let variant: 'default' | 'bordered' | 'card' | 'ghost' = 'default';
export let color: 'default' | 'primary' | 'secondary' | 'tertiary' = 'default';
export let size: 'sm' | 'md' | 'lg' = 'md';
let isOpen = defaultOpen;
$: collapseClasses = [
'collapse',
isOpen ? 'collapse-open' : 'collapse-closed',
variant !== 'default' ? `collapse-${variant}` : '',
color !== 'default' ? `collapse-${color}` : '',
size !== 'md' ? `collapse-${size}` : ''
].filter(Boolean).join(' ');
</script>
<div class={collapseClasses}>
<button
class="collapse-trigger"
on:click={() => isOpen = !isOpen}
aria-expanded={isOpen}
aria-controls={panelId}
>
<span>{title}</span>
<span class="collapse-icon">▼</span>
</button>
<div class="collapse-content" id={panelId} hidden={!isOpen} inert={!isOpen}>
<slot />
</div>
</div>
<!-- Usage -->
<Collapse panelId="demo-svelte-faq-panel" title="FAQ Item" variant="bordered" color="primary">
<p>Answer to the frequently asked question.</p>
</Collapse>
API Reference
Class Names
| Class | Description |
|---|---|
.collapse |
Base collapse container (required) |
.collapse-open |
Expanded state - shows content |
.collapse-closed |
Collapsed state - hides content |
.collapse-trigger |
Clickable trigger/header element |
.collapse-icon |
Icon that rotates on toggle |
.collapse-content |
Content area that expands/collapses |
.collapse-bordered |
Adds border around collapse |
.collapse-card |
Card style with shadow and border |
.collapse-ghost |
Minimal styling, transparent background |
.collapse-primary |
Primary color theme |
.collapse-secondary |
Secondary color theme |
.collapse-tertiary |
Tertiary color theme |
.collapse-sm |
Small size variant |
.collapse-lg |
Large size variant |
.collapse-group |
Container for accordion groups |
.collapse-fade |
Fade animation effect |
.collapse-slide |
Slide animation with transform |
.collapse-divider |
Adds divider between trigger and content |
.collapse-disabled |
Disables interaction |
.collapse-loading |
Shows loading spinner |
Animation Details
The collapse component uses the following animation timing:
- Duration: 300ms
- Easing: ease-in-out
- Icon Rotation: 180deg on expand
- Fade Transition: Opacity changes over 200ms
- Slide Transform: translateY(-0.5rem) when collapsed
Combinations
Combine classes for customized appearance:
Combined Variants
Custom Collapse
Combined styling options.
Enhanced Collapse
Multiple variant combinations.
<!-- Bordered primary collapse, large size -->
<details class="collapse collapse-bordered collapse-primary collapse-lg">
<summary class="collapse-trigger">
<span>Custom Collapse</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Combined styling options.</p>
</div>
</details>
<!-- Card with fade animation and divider -->
<details class="collapse collapse-card collapse-fade collapse-divider">
<summary class="collapse-trigger">
<span>Enhanced Collapse</span>
<span class="collapse-icon">▼</span>
</summary>
<div class="collapse-content">
<p>Multiple variant combinations.</p>
</div>
</details>Related Components
- Accordion - Multiple collapse components working together
- Card - Container component
- List - List items with expandable content
- Tabs - Alternative content organization