Skip to main content
Nexus Design System
GitHub

Collapsible

Collapsible shows and hides one content region. Use it for a single expandable section. For a stacked set of sections, use Accordion.

@nxds starred 3 repositories
@radix-ui/react-collapsible
'use client';

import { CaretUpDownIcon } from '@phosphor-icons/react/ssr';

import { Button } from '@/components/nxds/components/button/button';
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from '@/components/nxds/components/collapsible/collapsible';

export default function CollapsibleDefault() {
  return (
    <Collapsible className="nx:flex nx:w-72 nx:flex-col">
      <div className="nx:flex nx:flex-col nx:gap-2">
        <div className="nx:flex nx:items-center nx:justify-between nx:gap-4">
          <span className="nx:typography-label-default nx:text-foreground">
            @nxds starred 3 repositories
          </span>
          <CollapsibleTrigger asChild>
            <Button variant="ghost" size="sm" aria-label="Toggle repositories">
              <CaretUpDownIcon />
            </Button>
          </CollapsibleTrigger>
        </div>
        <div className="nx:rounded-md nx:border-default nx:border-border-default nx:px-4 nx:py-2 nx:typography-label-default nx:text-foreground">
          @radix-ui/react-collapsible
        </div>
      </div>
      {/* Spacing lives inside the content (overflow-hidden contains it), so
          there is no parent flex `gap` to snap when it collapses to height 0. */}
      <CollapsibleContent>
        <div className="nx:mt-2 nx:flex nx:flex-col nx:gap-2">
          <div className="nx:rounded-md nx:border-default nx:border-border-default nx:px-4 nx:py-2 nx:typography-label-default nx:text-foreground">
            @radix-ui/react-toggle
          </div>
          <div className="nx:rounded-md nx:border-default nx:border-border-default nx:px-4 nx:py-2 nx:typography-label-default nx:text-foreground">
            @radix-ui/react-slider
          </div>
        </div>
      </CollapsibleContent>
    </Collapsible>
  );
}

This example also needs: Button.

Installation

npx nxds-cli@latest add collapsible

Run npx nxds-cli@latest init first if nxds isn’t set up in your app yet. See Install.

Usage

import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from '@/components/nxds/components/collapsible/collapsible';

Composition

Collapsible
├── CollapsibleTrigger
└── CollapsibleContent

Examples

Open

Set defaultOpen to show the content on first render.

Revealed content shown while open.
'use client';

import { Button } from '@/components/nxds/components/button/button';
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from '@/components/nxds/components/collapsible/collapsible';

export default function CollapsibleOpen() {
  return (
    <Collapsible defaultOpen className="nx:flex nx:w-72 nx:flex-col">
      <CollapsibleTrigger asChild>
        <Button variant="outline">Toggle details</Button>
      </CollapsibleTrigger>
      <CollapsibleContent>
        <div className="nx:mt-2 nx:rounded-md nx:border-default nx:border-border-default nx:px-4 nx:py-2 nx:typography-label-default nx:text-muted-foreground">
          Revealed content shown while open.
        </div>
      </CollapsibleContent>
    </Collapsible>
  );
}

Disabled

Set disabled so the trigger cannot toggle the content.

'use client';

import { Button } from '@/components/nxds/components/button/button';
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from '@/components/nxds/components/collapsible/collapsible';

export default function CollapsibleDisabled() {
  return (
    <Collapsible disabled className="nx:flex nx:w-72 nx:flex-col">
      <CollapsibleTrigger asChild>
        <Button variant="outline">Toggle details</Button>
      </CollapsibleTrigger>
      <CollapsibleContent>
        <div className="nx:mt-2 nx:rounded-md nx:border-default nx:border-border-default nx:px-4 nx:py-2 nx:typography-label-default nx:text-foreground">
          Unreachable while disabled.
        </div>
      </CollapsibleContent>
    </Collapsible>
  );
}

Props

Collapsible

Collapsible adds no props of its own; it accepts the props of the element or primitive it wraps.

CollapsibleContent

CollapsibleContent adds no props of its own; it accepts the props of the element or primitive it wraps.

CollapsibleTrigger

CollapsibleTrigger adds no props of its own; it accepts the props of the element or primitive it wraps.

Collapsible shows and hides one content region. Use it for a single expandable section. For a stacked set of sections, use Accordion.

<script setup lang="ts">
import { PhCaretUpDown } from '@phosphor-icons/vue';

import { Button } from '@/components/button';
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from '@/components/collapsible';
</script>

<template>
  <Collapsible class="nx:flex nx:w-72 nx:flex-col">
    <div class="nx:flex nx:flex-col nx:gap-2">
      <div class="nx:flex nx:items-center nx:justify-between nx:gap-4">
        <span class="nx:typography-label-default nx:text-foreground"
          >@nxds starred 3 repositories</span
        >
        <CollapsibleTrigger as-child>
          <Button variant="ghost" size="sm" aria-label="Toggle repositories">
            <PhCaretUpDown />
          </Button>
        </CollapsibleTrigger>
      </div>
      <div
        class="nx:rounded-md nx:border-default nx:border-border-default nx:px-4 nx:py-2 nx:typography-label-default nx:text-foreground"
      >
        @radix-ui/react-collapsible
      </div>
    </div>
    <CollapsibleContent>
      <div class="nx:mt-2 nx:flex nx:flex-col nx:gap-2">
        <div
          class="nx:rounded-md nx:border-default nx:border-border-default nx:px-4 nx:py-2 nx:typography-label-default nx:text-foreground"
        >
          @radix-ui/react-toggle
        </div>
        <div
          class="nx:rounded-md nx:border-default nx:border-border-default nx:px-4 nx:py-2 nx:typography-label-default nx:text-foreground"
        >
          @radix-ui/react-slider
        </div>
      </div>
    </CollapsibleContent>
  </Collapsible>
</template>

This example also needs: Button.

npm install @phosphor-icons/vue@^2.2.1

Installation

  1. Install the following dependencies:

    npm install @vueuse/core@^14.1.0 reka-ui@^2.11.0
    
  2. Copy and paste the following code into your project.

    components/collapsible/Collapsible.vue
    <script setup lang="ts">
    import {
      CollapsibleRoot,
      type CollapsibleRootEmits,
      type CollapsibleRootProps,
      useForwardPropsEmits,
    } from 'reka-ui';
    
    export type CollapsibleProps = CollapsibleRootProps;
    
    const props = withDefaults(defineProps<CollapsibleRootProps>(), {
      open: undefined,
      defaultOpen: undefined,
    });
    const emits = defineEmits<CollapsibleRootEmits>();
    
    const forwarded = useForwardPropsEmits(props, emits);
    </script>
    
    <template>
      <CollapsibleRoot
        v-slot="slotProps"
        v-bind="forwarded"
        data-slot="collapsible"
      >
        <slot v-bind="slotProps" />
      </CollapsibleRoot>
    </template>
    
    components/collapsible/CollapsibleContent.vue
    <script setup lang="ts">
    import { reactiveOmit } from '@vueuse/core';
    import {
      CollapsibleContent,
      type CollapsibleContentEmits,
      type CollapsibleContentProps as RekaCollapsibleContentProps,
      useForwardPropsEmits,
    } from 'reka-ui';
    import type { HTMLAttributes } from 'vue';
    
    import { cn } from '../../lib/utils';
    
    export interface CollapsibleContentProps extends RekaCollapsibleContentProps {
      class?: HTMLAttributes['class'];
    }
    
    const props = defineProps<CollapsibleContentProps>();
    const emits = defineEmits<CollapsibleContentEmits>();
    
    const delegated = reactiveOmit(props, 'class');
    const forwarded = useForwardPropsEmits(delegated, emits);
    </script>
    
    <template>
      <CollapsibleContent
        v-bind="forwarded"
        data-slot="collapsible-content"
        :class="
          cn(
            'nx:overflow-hidden nx:duration-default nx:data-[state=closed]:animate-collapsible-up nx:data-[state=open]:animate-collapsible-down',
            props.class
          )
        "
      >
        <slot />
      </CollapsibleContent>
    </template>
    
    components/collapsible/CollapsibleTrigger.vue
    <script setup lang="ts">
    import {
      CollapsibleTrigger,
      type CollapsibleTriggerProps as RekaCollapsibleTriggerProps,
    } from 'reka-ui';
    
    export type CollapsibleTriggerProps = RekaCollapsibleTriggerProps;
    
    const props = defineProps<RekaCollapsibleTriggerProps>();
    </script>
    
    <template>
      <CollapsibleTrigger v-bind="props" data-slot="collapsible-trigger">
        <slot />
      </CollapsibleTrigger>
    </template>
    
    components/collapsible/index.ts
    export {
      default as Collapsible,
      type CollapsibleProps,
    } from './Collapsible.vue';
    export {
      default as CollapsibleContent,
      type CollapsibleContentProps,
    } from './CollapsibleContent.vue';
    export {
      default as CollapsibleTrigger,
      type CollapsibleTriggerProps,
    } from './CollapsibleTrigger.vue';
    
  3. Keep each file at the path shown above it.

    Paths are relative to packages/vue/src/. Keep the same folders under your @/ root, and the imports between files work without edits.

Usage

import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/collapsible';

Examples

Open

Set default-open to show the content on first render.

<script setup lang="ts">
import { Button } from '@/components/button';
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from '@/components/collapsible';
</script>

<template>
  <Collapsible default-open class="nx:flex nx:w-72 nx:flex-col">
    <CollapsibleTrigger as-child>
      <Button variant="outline">Toggle details</Button>
    </CollapsibleTrigger>
    <CollapsibleContent>
      <div
        class="nx:mt-2 nx:rounded-md nx:border-default nx:border-border-default nx:px-4 nx:py-2 nx:typography-label-default nx:text-muted-foreground"
      >
        Revealed content shown while open.
      </div>
    </CollapsibleContent>
  </Collapsible>
</template>

Disabled

Set disabled so the trigger cannot toggle the content.

<script setup lang="ts">
import { Button } from '@/components/button';
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from '@/components/collapsible';
</script>

<template>
  <Collapsible disabled class="nx:flex nx:w-72 nx:flex-col">
    <CollapsibleTrigger as-child>
      <Button variant="outline">Toggle details</Button>
    </CollapsibleTrigger>
    <CollapsibleContent>
      <div
        class="nx:mt-2 nx:rounded-md nx:border-default nx:border-border-default nx:px-4 nx:py-2 nx:typography-label-default nx:text-foreground"
      >
        Unreachable while disabled.
      </div>
    </CollapsibleContent>
  </Collapsible>
</template>

Props

Collapsible

Collapsible adds no props of its own; it accepts the props of the element or primitive it wraps.

CollapsibleContent

CollapsibleContent adds no props of its own; it accepts the props of the element or primitive it wraps.

CollapsibleTrigger

CollapsibleTrigger adds no props of its own; it accepts the props of the element or primitive it wraps.