Expander Documentation has not yet been reviewed by a human.
Expander keeps a section title visible while its content opens and closes. Use it for supporting details, settings groups, or sections that readers can reveal as needed. Select the header or focus it and press Enter or Space to toggle.
Priority and tone
Choose primary for a pronounced surface, secondary for a subtle filled box,
or tertiary for a transparent disclosure in a content column. tone colors
the header and filled surfaces using the current theme.
<Expander title="Primary" priority="primary" tone="brand"><Text>A pronounced section surface.</Text></Expander><Expander title="Secondary"><Text>A subtle section surface.</Text></Expander><Expander title="Tertiary" priority="tertiary"><Text>A disclosure in a content column.</Text></Expander>Title and level
A string or number title uses Expander’s type scale. level changes that scale
without adding heading semantics. Pass a Title or your own heading when the
section belongs in the document outline; that node owns its typography.
<Expander title="Section title" level={2}><Text>A larger title scale.</Text></Expander><Expander title="Supporting details" level={6}><Text>A compact title scale.</Text></Expander>Selecting a link, input, or enabled control in the title activates that control
without toggling the section. Selecting title text keeps the selection. Prefer
actions for independent header controls so they sit outside the toggle button.
Actions and indicators
Actions stay visible when the section is closed. Use them for operations that
should remain available without opening the content. indicatorPlacement="end"
places the disclosure mark after the actions. At the start, the chevron turns
from right to down; at the end, it turns from down to up.
Choose triangle for a filled arrow or plus for a plus that becomes a minus.
Custom artwork can rotate as one node or switch between closed and open
visuals. Indicator artwork stays decorative and adds no focus stop.
The demonstration uses a custom title, two indicator visuals, and an independent Download action. Open the section to see the action count.
import { Button, Expander, Icon, Text, Title } from '@antadesign/anta'import { useState } from 'react'
function ExpanderActionsDemo() { const [downloads, setDownloads] = useState(0) return ( <Expander title={<Title level={4}>Build artifacts</Title>} indicatorPlacement="end" indicator={{ closed: <Icon shape="plus" />, open: <Icon shape="minus" /> }} actions={<Button size="small" label="Download" onClick={() => setDownloads(count => count + 1)} />} > <Text>Download requests: {downloads}. The action leaves the disclosure state unchanged.</Text> </Expander> )}Round and outdent
Use round for a 1rem radius that stays constant when the section opens.
Use outdent with priority="tertiary" to align the title and body with
surrounding prose. The start indicator hangs in the gutter; an end indicator
stays inside the end edge.
<Expander title="Rounded section" round><Text>The corners keep their radius when expanded.</Text></Expander><Expander title="Aligned section" priority="tertiary" outdent defaultOpen><Text>The title and body align with the surrounding content.</Text></Expander>Disabled
disabled removes the toggle button from focus navigation and prevents toggling.
An open section stays open. Header actions remain available; disable those
controls separately when needed.
<Expander title="Disabled, closed" disabled><Text>Collapsed content.</Text></Expander><Expander title="Disabled, open" disabled defaultOpen><Text>Content remains visible.</Text></Expander>Open state
Without open, Expander owns its state. defaultOpen chooses its initial state.
Closing keeps the content mounted and removes it from focus navigation and the
accessibility tree. Reopening restores its visibility and interaction.
Call event.preventDefault() in onStateChange to reject an uncontrolled
request synchronously. The demonstration keeps the section open until you clear
the checkbox.
import { Box, Checkbox, Expander, Text } from '@antadesign/anta'import { useState } from 'react'
function CancelableExample() { const [keepOpen, setKeepOpen] = useState(true) return ( <Box display="grid" gap={12} style={{ width: '100%' }}> <Checkbox checked={keepOpen} onStateChange={(_, { next }) => setKeepOpen(next === true)}>Keep details open</Checkbox> <Expander title="Unsaved changes" defaultOpen onStateChange={(event, { next }) => { if (!next && keepOpen) event.preventDefault() }}> <Text>Clear the checkbox to allow this section to close.</Text> </Expander> </Box> )}Pass open to control the section. Update it with detail.next in
onStateChange to accept a request, or leave it unchanged to reject. The
following demonstration counts requests even when they are rejected.
import { Box, Button, Checkbox, Expander, Text } from '@antadesign/anta'import { useState } from 'react'
function ControlledExample() { const [open, setOpen] = useState(false) const [accept, setAccept] = useState(true) const [requests, setRequests] = useState(0) return ( <Box display="grid" gap={12} style={{ width: '100%' }}> <Checkbox checked={accept} onStateChange={(_, { next }) => setAccept(next === true)}>Accept state requests</Checkbox> <Expander title="Release details" open={open} onStateChange={(_, { next }) => { setRequests(count => count + 1) if (accept) setOpen(next) }}> <Text>The content stays mounted when closed.</Text> </Expander> <Text>State requests: {requests}</Text> <Button label="Show details" onClick={() => setOpen(true)} /> </Box> )}Accordion
Share one open item identifier across a group of controlled Expanders when only one section should be open at a time.
import { Box, Expander, Text } from '@antadesign/anta'import { useState } from 'react'
const items = [ { id: 'shipping', title: 'Shipping', body: 'Orders ship within two business days.' }, { id: 'returns', title: 'Returns', body: 'Returns are accepted within 30 days.' },]
function Accordion() { const [openId, setOpenId] = useState('shipping') return ( <Box display="grid" gap={1}> {items.map(item => ( <Expander key={item.id} title={item.title} open={openId === item.id} onStateChange={(_, { next }) => setOpenId(next ? item.id : null)}> <Text>{item.body}</Text> </Expander> ))} </Box> )} Component props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | ReactNode | — | Summary (header) content. A string is rendered with the level
type scale; pass a node (e.g. a <Title>) for full control or real
heading semantics in the document outline. |
| level? | 123456 | 5 | Heading type scale applied to a string title (mirrors <Title>'s
levels). Visual only — for outline semantics, pass a <Title> as
the title. |
| tone? | neutralbrandcriticalinfosuccesswarningstring | 'neutral' | Semantic tone, or any literal CSS color ('#ff1493', 'rebeccapurple')
for a one-off custom tone. Named tones re-point the text and (on filled
priorities) the surface to the matching palette; a custom color keeps
its hue while lightness/chroma are pinned. 'neutral' (the default) is
the same as omitting it. |
| priority? | primarysecondarytertiary | 'secondary' | Surface emphasis. secondary (the default) is a subtle fill;
primary is a more pronounced card; tertiary is transparent (the
bare disclosure). |
| outdent? | boolean | — | Align the title and body with surrounding content on a tertiary expander. With the default start indicator, the mark hangs in the gutter; an end indicator stays inside its edge. |
| actions? | ReactNode | — | Header actions rendered outside the toggle trigger. With an end indicator, actions sit between the title and indicator. |
| indicator? | ReactNode— | 'chevron' | Built-in disclosure mark, a decorative node, or separate closed/open
visuals. Other primitive values leave the indicator empty. Custom nodes
are passive; use actions for controls. |
| indicatorPlacement? | startend | 'start' | Place the disclosure mark before the title or after the actions. |
| disabled? | boolean | — | Disables the header: not clickable or focusable, hover affordance
off, text dimmed. The open state freezes as-is — disabling an open
expander keeps it open. actions stay live; disable them
separately if needed. |
| round? | booleannumberstring | — | Use a 1rem corner radius in both folded and expanded states, independent
of title level or header height. Pass a number (px) or CSS length string
for a custom radius. |
| open? | boolean | — | Controlled open state. When provided, the consumer owns open/close. Selecting
the summary requests a change through onStateChange; omit open for
uncontrolled use. |
| defaultOpen? | boolean | — | Initial open state for the uncontrolled case. |
| onStateChange? | (event, detail) => void | — | Fired before the open state changes. event is the cancelable
statechange — call event.preventDefault() to veto an uncontrolled
toggle (e.g. confirm before closing). detail.next is the requested
open state, detail.prev the current one (booleans). In controlled
mode, apply detail.next to open to accept, or do nothing to reject. |
Inherited props (children, className, id, slot, style, tabIndex)
| Prop | Type | Default | Description |
|---|---|---|---|
| children? | ReactNode | — | Child elements. When provided, replaces the component's default label/content. |
| className? | string | — | CSS class on the component's root element (merged with the component's own
classes). Use it directly for layout and positioning — grid/flex placement,
margins, alignment — rather than wrapping the component in a <div>/<span>. |
| id? | string | — | HTML id attribute. |
| slot? | string | — | Assigns the element to a named <slot> of a parent web component (e.g.
slot="header" inside a <Dialog>, slot="footer" inside a <Dialog>). |
| style? | CSSProperties | — | Inline styles on the component's root element. Set layout/positioning here
(or via className) directly on the component instead of adding a wrapper. |
| tabIndex? | number | — | Tab order. Set to -1 to skip the element when tabbing. |
Plus every standard DOM event handler (onClick,
onFocus, onKeyDown, …) and any
data-* or aria-* attribute — forwarded as-is to
the underlying <a-*> element.
Web Component
Supply direct named regions. Use a-expander-summary for the built-in title
scale, with level on that region. Use boxless a-expander-title for custom
title markup. Put controls in a-expander-actions and body content in
a-expander-details. Place a-expander-indicator before the title or after the
actions to choose its position. Set its shape to chevron, triangle, or
plus, or omit shape and supply custom artwork as children.
<a-expander default-state="open"> <a-expander-summary level="4">Build artifacts</a-expander-summary> <a-expander-actions> <a-button role="button" tabindex="0" size="small"> <a-button-label>Download</a-button-label> </a-button> </a-expander-actions> <a-expander-indicator shape="chevron" aria-hidden="true" inert></a-expander-indicator> <a-expander-details>The title and indicator toggle this section. Download is an independent action.</a-expander-details></a-expander>The element retains a native button and inert collapse region in shadow DOM.
It projects authored regions without changing their attributes, styles, ARIA,
or children. The default styling loads from external CSS. Import
@antadesign/anta/elements/a-expander for browser behavior; import the entries
for any nested Anta controls separately. The full bundle includes them.
state="open" or state="closed" controls the section. Without state,
default-state seeds it once; the omitted default is closed. Reconnection
retains applied state. statechange is a cancelable, non-bubbling request with
{ next, prev } in the open/closed vocabulary. Call preventDefault() to
reject an uncontrolled request. For controlled use, update state to accept
or leave it unchanged to reject. The element owns the native button’s expanded
and disabled states and the collapsed region’s inertness.
The same indicator element renders before and after registration. An omitted
indicator renders no artwork. JSX creates the built-in chevron automatically;
indicatorPlacement changes the region order.
One custom a-expander-indicator rotates with state. Two regions with
data-when="closed" and data-when="open" switch instead. Raw authors mark
all indicator artwork aria-hidden="true" and inert. The owned indicator container
also excludes its projected artwork from interaction and accessibility.
Omit unused regions. CSS hides empty actions regions. Whitespace counts as
content; JSX omits whitespace-only string actions. Mark an otherwise
unrecognized interactive title widget data-expander-ignore to prevent toggling.
A disabled title control behaves like title text; actions never toggle.
Named regions replace public slot attributes, and raw level moves from
a-expander to a-expander-summary. Raw indicator shape and placement now
belong to its region and DOM order. Existing shadow part overrides remain
supported. JSX props stay the same.
Named tones and boolean round need no inline styles. Custom tones and radii
use JSX-owned CSS variables. Under a CSP that blocks style attributes, supply
--expander-tone-source or --expander-round through external CSS. Raw authors
also use those variables on browsers without typed CSS attr().
Native details[data-anta] shares this component’s stylesheet and entry. Its
summary and open attribute retain native behavior; it does not use Anta’s
cancelable state contract. Keep its level attribute on details.
Styling
Use priority, tone, round, and outdent for the built-in appearance.
--expander-tone-source connects a custom tone to an application color, and
--expander-gutter sets the shared title/body inset. Style authored regions
directly and the owned button, actions row, indicator, or content through
::part(). Unlayered rules override Anta’s defaults without !important.
The classes below identify demonstrations. Substitute your own selectors.
Hide the indicator and reduce the title/body inset.
a-expander.hide-chevron > a-expander-indicator { display: none; }a-expander.hide-chevron { --expander-gutter: 12px; }Change header height and hover appearance while keeping its focus ring.
a-expander.header-tint::part(summary) { min-height: 40px; }a-expander.header-tint::part(summary):enabled:hover { background: var(--bg-3); }Respond to applied state without managing CSS classes.
a-expander.state-indicator > a-expander-indicator { color: var(--text-4); }a-expander.state-indicator:state(open) > a-expander-indicator { color: var(--text-1); }