Collapsible
Collapsible is a component that shows or hides content.
This is the collapsed content. The element that shows and hides the content has role button
When the content is visible, the element with role `button` has `aria-expanded` set to `true`
When the content area is hidden, it is set to `false`
Optionally, the element with role `button` has a value specified for `aria-controls` that refers to the element that contains all the content that is shown or hidden
'use client';
import * as React from 'react';
import { styled, useTheme, Box } from '@mui/system';
import { Collapsible as BaseCollapsible } from '@base_ui/react/Collapsible';
const Collapsible = BaseCollapsible.Root;
const CollapsibleTrigger = styled(BaseCollapsible.Trigger)`
display: flex;
flex-flow: row nowrap;
justify-content: center;
gap: 4px;
font-size: 16px;
& svg {
margin-top: 1px;
}
&[data-state='open'] svg {
transform: rotate(180deg);
}
`;
const CollapsibleContent = styled(BaseCollapsible.Content)``;
export default function UnstyledCollapsibleIntroduction() {
// Replace this with your app logic for determining dark mode
const isDarkMode = useIsDarkMode();
const [open, setOpen] = React.useState(true);
return (
<Box
className={isDarkMode ? 'dark' : ''}
sx={{ width: 480, fontFamily: 'IBM Plex Sans, sans-serif' }}
>
<Collapsible open={open} onOpenChange={setOpen}>
<CollapsibleTrigger>
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
width="16"
height="16"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="m19.5 8.25-7.5 7.5-7.5-7.5"
/>
</svg>
Show {open ? 'less' : 'more'}
</CollapsibleTrigger>
<CollapsibleContent>
<p>
This is the collapsed content. The element that shows and hides the
content has role button
</p>
<p>
When the content is visible, the element with role `button` has
`aria-expanded` set to `true`
</p>
<p>When the content area is hidden, it is set to `false`</p>
<p>
Optionally, the element with role `button` has a value specified for
`aria-controls` that refers to the element that contains all the content
that is shown or hidden
</p>
</CollapsibleContent>
</Collapsible>
</Box>
);
}
function useIsDarkMode() {
const theme = useTheme();
return theme.palette.mode === 'dark';
}
Installation
Base UI components are all available as a single package.
npm install @base_ui/react
Once you have the package installed, import the component.
import { Collapsible } from '@base_ui/react/Collapsible';
Anatomy
<Collapsible.Root />
is a top-level component that facilitates communication between other components. It does not render to the DOM by default.<Collapsible.Trigger />
is the trigger element, a<button>
by default, that toggles the open/closed state of the content<Collapsible.Content />
is component that contains the Collapsible's content
Improving searchability of hidden content
This is not yet
supported in Safari and
Firefox as of August 2024 and will fall back to the default hidden
behavior.
Content hidden in the Collapsible.Content
component can be made accessible only to a browser's find-in-page functionality with the htmlHidden
prop to improve searchability:
We recommend using CSS animations for animated collapsibles that use this feature. Currently there is browser bug that does not highlight the found text inside elements that have a CSS transition applied.
This relies on the HTML hidden="until-found"
attribute which only has partial browser support as of August 2024, but automatically falls back to the default hidden
state in unsupported browsers.
Animations
Animation states
Four states are available as data attributes to animate the Collapsible:
[data-state="open"]
-open
state istrue
.[data-state="closed"]
-open
state isfalse
. Can still be mounted to the DOM if closing.[data-entering]
- thehidden
attribute was just removed from the DOM and the content element participates in page layout. Thedata-entering
attribute will be removed 1 animation frame later.[data-exiting]
- the content element is in the process of being hidden from the DOM, but is still mounted.
The component can be animate when opening or closing using either:
- CSS animations
- CSS transitions
- JavaScript animations
The height of the Content
subcomponent is provided as the --collapsible-content-height
CSS variable
CSS Animations
CSS animations can be used with two declarations:
This is the collapsed content
This is the second paragraph
This is a longer sentence and also the third paragraph
'use client';
import * as React from 'react';
import { useTheme } from '@mui/system';
import { Collapsible } from '@base_ui/react/Collapsible';
export default function CssAnimatedCollapsible() {
const [open, setOpen] = React.useState(true);
return (
<div className="CssAnimatedCollapsible">
<Collapsible.Root open={open} onOpenChange={setOpen}>
<Collapsible.Trigger className="CssAnimatedCollapsible-trigger">
<span className="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 80 80"
focusable="false"
>
<path d="M70.3 13.8L40 66.3 9.7 13.8z" />
</svg>
</span>
Show {open ? 'less' : 'more'}
</Collapsible.Trigger>
<Collapsible.Content className="CssAnimatedCollapsible-content">
<p>This is the collapsed content</p>
<p>This is the second paragraph</p>
<p>This is a longer sentence and also the third paragraph</p>
</Collapsible.Content>
</Collapsible.Root>
<Styles />
</div>
);
}
const grey = {
50: '#F3F6F9',
100: '#E5EAF2',
200: '#DAE2ED',
300: '#C7D0DD',
400: '#B0B8C4',
500: '#9DA8B7',
600: '#6B7A90',
700: '#434D5B',
800: '#303740',
900: '#1C2025',
};
function useIsDarkMode() {
const theme = useTheme();
return theme.palette.mode === 'dark';
}
export function Styles() {
const isDarkMode = useIsDarkMode();
return (
<style suppressHydrationWarning>{`
.CssAnimatedCollapsible {
font-family: system-ui, sans-serif;
line-height: 1.4;
width: 480px;
}
.CssAnimatedCollapsible h3 {
color: ${isDarkMode ? 'cyan' : 'blue'};
}
.CssAnimatedCollapsible-trigger {
border: 0;
padding: .5rem 1rem .5rem 0;
font: inherit;
background-color: transparent;
}
.CssAnimatedCollapsible-trigger:hover {
cursor: pointer;
}
.CssAnimatedCollapsible-trigger .icon {
display: inline-block;
font-size: 60%;
color: #000;
background-color: ${isDarkMode ? grey[50] : grey[700]};
padding: 0.3em 0.2em 0 0.2em;
border: 0;
border-radius: 50%;
line-height: 1;
text-align: center;
text-indent: 0;
transform: rotate(270deg);
margin-right: 0.6em;
}
.CssAnimatedCollapsible-trigger svg {
width: 1.25em;
height: 1.25em;
fill: ${isDarkMode ? grey[900] : grey[300]};
transition: transform 0.2s ease-in;
transform-origin: center 45%;
}
.CssAnimatedCollapsible-trigger[data-state="open"] svg {
transform: rotate(90deg);
}
.CssAnimatedCollapsible-content {
background-color: ${isDarkMode ? grey[700] : grey[300]};
overflow: hidden;
}
.CssAnimatedCollapsible-content p {
padding: 0 1rem;
}
.CssAnimatedCollapsible-content[data-state='open'] {
animation: slideDown 300ms ease-out;
}
.CssAnimatedCollapsible-content[data-state='closed'] {
animation: slideUp 300ms ease-out;
}
@keyframes slideDown {
from {
height: 0;
}
to {
height: var(--collapsible-content-height);
}
}
@keyframes slideUp {
from {
height: var(--collapsible-content-height);
}
to {
height: 0;
}
}
`}</style>
);
}
CSS Transitions
When using CSS transitions, styles for the Content
subcomponent must be applied to three states:
- The closed styles with
[data-state="closed"]
- The open styles with
[data-state="open"]
- The entering styles with
[data-entering]
This is the collapsed content
This is the second paragraph
This is a longer sentence and also the third paragraph
'use client';
import * as React from 'react';
import { useTheme } from '@mui/system';
import { Collapsible } from '@base_ui/react/Collapsible';
export default function CssTransitionCollapsible() {
const [open, setOpen] = React.useState(true);
return (
<div className="CssTransitionCollapsible">
<Collapsible.Root open={open} onOpenChange={setOpen}>
<Collapsible.Trigger className="CssTransitionCollapsible-trigger">
<span className="icon">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 80 80"
focusable="false"
>
<path d="M70.3 13.8L40 66.3 9.7 13.8z" />
</svg>
</span>
Show {open ? 'less' : 'more'}
</Collapsible.Trigger>
<Collapsible.Content className="CssTransitionCollapsible-content">
<p>This is the collapsed content</p>
<p>This is the second paragraph</p>
<p>This is a longer sentence and also the third paragraph</p>
</Collapsible.Content>
</Collapsible.Root>
<Styles />
</div>
);
}
const grey = {
50: '#F3F6F9',
100: '#E5EAF2',
200: '#DAE2ED',
300: '#C7D0DD',
400: '#B0B8C4',
500: '#9DA8B7',
600: '#6B7A90',
700: '#434D5B',
800: '#303740',
900: '#1C2025',
};
function useIsDarkMode() {
const theme = useTheme();
return theme.palette.mode === 'dark';
}
export function Styles() {
const isDarkMode = useIsDarkMode();
return (
<style suppressHydrationWarning>{`
.CssTransitionCollapsible {
font-family: system-ui, sans-serif;
line-height: 1.4;
width: 480px;
}
.CssTransitionCollapsible h3 {
color: ${isDarkMode ? 'cyan' : 'blue'};
}
.CssTransitionCollapsible-trigger {
border: 0;
padding: .5rem 1rem .5rem 0;
font: inherit;
background-color: transparent;
}
.CssTransitionCollapsible-trigger:hover {
cursor: pointer;
}
.CssTransitionCollapsible-trigger .icon {
display: inline-block;
font-size: 60%;
color: #000;
background-color: ${isDarkMode ? grey[50] : grey[700]};
padding: 0.3em 0.2em 0 0.2em;
border: 0;
border-radius: 50%;
line-height: 1;
text-align: center;
text-indent: 0;
transform: rotate(270deg);
margin-right: 0.6em;
}
.CssTransitionCollapsible-trigger svg {
width: 1.25em;
height: 1.25em;
fill: ${isDarkMode ? grey[900] : grey[300]};
transition: transform 0.2s ease-in;
transform-origin: center 45%;
}
.CssTransitionCollapsible-trigger[data-state="open"] svg {
transform: rotate(90deg);
}
.CssTransitionCollapsible-content {
background-color: ${isDarkMode ? grey[700] : grey[300]};
overflow: hidden;
}
.CssTransitionCollapsible-content p {
padding: 0 1rem;
}
.CssTransitionCollapsible-content[data-state='open'] {
height: var(--collapsible-content-height);
transition: height 200ms ease-out;
}
.CssTransitionCollapsible-content[data-state='closed'] {
height: 0;
transition: height 200ms ease-in;
}
.CssTransitionCollapsible-content[data-entering] {
height: 0;
}
`}</style>
);
}
JavaScript Animations
When using external libraries for animation, for example framer-motion
, be aware that Collapsible hides content using the html hidden
attribute in the closed state, and does not unmount the Collapsible.Content
subcomponent.
Overriding default components
Use the render
prop to override the rendered elements with your own components. The Collapsible.Root
component does not render an element to the DOM by default, but can do so with the render prop:
API Reference
CollapsibleRoot
Prop | Type | Default | Description |
---|---|---|---|
animated | bool | true | If true , the component supports CSS/JS-based animations and transitions. |
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
defaultOpen | bool | true | If true , the Collapsible is initially open. This is the uncontrolled counterpart of open . |
disabled | bool | false | If true , the component is disabled. |
onOpenChange | func | Callback fired when the Collapsible is opened or closed. | |
open | bool | If true , the Collapsible is initially open. This is the controlled counterpart of defaultOpen . | |
render | union | A function to customize rendering of the component. |
CollapsibleTrigger
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
render | union | A function to customize rendering of the component. |
CollapsibleContent
Prop | Type | Default | Description |
---|---|---|---|
className | union | Class names applied to the element or a function that returns them based on the component's state. | |
htmlHidden | enum | 'hidden' | The hidden state when closed |
render | union | A function to customize rendering of the component. |