Accordion

Accordion renders Base UI’s accordion primitives from one items prop. It fills its available width, keeps triggers flush with their bordered panels, adds a panel-style border to each trigger, and smoothly transitions panels when they open or close.

Review the project scope and current status.

import { useState } from "react";
import { Accordion, View, Label } from "density-base-ui";

export default function AccordionExample() {
  const [expandedItems, setExpandedItems] = useState(["overview"]);

  return (
    <View align="start" gap="md" padding={false} style={{ maxWidth: "500px" }}>
      <Accordion
        value={expandedItems}
        onValueChange={setExpandedItems}
        items={[
          {
            id: "overview",
            label: "Overview",
            content: (
              <Label
                as="div"
                emphasis="secondary"
                size="md"
                style={{ padding: "0.5rem 0.75rem" }}
              >
                Review the project scope and current status.
              </Label>
            ),
          },
          {
            id: "activity",
            label: "Activity",
            content: (
              <Label
                as="div"
                emphasis="secondary"
                size="md"
                style={{ padding: "0.5rem 0.75rem" }}
              >
                Scan recent updates, comments, and handoffs.
              </Label>
            ),
          },
        ]}
      />
    </View>
  );
}

Set headerPadding and panelPadding to false to remove their named insets. Omit them or pass true to use the size-derived default; an item’s panelPadding overrides the Accordion value. Each item may set chevronPosition to start, end, or none; it defaults to none.

Bounded scrolling

Set fill when an Accordion fills a height-constrained parent. Set scroll on items whose expanded content should scroll independently; do not place the whole Accordion in a scrolling Panel.

import { useState } from "react";
import { Accordion, View, Label } from "density-base-ui";

const changes = [
  "Add compact status controls",
  "Document responsive table behavior",
  "Refine keyboard navigation",
  "Update package exports",
  "Remove obsolete preview styles",
  "Tighten component examples",
  "Review release checklist",
];

export default function AccordionScrollExample() {
  const [expandedItems, setExpandedItems] = useState(["changes", "history"]);

  return (
    <View
      align="start"
      gap="md"
      padding={false}
      style={{ height: "16rem", maxWidth: "500px" }}
    >
      <Accordion
        value={expandedItems}
        onValueChange={setExpandedItems}
        fill
        size="sm"
        items={[
          {
            id: "changes",
            label: "Changes",
            chevronPosition: "end",
            scroll: true,
            content: (
              <View gap="md" layout="grid" padding={false}>
                {changes.map((change) => (
                  <Label key={change}>{change}</Label>
                ))}
              </View>
            ),
          },
          {
            id: "history",
            label: "History",
            chevronPosition: "end",
            scroll: true,
            content: (
              <View gap="md" layout="grid" padding={false}>
                {changes.map((change) => (
                  <Label key={change}>{change}</Label>
                ))}
              </View>
            ),
          },
        ]}
      />
    </View>
  );
}

Depth

Accordion panel at depth 1.

Accordion panel at depth 2.

Accordion panel at depth 3.

Accordion panel at depth 4.
import { useState } from "react";
import { Accordion, View, Label } from "density-base-ui";

export default function AccordionDepthExample() {
  const [depthOneItems, setDepthOneItems] = useState(["1"]);
  const [depthTwoItems, setDepthTwoItems] = useState(["2"]);
  const [depthThreeItems, setDepthThreeItems] = useState(["3"]);
  const [depthFourItems, setDepthFourItems] = useState(["4"]);

  return (
    <View align="start" gap="md" padding={false} style={{ maxWidth: "500px" }}>
      <Accordion
        value={depthOneItems}
        onValueChange={setDepthOneItems}
        headerDepth="1"
        panelDepth="1"
        items={[
          {
            id: "1",
            label: "Depth 1",
            content: (
              <Label as="div" size="md" style={{ padding: "0.375rem 0.75rem" }}>
                Accordion panel at depth 1.
              </Label>
            ),
          },
        ]}
      />
      <Accordion
        value={depthTwoItems}
        onValueChange={setDepthTwoItems}
        headerDepth="2"
        panelDepth="2"
        items={[
          {
            id: "2",
            label: "Depth 2",
            content: (
              <Label as="div" size="md" style={{ padding: "0.375rem 0.75rem" }}>
                Accordion panel at depth 2.
              </Label>
            ),
          },
        ]}
      />
      <Accordion
        value={depthThreeItems}
        onValueChange={setDepthThreeItems}
        headerDepth="3"
        panelDepth="3"
        items={[
          {
            id: "3",
            label: "Depth 3",
            content: (
              <Label as="div" size="md" style={{ padding: "0.375rem 0.75rem" }}>
                Accordion panel at depth 3.
              </Label>
            ),
          },
        ]}
      />
      <Accordion
        value={depthFourItems}
        onValueChange={setDepthFourItems}
        headerDepth="4"
        panelDepth="4"
        items={[
          {
            id: "4",
            label: "Depth 4",
            content: (
              <Label as="div" size="md" style={{ padding: "0.375rem 0.75rem" }}>
                Accordion panel at depth 4.
              </Label>
            ),
          },
        ]}
      />
    </View>
  );
}

Padding

Flush disclosure content.

Default-sized disclosure content.
import { useState } from "react";
import { Accordion, View, Label } from "density-base-ui";

export default function AccordionPaddingExample() {
  const [unpaddedItems, setUnpaddedItems] = useState(["unpadded"]);
  const [paddedItems, setPaddedItems] = useState(["padded"]);

  return (
    <View align="start" gap="md" padding={false} style={{ maxWidth: "500px" }}>
      <Accordion
        value={unpaddedItems}
        onValueChange={setUnpaddedItems}
        headerPadding={false}
        panelPadding={false}
        items={[
          {
            id: "unpadded",
            label: "No padding",
            content: (
              <Label as="div" emphasis="secondary">
                Flush disclosure content.
              </Label>
            ),
          },
        ]}
      />
      <Accordion
        value={paddedItems}
        onValueChange={setPaddedItems}
        headerPadding
        panelPadding
        items={[
          {
            id: "padded",
            label: "Default padding",
            content: (
              <Label as="div" emphasis="secondary">
                Default-sized disclosure content.
              </Label>
            ),
          },
        ]}
      />
    </View>
  );
}

Chevron position

This item does not render a disclosure chevron.

This item renders its disclosure chevron before the label.

This item renders its disclosure chevron after the label.
import { useState } from "react";
import { Accordion, View } from "density-base-ui";

export default function AccordionChevronPositionExample() {
  const [noChevronItems, setNoChevronItems] = useState(["none"]);
  const [startChevronItems, setStartChevronItems] = useState(["start"]);
  const [endChevronItems, setEndChevronItems] = useState(["end"]);

  return (
    <View align="start" gap="md" style={{ width: "min(100%, 32rem)" }}>
      <Accordion
        value={noChevronItems}
        onValueChange={setNoChevronItems}
        items={[
          {
            id: "none",
            label: "No chevron (default)",
            content: "This item does not render a disclosure chevron.",
          },
        ]}
      />
      <Accordion
        value={startChevronItems}
        onValueChange={setStartChevronItems}
        items={[
          {
            id: "start",
            label: "Start chevron",
            chevronPosition: "start",
            content:
              "This item renders its disclosure chevron before the label.",
          },
        ]}
      />
      <Accordion
        value={endChevronItems}
        onValueChange={setEndChevronItems}
        items={[
          {
            id: "end",
            label: "End chevron",
            chevronPosition: "end",
            content:
              "This item renders its disclosure chevron after the label.",
          },
        ]}
      />
    </View>
  );
}

Size

Compact disclosure.

Default disclosure.

Roomier disclosure.
import { useState } from "react";
import { Accordion, View, Label } from "density-base-ui";

export default function AccordionSizeExample() {
  const [smallItems, setSmallItems] = useState(["sm"]);
  const [mediumItems, setMediumItems] = useState(["md"]);
  const [largeItems, setLargeItems] = useState(["lg"]);

  return (
    <View align="start" gap="md" padding={false} style={{ maxWidth: "500px" }}>
      <Accordion
        value={smallItems}
        onValueChange={setSmallItems}
        size="sm"
        items={[
          {
            id: "sm",
            label: "Small",
            content: (
              <Label as="div" emphasis="secondary">
                Compact disclosure.
              </Label>
            ),
          },
        ]}
      />
      <Accordion
        value={mediumItems}
        onValueChange={setMediumItems}
        size="md"
        items={[
          {
            id: "md",
            label: "Medium",
            content: (
              <Label as="div" emphasis="secondary">
                Default disclosure.
              </Label>
            ),
          },
        ]}
      />
      <Accordion
        value={largeItems}
        onValueChange={setLargeItems}
        size="lg"
        items={[
          {
            id: "lg",
            label: "Large",
            content: (
              <Label as="div" emphasis="secondary">
                Roomier disclosure.
              </Label>
            ),
          },
        ]}
      />
    </View>
  );
}