Accordion

A set of collapsible sections for showing one or many panels.

Native package

@milkui/core/accordion

React adapter

Shared primitive

React source
import * as Accordion from '@milkui/react/accordion';

export function Packages() {
  return (
    <Accordion.Root type="single" defaultValue="native" collapsible>
      <Accordion.Item value="native">
        <Accordion.Header>
          <Accordion.Trigger>Native package</Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Content>
          <div className="demo__content-inner">@milkui/core/accordion</div>
        </Accordion.Content>
      </Accordion.Item>
      <Accordion.Item value="react">
        <Accordion.Header>
          <Accordion.Trigger>React adapter</Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Content>
          <div className="demo__content-inner">@milkui/react/accordion</div>
        </Accordion.Content>
      </Accordion.Item>
      <Accordion.Item value="primitive">
        <Accordion.Header>
          <Accordion.Trigger>Shared primitive</Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Content>
          <div className="demo__content-inner">@milkui/core/primitive</div>
        </Accordion.Content>
      </Accordion.Item>
    </Accordion.Root>
  );
}

Features

  • Fragment links work before JavaScript loads.
  • Single or multiple item selection.
  • Controlled or uncontrolled value state.
  • Collapsible single-item mode.
  • Disabled roots and disabled items.
  • Vertical or horizontal orientation with direction support.

Anatomy

Triggers link to content marked hidden="until-found" before JavaScript. Following a link reveals that panel without resetting others. Enhancement adds toggle and keyboard behavior.

React anatomy
import * as Accordion from '@milkui/react/accordion';

<Accordion.Root>
  <Accordion.Item>
    <Accordion.Header>
      <Accordion.Trigger />
    </Accordion.Header>
    <Accordion.Content />
  </Accordion.Item>
</Accordion.Root>

API reference

Accordion.Root

PropTypeDefaultDescription
type"single" | "multiple""single"Selection mode.
valuestring | string[]undefinedControlled value.
defaultValuestring | string[]undefinedInitial uncontrolled value.
onValueChange(value) => voidundefinedValue change request callback.
collapsiblebooleanfalseAllow the open single item to close.
disabledbooleanfalseDisable every item.
orientation"vertical" | "horizontal""vertical"Keyboard orientation.
dir"ltr" | "rtl""ltr"Direction for horizontal navigation.
asChildbooleanfalseRender onto the child element.

Accordion.Item

PropTypeDefaultDescription
valuestringGenerated identifierSelection value; independent of the generated DOM ID.
disabledbooleanfalseDisable this item.
asChildbooleanfalseRender onto the child element.

Accordion.Header

PropTypeDefaultDescription
asChildbooleanfalseHeading wrapper for the trigger.

Accordion.Trigger

PropTypeDefaultDescription
asChildbooleanfalseLink enhanced with disclosure behavior.

Accordion.Content

PropTypeDefaultDescription
asChildbooleanfalseRender onto the child element.
Data attributeValues
[data-open]Present when the item is open
[data-disabled]Present when disabled
[mlk-accordion-root]Root marker
[mlk-accordion-item]Item marker
[mlk-accordion-header]Header marker
[mlk-accordion-trigger]Trigger marker
[mlk-accordion-content]Content marker
CSS variableDescription
--mlk-accordion-content-widthMeasured content width.
--mlk-accordion-content-heightMeasured content height.

Examples

Independent accordions

Disable JavaScript and reload, then open Returns and Privacy. Both remain visible. Links reveal panels without closing others. Enhancement adds independent single-selection toggling.

Delivery

Shipping

Shipping information.

Returns

Account

Billing

Billing information.

Privacy

React source
<Accordion.Root defaultValue="shipping" collapsible>
  <Accordion.Item value="shipping"><Accordion.Trigger>Shipping</Accordion.Trigger><Accordion.Content>Shipping information.</Accordion.Content></Accordion.Item>
  <Accordion.Item value="returns"><Accordion.Trigger>Returns</Accordion.Trigger><Accordion.Content>Returns information.</Accordion.Content></Accordion.Item>
</Accordion.Root>
<Accordion.Root defaultValue="billing" collapsible>
  <Accordion.Item value="billing"><Accordion.Trigger>Billing</Accordion.Trigger><Accordion.Content>Billing information.</Accordion.Content></Accordion.Item>
  <Accordion.Item value="privacy"><Accordion.Trigger>Privacy</Accordion.Trigger><Accordion.Content>Privacy information.</Accordion.Content></Accordion.Item>
</Accordion.Root>

Controlled value

The root requests value changes. Your state decides whether to accept them.

Usage

Controlled accordion content

API

React source
const [value, setValue] = React.useState('usage');
const [acceptRequests, setAcceptRequests] = React.useState(true);

<Accordion.Root
  type="single"
  value={value}
  onValueChange={(nextValue) => {
    if (acceptRequests) setValue(nextValue);
  }}
>
  <Accordion.Item value="usage">
    <Accordion.Header>
      <Accordion.Trigger>Usage</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Content>
      <div className="demo__content-inner">Controlled accordion content</div>
    </Accordion.Content>
  </Accordion.Item>
</Accordion.Root>

Multiple items

Multiple accordions can keep more than one item open at a time.

Native package

@milkui/core/accordion

React adapter

@milkui/react/accordion

Shared primitive

React source
<Accordion.Root type="multiple" defaultValue={['native', 'react']}>
  <Accordion.Item value="native">
    <Accordion.Header>
      <Accordion.Trigger>Native package</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Content>
      <div className="demo__content-inner">@milkui/core/accordion</div>
    </Accordion.Content>
  </Accordion.Item>
  <Accordion.Item value="react">
    <Accordion.Header>
      <Accordion.Trigger>React adapter</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Content>
      <div className="demo__content-inner">@milkui/react/accordion</div>
    </Accordion.Content>
  </Accordion.Item>
  <Accordion.Item value="primitive">
    <Accordion.Header>
      <Accordion.Trigger>Shared primitive</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Content>
      <div className="demo__content-inner">@milkui/core/primitive</div>
    </Accordion.Content>
  </Accordion.Item>
</Accordion.Root>

Horizontal orientation

Orientation is a root-level option for keyboard navigation and layout.

One

Horizontal item one.

Two

React source
<Accordion.Root
  className="accordion-horizontal"
  type="single"
  orientation="horizontal"
  defaultValue="one"
>
  <Accordion.Item value="one">
    <Accordion.Header>
      <Accordion.Trigger>One</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Content>
      <div className="demo__content-inner">Horizontal item one.</div>
    </Accordion.Content>
  </Accordion.Item>
  <Accordion.Item value="two">
    <Accordion.Header>
      <Accordion.Trigger>Two</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Content>
      <div className="demo__content-inner">Horizontal item two.</div>
    </Accordion.Content>
  </Accordion.Item>
</Accordion.Root>

Keyboard interactions

Before enhancement, triggers are links to sections. Once enhanced, Enter and Space toggle them without changing the URL.

KeyDescription
SpaceOpens or closes the focused trigger.
EnterOpens or closes the focused trigger.
ArrowDown / ArrowRightMoves focus to the next enabled trigger.
ArrowUp / ArrowLeftMoves focus to the previous enabled trigger.
HomeMoves focus to the first enabled trigger.
EndMoves focus to the last enabled trigger.