Components · Utilities

Collapsible

A row whose content hides until the user asks for it. The system's progressive-disclosure primitive — used inside FAQs, settings groups, onboarding checklists, and any surface where a long page benefits from being read in pieces.

Documentedby Derek Fidler

Frequently asked

Settlement and payouts

Funds for transactions captured before 22:00 CET land in your settlement account by 09:00 CET the next business day. Weekends and bank holidays push the next available banking day.

Visa, Mastercard, American Express, Maestro, V Pay, Cartes Bancaires, Dankort, BankAxept. Local schemes are activated based on the markets you operate in.

Yes — up to 8 ways. The split is recorded against the original transaction so refunds and chargebacks resolve to the correct portion automatically.

We hold the disputed amount, surface the case in the portal with the issuer's reason code, and walk you through the evidence we'll need within five working days.

Overview

A Collapsible is a single trigger row paired with a panel that height-animates open and closed. Three types cover the surfaces in the product: body for inline disclosure, heading for top-level section headers, and checklist for confirmable list items. The wrapper CollapsibleGroup stacks them into an accordion, with optional single-open behaviour when only one panel should be visible at a time.

Use it for content the user can ignore

Progressive disclosure earns its place when the panel content is optional or secondary — definitions, advanced settings, answers the user might not need. If every panel needs to be opened to make sense of the page, the collapsible is hiding something the user is going to ask for anyway. Show it instead.

Anatomy

A trigger row with three slots, an optional panel underneath, and a 1 px divider that runs across the bottom (suppressed on the last item).

Address verification is pending. Upload a recent utility bill or bank statement to complete the check.

  1. Title

    Inter Tight Medium 16 / 24 (body), Bold 24 / 32 (heading), or Regular 16 / 24 paired with a 20 px checkbox (checklist). Lives in the row's leading slot, takes the remaining width.

  2. Status slot (optional)

    A short pill — typically a tone-warning Badge — that announces the row needs attention. Sits between the title and the trigger glyph; never used for decoration.

  3. Trigger glyph

    24 px Material icon. Body and heading types use add / remove; checklist uses expand_more rotated 180° on open.

  4. Divider

    1 px hairline in border.accent.neutral (#eeeeef) at the bottom. The last item in a group passes isLastto drop the divider so it doesn't double up with a surrounding container border.

Types

Three types share the same trigger contract — only the geometry, weight, and indicator change.

Body

The default. 16 / 24 Medium label, 24 px row padding. Use inside FAQ lists, settings, and inline disclosure blocks.

The panel content lives inside this slot — paragraphs, forms, tables, anything the surface needs to disclose progressively.

Heading

24 / 32 Bold heading with an optional 24 px Material icon in a circular surface. Use as a top-level section header that opens into a panel — onboarding hub tiles, settings groups.

Trading hours, currency, language defaults, and the small print under the receipt — every customer-facing setting that isn't a price.

Checklist

A row that pairs a 20 px checkbox with a chevron-up indicator. Use for confirmable list items — the user ticks the box once they've completed the step, and can expand the row to read the instructions.

We'll send a small test deposit to your account. Confirm the amount in the portal within five business days.

A passport or national ID for every beneficial owner with 25% or greater ownership. Files are encrypted at rest and never shared externally.

Positions

The bottom divider is on by default and dropped on the last row. Inside CollapsibleGroup this happens automatically; outside of a group, pass isLast on the final item.

Default

Carries a 1 px bottom divider in border.accent.neutral so adjacent rows separate visually inside a stack.

Body content.

Body content.

Body content.

Last in group

Drops the bottom divider so the row doesn't double up with a surrounding container border. The CollapsibleGroup wrapper handles this automatically by passing isLast to its final child.

No divider below this row.

Status pill

A status pill in the trigger row signals that the panel beneath carries an action item. Use a tone-warning Badge — the only place a row earns a coloured pill in this component.

With status pill

The status slot sits between the title and the trigger glyph. Use sparingly — the pill is meant to announce a row that needs the user's attention.

We couldn't verify the address against the national registry. Upload a recent utility bill or bank statement (less than 90 days old).

Verified.

Accordion

Wrap a stack of Collapsibles in CollapsibleGroup to build an accordion. Pass single to limit the group to one open panel at a time. The wrapper handles the last-row divider for you.

Single-open accordion

Three top-level sections — opening one closes the others. Use single-open when reading two panels side-by-side wouldn't make sense, or when the panels are tall enough that two open at once pushes everything offscreen.

Trading hours, currency, language defaults, and the small print under the receipt.

Card schemes, local payment methods, and the rules that decide which appear at checkout for each market.

What appears on the printed receipt, the email receipt, and the digital wallet pass — including VAT lines and disclaimers.

In product

A real onboarding checklist. The heading-type row contains a three-step checklist; each row is independently checkable and independently expandable so the user can confirm completion without losing the instructions.

Onboarding

Get ready for your first transaction

1 / 3 done

A small test deposit will land within two business days. Match the amount in the portal to confirm the account is yours.

Passport or national ID for every owner with 25% or more. Files stay encrypted on Flatpay servers.

Two minutes, one e-signature. We'll countersign within an hour.

Motion

The panel uses a grid-template-rows transition (0fr 1fr) so the height animates without measurement and adapts to whatever the panel actually contains. 220 ms, cubic-bezier(0.22, 0.61, 0.36, 1) (ease-out-quart). Reduced-motion users get an instant reveal.

css

/* The panel grid transitions between 0fr and 1fr.
 * The inner div carries overflow:hidden so the content
 * is clipped while the row collapses. */
.collapsible-panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 220ms cubic-bezier(0.22, 0.61, 0.36, 1);
}

.collapsible-panel[data-open='true'] {
  grid-template-rows: 1fr;
}

.collapsible-panel > div {
  overflow: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .collapsible-panel { transition: none; }
}

Behavior

  • Click anywhere on the trigger row. The trigger is the entire row, not just the icon. The bigger tap target matters on mobile and reduces precision-pointing on dense lists.
  • Checklist taps split. Inside the checklist type, clicking the checkbox toggles the checked state without expanding the panel. Click anywhere else on the row to expand.
  • Single-open closes peers. In a single-open group, opening one row closes any other open row in the same group. The closing animation runs to completion before the opening one starts so the page never jumps.
  • Closed panels are hidden, not absent. The panel content stays in the DOM with visibility: hidden implied by the 0 row height — search-in-page (Cmd-F) still finds text inside closed panels and opens the row containing it.
  • URL hash deep-links open. When the URL hash matches an item's id, the page opens that item on load and scrolls to it. This is how an FAQ question becomes a shareable link.

Accessibility

  • Roles: the trigger is a native <button> with aria-expanded tracking the open state and aria-controls pointing at the panel id. The panel is a role="region" labelled by the trigger.
  • Keyboard: Enter / Space toggles the current row. Tab / Shift+Tab moves focus through triggers. Inside a single-open group, focus stays on the activated trigger after the toggle so the user can step through one row at a time.
  • Focus visibility: the trigger uses a 2 px offset focus ring in border.selected. On click the ring is suppressed via :focus-visibleso mouse and keyboard read differently — keyboard users see the ring; mouse users don't.
  • Checkbox separation: the checklist type uses a nested button with role="checkbox" and aria-checked. The event bubble is stopped so the outer trigger doesn't toggle when the user activates the checkbox.
  • Reduced motion: the grid-row transition is gated on prefers-reduced-motion: no-preference. Users who request reduced motion get an instant open / close with no height interpolation.
  • Screen-reader announcements: changes to aria-expandedare announced as “expanded” / “collapsed” by every modern screen reader without an additional live region.

Code

The component is a thin pair: a Collapsible primitive (a self-contained trigger + panel) and a CollapsibleGroup wrapper that passes isLast and, in single mode, controls which item is open.

tsx

import { Collapsible, CollapsibleGroup } from "@flatpay-dk/ui";
import StorefrontIcon from "@mui/icons-material/StorefrontOutlined";

// Stand-alone — uncontrolled
<Collapsible type="body" title="How long does payout take?">
  <p>
    Funds for transactions captured before 22:00 CET land in your
    settlement account by 09:00 CET the next business day.
  </p>
</Collapsible>

// Heading row with a leading icon
<Collapsible
  type="heading"
  title="Storefront"
  leadingIcon={StorefrontIcon}
>
  <p>Trading hours, currency, language defaults.</p>
</Collapsible>

// Single-open accordion
<CollapsibleGroup single defaultIndex={0}>
  <Collapsible type="body" title="Item one">…</Collapsible>
  <Collapsible type="body" title="Item two">…</Collapsible>
  <Collapsible type="body" title="Item three">…</Collapsible>
</CollapsibleGroup>

// Checklist — checkbox state independent of expand state
const [done, setDone] = useState(false);
<Collapsible
  type="checklist"
  title="Verify your bank account"
  checked={done}
  onCheckedChange={setDone}
>
  <p>We'll send a small test deposit within two business days.</p>
</Collapsible>

Best practices

A few rules that catch the common misuses before they reach the screen.

The window between when a card is authorised and when the bank moves money to your account.

Do

Use a collapsible for content the user can ignore — definitions, secondary settings, the small print under a primary action.

Step 1 of 4 — start by clicking…

Don't

Don't hide content the user must read to use the page. If every panel needs to be opened, the disclosure is wasting their time.

Long settings panel.

Long settings panel.

Long settings panel.

Do

Pick single-open when two open panels would push the page off-screen. Keep multi-open when each panel is short and the user might compare two at once.

Open by default — every one.

Open by default — every one.

Open by default — every one.

Open by default — every one.

Open by default — every one.

Open by default — every one.

Don't

Don't pile twelve open panels onto one page. The user loses the scroll position and the disclosure stops disclosing.

Upload a utility bill less than 90 days old.

Verified.

Do

Use the status pill when the row is the user's job. The Needs review badge tells them which row to open without scanning every panel.

Don't

Don't put a status pill on every row. The pill stops being a signal when it's wallpaper.

Props

Collapsible

PropTypeDefaultDescription
titleReactNodeThe trigger label. Body / checklist render as 16/24, heading as Bold 24/32.
childrenReactNodeThe panel content — paragraphs, forms, tables, or nested CollapsibleGroups.
type"body" | "heading" | "checklist""body"Geometry preset. Body for inline disclosure, heading for top-level sections, checklist for confirmable list items.
leadingIconReact.ComponentTypeOptional 24 px Material icon component, only rendered for type='heading'. Sits in a circular surface to the left of the title.
statusReactNodeOptional pill rendered between the title and trigger glyph. Typically a tone-warning Badge announcing 'Needs review'.
isLastbooleanfalseDrops the bottom divider so the row doesn't double up with a surrounding container border. Set automatically by CollapsibleGroup on its last child.
disabledbooleanfalseLocks the trigger and dims the row. The panel cannot be opened.
defaultOpenbooleanfalseInitial open state for uncontrolled use. Ignored when 'open' is set.
open / onOpenChangeboolean / (open: boolean) => voidControlled open state. Pair both — used by CollapsibleGroup in single-open mode to coordinate peers.
checked / onCheckedChangeboolean / (checked: boolean) => voidChecklist type only. Independent state for the inline 20 px checkbox.

CollapsibleGroup

PropTypeDefaultDescription
singlebooleanfalseWhen true, only one item can be open at a time. Opening one closes any peer that's open.
defaultIndexnumberIndex of the item to open on mount. Single-mode only — multi-mode lets each child manage its own defaultOpen.
childrenReactElement<CollapsibleProps>[]Collapsible items in render order. The wrapper passes isLast to the final child automatically.