Skip to content
v2.5.0
English
Русский
Documentation

Collapsible

Collapsible is a compound component for one expandable region with a trigger and content surface.
VAT ID, invoice email and legal address.
collapsible.tsx · 18 lines
import {
  Button,
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  Text,
} from '@vega-ui/react';

<div>
  <Collapsible defaultOpen>
    <CollapsibleTrigger asChild>
      <Button size='sm' variant='secondary' appearance='ghost'>Billing details</Button>
Browser support
Widely available
  • Chrome 111
  • Edge 111
  • Firefox 113
  • Safari 15.4
  • Safari on iOS 15.4
Bundle size
Tested in
21 tests

Summary

Collapsible is a compound component for one expandable region with a trigger and content surface. It is the primitive disclosure pattern when grouped accordion behavior is not needed.

Imports

import {
  Collapsible,
  CollapsibleTrigger,
  CollapsibleContent,
  type CollapsibleProps,
} from '@vega-ui/react';

Exported Types

  • CollapsibleProps
  • CollapsibleTriggerProps
  • CollapsibleContentProps

Minimal Composition

minimal-composition.tsx · 14 lines
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@vega-ui/react';

export default function MinimalComposition() {
  return (
    <div style={{ width: '100%' }}>
      <Collapsible>
        <CollapsibleTrigger>Advanced billing options</CollapsibleTrigger>
        <CollapsibleContent>
          VAT settings, invoice recipients, and legal entity data.
        </CollapsibleContent>
      </Collapsible>
    </div>

Required Parts

  • Collapsible: root state owner
  • CollapsibleTrigger: expandable trigger surface
  • CollapsibleContent: expandable content region

Optional Parts

  • No extra public parts beyond the root trigger and content model

Composition Order

  1. Collapsible
  2. CollapsibleTrigger
  3. CollapsibleContent

Variants

  • Uncontrolled versus controlled open state
  • Default button trigger versus asChild trigger composition
  • Inline help, filters, and secondary details as product-level variants

Edge Cases

  • CollapsibleTrigger can render through asChild, but custom semantics should still preserve a real trigger contract.
  • onChangeHidden reflects visibility lifecycle, which can differ from the open state during height transitions.
  • Dynamic content height should be tested with real content, not just short placeholders.

Common Mistakes

  • Using Collapsible where grouped Accordion behavior is actually needed.
  • Hiding essential information behind vague trigger labels.
  • Treating open and hidden as the same lifecycle signal.

Examples

Basic

Basic: price breakdown under a total

An uncontrolled disclosure that keeps the total visible and hides the line items until asked. The trigger is a Button through asChild, so it keeps the button styles and still gets aria-expanded and aria-controls.

price-breakdown.tsx · 34 lines
import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger, Text } from '@vega-ui/react';

const lines = [
  { label: 'Team plan · 8 seats × $12', amount: '$96.00' },
  { label: 'Extra storage · 200 GB', amount: '$20.00' },
  { label: 'Annual discount −10%', amount: '−$11.60' },
  { label: 'VAT 19%', amount: '$19.84' },
];

export default function PriceBreakdown() {
  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-6)', width: '100%', maxWidth: 360 }}>

Controlled/Stateful

Controlled/Stateful: advanced filters with a summary

The parent owns open and uses it for the trigger label. The filter values live next to it, so the summary under the block still says how many filters apply when the block is collapsed.

advanced-filters.tsx · 64 lines
import { useState } from 'react';
import {
  Button,
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  Label,
  Text,
  TextField,
  TextFieldInput,
} from '@vega-ui/react';

Form/Integration

Form/Integration: optional company details

Hidden content is not disabled: its inputs would still be submitted and their required would still block the form. A <fieldset disabled={!open}> around the optional fields removes them from FormData and from validation while the block is closed.

invoice-details-form.tsx · 67 lines
import { FormEvent, useState } from 'react';
import {
  Button,
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  Label,
  TextField,
  TextFieldInput,
} from '@vega-ui/react';

export default function InvoiceDetailsForm() {

Layout/Overlay

Layout/Overlay: technical details in a dialog

The dialog explains the failure in plain words; the stack trace sits in a collapsed block with its own scroll area, so a long trace does not stretch the dialog.

error-details-dialog.tsx · 65 lines
import type { CSSProperties } from 'react';
import {
  Button,
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  Dialog,
  DialogClose,
  DialogContent,
  DialogTrigger,
  Heading,
  Icon,

Error

Error: promo code with inline validation

The form lives inside the content. An unknown code shows an error linked with aria-describedby, and typing clears it. The applied discount is announced through a role='status' region that is always rendered.

promo-code.tsx · 68 lines
import { FormEvent, useState } from 'react';
import {
  Button,
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  HelperText,
  Label,
  Text,
  TextField,
  TextFieldInput,
} from '@vega-ui/react';

Disabled

Disabled: trigger disabled by a parent fieldset

CollapsibleTrigger renders a native button, so a <fieldset disabled> disables it together with the inputs inside. The reason is linked to the fieldset with aria-describedby. If the block was open before locking, it stays open but read-only.

locked-order.tsx · 44 lines
import { useState } from 'react';
import {
  Button,
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
  Label,
  Text,
  TextField,
  TextFieldInput,
} from '@vega-ui/react';

Edge

Edge: stop background work once the content is hidden

onChangeHidden fires after the closing transition ends, not when the trigger is pressed. The log polls only while the content is shown and pauses when it is fully hidden.

live-log.tsx · 41 lines
import { useEffect, useState } from 'react';
import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger, Text } from '@vega-ui/react';

const routes = ['GET /api/health', 'POST /api/orders', 'GET /api/orders/10482', 'PATCH /api/users/42'];

export default function LiveLog() {
  const [hidden, setHidden] = useState(true);
  const [lines, setLines] = useState<string[]>([]);

  useEffect(() => {
    if (hidden) return;
    const timer = setInterval(() => {

Anatomy

Overview

Collapsible owns one expandable region and coordinates its trigger, content visibility, and hidden-state lifecycle. It is a compound component because the trigger and content depend on one shared root context.

Required Parts

Collapsible

Required. Owns open state, hidden-state lifecycle, and content ID coordination.

CollapsibleTrigger

Required. Toggles the region and carries aria-expanded plus aria-controls.

CollapsibleContent

Required. Renders the expandable region and manages the height-based visibility transition.

Optional Parts

Collapsible does not expose extra public child parts beyond the trigger and content pair.

Composition Order

  1. Collapsible
  2. CollapsibleTrigger
  3. CollapsibleContent

Valid Composition Patterns

price-breakdown.tsx · 34 lines
import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger, Text } from '@vega-ui/react';

const lines = [
  { label: 'Team plan · 8 seats × $12', amount: '$96.00' },
  { label: 'Extra storage · 200 GB', amount: '$20.00' },
  { label: 'Annual discount −10%', amount: '−$11.60' },
  { label: 'VAT 19%', amount: '$19.84' },
];

export default function PriceBreakdown() {
  return (
    <div style={{ display: 'grid', gap: 'var(--spacing-6)', width: '100%', maxWidth: 360 }}>

Invalid Composition Patterns

Content outside Collapsible

This breaks the shared ID, hidden state, and transition lifecycle.

Trigger semantics replaced by a non-interactive child

If asChild is used, the child still needs to behave like a real trigger.

If the sections should coordinate, use Accordion instead of separate Collapsible roots.