Skip to main content

Accordion

Accordion is a widget that stacks sections of content under headers people can expand or collapse, such as FAQs or grouped settings.

Import

import { Accordion } from '@vortexlabs/vortex';
import type { AccordionProps } from '@vortexlabs/vortex';

Other links

Standard delivery takes three to five business days.
"use client";

import { Accordion, Stack } from "@vortexlabs/vortex";

export const AccordionExample = () => {
  return (
    <Accordion.Root defaultValue="shipping" divider gap={3}>
      <Accordion.Item value="shipping">
        <Accordion.Trigger paddingBlock={3}>
          When will my order arrive?
        </Accordion.Trigger>
        <Accordion.Content>
          <Stack paddingBottom={4}>
            Standard delivery takes three to five business days.
          </Stack>
        </Accordion.Content>
      </Accordion.Item>
      <Accordion.Item value="returns">
        <Accordion.Trigger paddingBlock={3}>Can I return an item?</Accordion.Trigger>
        <Accordion.Content>
          <Stack paddingBottom={4}>
            You can request a return within 30 days.
          </Stack>
        </Accordion.Content>
      </Accordion.Item>
    </Accordion.Root>
  );
};

Features

  • Supports single or multiple expanded sections.
  • Supports controlled values and initially expanded items.
  • Controls trigger sizes, spacing, and divider lines.
  • Can disable individual items.
  • Supports custom triggers with your own buttons.

Anatomy

<Accordion.Root>
  <Accordion.Item value="shipping">
    <Accordion.Trigger>Shipping</Accordion.Trigger>
    <Accordion.Content>Three to five business days.</Accordion.Content>
  </Accordion.Item>
  <Accordion.Item value="returns">
    <Accordion.Trigger>Returns</Accordion.Trigger>
    <Accordion.Content>Within 30 days.</Accordion.Content>
  </Accordion.Item>
</Accordion.Root>
Component Part
Description
Holds the expanded state and shared spacing.
Defines one section, identified by its value.
Expands or collapses its section.
Shows the section body while expanded.

Multiple Sections

The default type="single" allows one open item. Set type="multiple" to expand items independently and pass an array to defaultValue.

Delivery is free for orders over $75.
Keep the original packaging for your return.
"use client";

import { Accordion, Stack } from "@vortexlabs/vortex";

export const AccordionMultiple = () => {
  return (
    <Accordion.Root
      type="multiple"
      defaultValue={["delivery", "returns"]}
      divider
      gap={3}
    >
      <Accordion.Item value="delivery">
        <Accordion.Trigger paddingBlock={3}>Delivery</Accordion.Trigger>
        <Accordion.Content>
          <Stack paddingBottom={4}>
            Delivery is free for orders over $75.
          </Stack>
        </Accordion.Content>
      </Accordion.Item>
      <Accordion.Item value="returns">
        <Accordion.Trigger paddingBlock={3}>Returns</Accordion.Trigger>
        <Accordion.Content>
          <Stack paddingBottom={4}>
            Keep the original packaging for your return.
          </Stack>
        </Accordion.Content>
      </Accordion.Item>
    </Accordion.Root>
  );
};

Controlled State

defaultValue chooses the initially expanded section. Use value and onChange when another control needs to open or close an item. In single mode, null closes every item; multiple mode uses a string array.

Open section: none
"use client";

import { Accordion, Button, Stack, Text } from "@vortexlabs/vortex";
import React from "react";

export const AccordionControlled = () => {
  const [value, setValue] = React.useState<string | null>(null);
  return (
    <Stack gap={4}>
      <Stack direction="row" gap={3} wrap>
        <Button size="sm" onClick={() => setValue("details")}>Show details</Button>
        <Button size="sm" variant="secondary" onClick={() => setValue(null)}>
          Close all
        </Button>
      </Stack>
      <Accordion.Root value={value} onChange={setValue} gap={3}>
        <Accordion.Item value="details">
          <Accordion.Trigger>Order details</Accordion.Trigger>
          <Accordion.Content>Your order contains two items.</Accordion.Content>
        </Accordion.Item>
      </Accordion.Root>
      <Text size="body-sm-desktop">Open section: {value ?? "none"}</Text>
    </Stack>
  );
};

Sizes

Trigger size styles the label as md or lg. Set iconSize on the root to change every expand icon.

"use client";

import { Accordion, Stack } from "@vortexlabs/vortex";

export const AccordionSizes = () => {
  return (
    <Stack gap={5}>
      <Accordion.Root iconSize={4}>
        <Accordion.Item value="compact">
          <Accordion.Trigger size="md">Medium label and smaller icon</Accordion.Trigger>
          <Accordion.Content>Compact order information.</Accordion.Content>
        </Accordion.Item>
      </Accordion.Root>
      <Accordion.Root iconSize={{ xs: 5, md: 6 }}>
        <Accordion.Item value="large">
          <Accordion.Trigger size="lg">
            Large label and responsive icon
          </Accordion.Trigger>
          <Accordion.Content>More prominent order information.</Accordion.Content>
        </Accordion.Item>
      </Accordion.Root>
    </Stack>
  );
};

Spacing and Dividers

Use divider to separate items. Trigger padding sets the clickable area, and root gap adds space above expanded content. Pad the bottom of Content to keep text off the next divider.

Use the address associated with your payment method.
"use client";

import { Accordion, Stack } from "@vortexlabs/vortex";

export const AccordionSpacing = () => {
  return (
    <Accordion.Root divider gap={{ xs: 2, md: 4 }} defaultValue="billing">
      <Accordion.Item value="billing">
        <Accordion.Trigger paddingBlock={{ xs: 3, md: 4 }} paddingInline={2}>
          Billing address
        </Accordion.Trigger>
        <Accordion.Content>
          <Stack paddingInline={2} paddingBottom={4}>
            Use the address associated with your payment method.
          </Stack>
        </Accordion.Content>
      </Accordion.Item>
      <Accordion.Item value="payment">
        <Accordion.Trigger paddingBlock={{ xs: 3, md: 4 }} paddingInline={2}>
          Payment methods
        </Accordion.Trigger>
        <Accordion.Content>
          <Stack paddingInline={2} paddingBottom={4}>
            We accept credit and debit cards.
          </Stack>
        </Accordion.Content>
      </Accordion.Item>
    </Accordion.Root>
  );
};

Clickable Items

Set clickableItems on the root to toggle an item by clicking anywhere on it. Buttons, links, form controls, and text selection inside keep their own behavior. Add item padding and borderRadius to style items as cards. Target data-vortex-accordion-state and data-vortex-accordion-disabled to style open and disabled items.

"use client";

import { Accordion, Button, Stack } from "@vortexlabs/vortex";

const surface = {
  style: { background: "var(--vortex-color-background-surface-subtlest)" },
};

export const AccordionClickable = () => {
  return (
    <Accordion.Root clickableItems gap={3}>
      <Stack gap={3}>
        <Accordion.Item
          value="starter"
          padding={{ xs: 4, md: 5 }}
          borderRadius="md"
          attributes={surface}
        >
          <Accordion.Trigger size="md">Starter plan</Accordion.Trigger>
          <Accordion.Content>One project and up to three teammates.</Accordion.Content>
        </Accordion.Item>
        <Accordion.Item
          value="team"
          padding={{ xs: 4, md: 5 }}
          borderRadius="md"
          attributes={surface}
        >
          <Accordion.Trigger size="md">Team plan</Accordion.Trigger>
          <Accordion.Content>
            <Stack gap={3} align="start">
              Unlimited projects and shared workspaces.
              <Button size="sm">Compare plans</Button>
            </Stack>
          </Accordion.Content>
        </Accordion.Item>
      </Stack>
    </Accordion.Root>
  );
};

Disabled Items

Set disabled on an Item to prevent it from being toggled. Other items remain interactive.

"use client";

import { Accordion, Stack } from "@vortexlabs/vortex";

export const AccordionDisabled = () => {
  return (
    <Accordion.Root divider gap={3}>
      <Accordion.Item value="available">
        <Accordion.Trigger paddingBlock={3}>Delivery information</Accordion.Trigger>
        <Accordion.Content>
          <Stack paddingBottom={4}>Your delivery details appear here.</Stack>
        </Accordion.Content>
      </Accordion.Item>
      <Accordion.Item value="unavailable" disabled>
        <Accordion.Trigger paddingBlock={3}>
          Tracking available after dispatch
        </Accordion.Trigger>
        <Accordion.Content>
          <Stack paddingBottom={4}>
            Your tracking number appears after dispatch.
          </Stack>
        </Accordion.Content>
      </Accordion.Item>
    </Accordion.Root>
  );
};

Custom Triggers

Pass a render function to Trigger to use your own button. Pass its attributes to the button to keep the expanded state and panel link, and forward disabled.

"use client";

import { Accordion, Button } from "@vortexlabs/vortex";

export const AccordionCustom = () => {
  return (
    <Accordion.Root gap={3}>
      <Accordion.Item value="summary">
        <Accordion.Trigger>
          {(attributes, { active, disabled }) => (
            <Button
              type="button"
              size="sm"
              variant="secondary"
              disabled={disabled}
              attributes={attributes}
            >
              {active ? "Hide order summary −" : "Show order summary +"}
            </Button>
          )}
        </Accordion.Trigger>
        <Accordion.Content>Two items · Free delivery · $84 total</Accordion.Content>
      </Accordion.Item>
    </Accordion.Root>
  );
};

Accessibility

Key
Description
TabShiftTab
Move between enabled triggers and focusable content in expanded panels.
EnterSpace
Activate the focused trigger to expand or collapse its item.
Default triggers are buttons with aria-expanded and aria-controls. Custom triggers must retain the supplied attributes and a meaningful accessible name.