Collapsible

A trigger that shows or hides a region, with a height animation.

import { … } from '@inlaylab/ivorie/collapsible'

Hidden content.

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { LucideChevronRight } from '@lucide/angular';
import { IV_COLLAPSIBLE_IMPORTS } from '@inlaylab/ivorie/collapsible';

@Component({
  selector: 'demo-collapsible',
  imports: [IV_COLLAPSIBLE_IMPORTS, LucideChevronRight],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <iv-collapsible [(open)]="open" class="w-80">
      <button ivCollapsibleTrigger class="group flex items-center gap-2 text-sm font-medium">
        Advanced settings
        <svg lucideChevronRight class="size-4 transition-transform group-data-[state=open]:rotate-90"></svg>
      </button>
      <iv-collapsible-content>
        <p class="pt-2 text-sm text-muted-foreground">Hidden content.</p>
      </iv-collapsible-content>
    </iv-collapsible>
  `,
})
export default class CollapsibleDemo {
  protected readonly open = signal(false);
}

Installation

CLInpm
npx ivorie add collapsible

Ports the shadcn/ui Collapsible. The height animates with a CSS grid transition. No @angular/animations needed.

Usage

import { IV_COLLAPSIBLE_IMPORTS } from '@inlaylab/ivorie/collapsible';
<iv-collapsible [(open)]="isOpen">
  <button ivCollapsibleTrigger>Advanced settings</button>
  <iv-collapsible-content>Hidden content.</iv-collapsible-content>
</iv-collapsible>

Examples

Open by default

Hidden content.

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { LucideChevronRight } from '@lucide/angular';
import { IV_COLLAPSIBLE_IMPORTS } from '@inlaylab/ivorie/collapsible';

@Component({
  selector: 'demo-collapsible-open-by-default',
  imports: [IV_COLLAPSIBLE_IMPORTS, LucideChevronRight],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <iv-collapsible [(open)]="open" class="w-80">
      <button ivCollapsibleTrigger class="group flex items-center gap-2 text-sm font-medium">
        Details
        <svg lucideChevronRight class="size-4 transition-transform group-data-[state=open]:rotate-90"></svg>
      </button>
      <iv-collapsible-content>
        <p class="pt-2 text-sm text-muted-foreground">Hidden content.</p>
      </iv-collapsible-content>
    </iv-collapsible>
  `,
})
export default class CollapsibleOpenByDefaultDemo {
  protected readonly open = signal(true);
}

Disabled

Hidden content.

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { LucideChevronRight } from '@lucide/angular';
import { IV_COLLAPSIBLE_IMPORTS } from '@inlaylab/ivorie/collapsible';

@Component({
  selector: 'demo-collapsible-disabled',
  imports: [IV_COLLAPSIBLE_IMPORTS, LucideChevronRight],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <iv-collapsible [(open)]="open" [disabled]="true" class="w-80">
      <button ivCollapsibleTrigger class="group flex items-center gap-2 text-sm font-medium">
        Can't open
        <svg lucideChevronRight class="size-4 transition-transform group-data-[state=open]:rotate-90"></svg>
      </button>
      <iv-collapsible-content>
        <p class="pt-2 text-sm text-muted-foreground">Hidden content.</p>
      </iv-collapsible-content>
    </iv-collapsible>
  `,
})
export default class CollapsibleDisabledDemo {
  protected readonly open = signal(false);
}

Animated chevron

Hidden content.

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { LucideChevronRight } from '@lucide/angular';
import { IV_COLLAPSIBLE_IMPORTS } from '@inlaylab/ivorie/collapsible';

@Component({
  selector: 'demo-collapsible-animated-chevron',
  imports: [IV_COLLAPSIBLE_IMPORTS, LucideChevronRight],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <iv-collapsible [(open)]="open" class="w-80">
      <button ivCollapsibleTrigger class="group flex items-center gap-2 text-sm font-medium">
        Details
        <svg lucideChevronRight class="size-4 transition-transform group-data-[state=open]:rotate-90"></svg>
      </button>
      <iv-collapsible-content>
        <p class="pt-2 text-sm text-muted-foreground">Hidden content.</p>
      </iv-collapsible-content>
    </iv-collapsible>
  `,
})
export default class CollapsibleAnimatedChevronDemo {
  protected readonly open = signal(false);
}

API

Every part also accepts class.

iv-collapsible

Name Type Default Description
open boolean false Open state. Two-way bindable with [(open)].
disabled boolean false Blocks toggling.

Outputs:

Name Payload Description
openChange boolean The open state changed. Use with [(open)].

ivCollapsibleTrigger

Attribute directive for the element that toggles. Put it on a <button> or any element. No inputs.

iv-collapsible-content

The expandable region. No own inputs.

Accessibility

  • The trigger has aria-expanded and aria-controls pointing at the content id.
  • A <button> trigger keeps native behavior. Any other element gets role="button", tabindex="0", and toggles on Enter and Space.
  • A disabled non-button trigger sets aria-disabled and tabindex="-1". A disabled <button> gets the disabled attribute.
  • Closed content is inert. Its links and fields are not focusable and are hidden from assistive tech.

Notes

  • data-state="open" | "closed" is set on the root, trigger and content. Use it for styling, for example group-data-[state=open]:rotate-90 on a chevron.
  • Closed content stays in the DOM so the height can animate.