Pho Design System

Accordion

Questions that each reveal an answer: the FAQ on photon.codes, as a component. Built on Base UI Accordion. The list is bare, so the page decides the surface; on the site it is the section’s card, in a settings page a Card. Every item is independent, so several can be open at once.

Import

import { Accordion } from "@photon-ai/pho-ui/components/accordion";

Basic

Each Accordion.Item carries a value. Set the initially open items with defaultValue (an array), or drive the open set with value / onValueChange. The trigger takes the question and wears the chevron itself; the panel takes the answer.

Yes, you can self-host Photon and use your own phone number. We recommend Photon Cloud, which is free for personal use with up to 10 users: a managed phone number at no additional cost, and a fully managed experience.

<Accordion.Root defaultValue={["self-host"]}>
  <Accordion.Item value="self-host">
    <Accordion.Header>
      <Accordion.Trigger>Can I self-host Photon?</Accordion.Trigger>
    </Accordion.Header>
    <Accordion.Panel>Yes, you can self-host Photon…</Accordion.Panel>
  </Accordion.Item>
</Accordion.Root>

Sizes

Three sizes, sm, md, lg, one step of the type scale apart; the default is md. lg is the site’s FAQ to the pixel: the question at 20/28, the answer at 18/28, 20px above and below, 8px between. The chevron is the same small glyph in the same 24px box at every size.

Cards through Stripe, plus invoicing on the Business tier.

Cards through Stripe, plus invoicing on the Business tier.

Cards through Stripe, plus invoicing on the Business tier.
<Accordion.Root size="sm">…</Accordion.Root>
<Accordion.Root size="md">…</Accordion.Root>
<Accordion.Root size="lg">…</Accordion.Root>

Single open

Items are independent by default. Set multiple={false} and one panel opens as another closes; defaultValue (and value) still hold an array.

Cards through Stripe, plus invoicing on the Business tier.

<Accordion.Root multiple={false} defaultValue={["payment"]}>
  {/* items */}
</Accordion.Root>

Motion

The site’s springs, sampled as CSS easings. Opening, the panel’s height, the chevron’s half turn, and the question’s tone settle together on the section spring (--ease-pho-spring-reveal, stiffness 436, damping 42, 500ms). The answer arrives 100ms later on the appear spring (--ease-pho-spring-appear, 417 and 68, 700ms), fading in as it rises 20px. Closing, the answer is gone at once and the height collapses on the section spring.

Anatomy

Props

Set on Accordion.Root:

Set on Accordion.Item:

Accordion.Trigger renders a native <button>; Accordion.Panel accepts keepMounted and hiddenUntilFound to override the Root defaults per item.

Accessibility

Each Accordion.Header renders an <h3> that wraps the Accordion.Trigger <button>. The trigger carries aria-expanded and aria-controls pointing at its panel; the Accordion.Panel is a role="region" labelled by its trigger via aria-labelledby. Keep the surrounding heading levels sensible — if <h3> is wrong for the page, re-render the header as another heading with render.

Keyboard:

Base UI follows the updated APG guidance that removed roving focus, so triggers sit in the normal tab order — there is no arrow-key navigation between headers. A collapsed panel is removed from the accessibility tree (and the DOM, unless keepMounted or hiddenUntilFound is set).

Best practices