Docs
Accordion

Accordion

A vertically stacked set of interactive headings that each reveal a section of content.

Preview Code
Loading...
Loading...
import { UbAccordionContentDirective, UbAccordionDirective, UbAccordionItemDirective, UbAccordionTriggerDirective } from '@/components/ui/accordion'

import { Component } from '@angular/core'

@Component({
  standalone: true,
  selector: 'accordion-demo-new-york',
  imports: [UbAccordionDirective, UbAccordionItemDirective, UbAccordionTriggerDirective, UbAccordionContentDirective],
  template: `
        <div ubAccordion class="w-full" orientation="vertical">
            <div ubAccordionItem value="item-1">
                <ub-accordion-trigger>Is it accessible?</ub-accordion-trigger>
                <div ubAccordionContent>
                    Yes. It adheres to the WAI-ARIA design pattern.
                </div>
            </div>
            <div ubAccordionItem value="item-2">
                <ub-accordion-trigger>Is it styled?</ub-accordion-trigger>
                <div ubAccordionContent>
                    Yes. It comes with default styles that matches the other components&apos; aesthetic.
                </div>
            </div>
            <div ubAccordionItem value="item-3">
                <ub-accordion-trigger>Is it animated?</ub-accordion-trigger>
                <div ubAccordionContent>
                    Yes. It&apos;s animated by default, but you can disable it if you prefer.
                </div>
            </div>
        </div>
    `,
})
export class AccordionDemoNewYork { }

import { UbAccordionContentDirective, UbAccordionDirective, UbAccordionItemDirective, UbAccordionTriggerDirective } from '@/components/ui/accordion'

import { Component } from '@angular/core'

@Component({
  standalone: true,
  selector: 'accordion-demo-default',
  imports: [UbAccordionDirective, UbAccordionItemDirective, UbAccordionTriggerDirective, UbAccordionContentDirective],
  template: `
        <div ubAccordion class="w-full" orientation="vertical">
            <div ubAccordionItem value="item-1">
                <ub-accordion-trigger>Is it accessible?</ub-accordion-trigger>
                <div ubAccordionContent>
                    Yes. It adheres to the WAI-ARIA design pattern.
                </div>
            </div>
            <div ubAccordionItem value="item-2">
                <ub-accordion-trigger>Is it styled?</ub-accordion-trigger>
                <div ubAccordionContent>
                    Yes. It comes with default styles that matches the other components&apos; aesthetic.
                </div>
            </div>
            <div ubAccordionItem value="item-3">
                <ub-accordion-trigger>Is it animated?</ub-accordion-trigger>
                <div ubAccordionContent>
                    Yes. It&apos;s animated by default, but you can disable it if you prefer.
                </div>
            </div>
        </div>
    `,
})
export class AccordionDemoDefault { }

Installation

CLI Manual
npx shadcn-ng@latest add accordion

Install the following dependencies:

npm install @ng-icons/core @ng-icons/lucide

Copy and paste the following code into your project.

import { cn } from '@/lib/utils'
import { Component, computed, Directive, input } from '@angular/core'

import { NgIconComponent, provideIcons } from '@ng-icons/core'
import { lucideChevronDown } from '@ng-icons/lucide'

import {
  RdxAccordionContentDirective,
  RdxAccordionHeaderDirective,
  RdxAccordionItemDirective,
  RdxAccordionRootDirective,
  RdxAccordionTriggerDirective,
} from '@radix-ng/primitives/accordion'
import type { ClassValue } from 'clsx'

@Directive({
  standalone: true,
  selector: '[ubAccordion]',
  hostDirectives: [RdxAccordionRootDirective],
})
export class UbAccordionDirective { }

@Directive({
  standalone: true,
  selector: '[ubAccordionItem]',
  hostDirectives: [
    {
      directive: RdxAccordionItemDirective,
      inputs: ['disabled', 'value'],
    },
  ],
  host: {
    '[class]': 'computedClass()',
  },
})
export class UbAccordionItemDirective {
  class = input<ClassValue>()
  computedClass = computed(() => {
    return cn('border-b', this.class())
  })
}

@Component({
  standalone: true,
  selector: '[ubAccordionTrigger], ub-accordion-trigger',
  imports: [RdxAccordionHeaderDirective, RdxAccordionTriggerDirective, NgIconComponent],
  viewProviders: [provideIcons({ lucideChevronDown })],
  template: `
    <h3 rdxAccordionHeader class="flex">
        <button rdxAccordionTrigger [className]="computedClass()">
            <ng-content></ng-content>
            <ng-icon name="lucideChevronDown" class="h-4 w-4 shrink-0 transition-transform duration-200"></ng-icon>
        </button>
    </h3>
    `,
})
export class UbAccordionTriggerDirective {
  class = input<ClassValue>()
  computedClass = computed(() => {
    return cn('flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>ng-icon]:rotate-180', this.class())
  })
}

@Component({
  standalone: true,
  selector: '[ubAccordionContent], ub-accordion-content',
  hostDirectives: [RdxAccordionContentDirective],
  host: {
    class:
            'overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
  },
  template: `
        <div [className]="computedClass()">
            <ng-content></ng-content>
        </div>
    `,
})
export class UbAccordionContentDirective {
  class = input<ClassValue>()
  computedClass = computed(() => {
    return cn('pb-4 pt-0', this.class())
  })
}
import { cn } from '@/lib/utils'
import { Component, computed, Directive, input } from '@angular/core'

import { NgIconComponent, provideIcons } from '@ng-icons/core'
import { lucideChevronDown } from '@ng-icons/lucide'

import {
  RdxAccordionContentDirective,
  RdxAccordionHeaderDirective,
  RdxAccordionItemDirective,
  RdxAccordionRootDirective,
  RdxAccordionTriggerDirective,
} from '@radix-ng/primitives/accordion'
import type { ClassValue } from 'clsx'

@Directive({
  standalone: true,
  selector: '[ubAccordion]',
  hostDirectives: [RdxAccordionRootDirective],
})
export class UbAccordionDirective { }

@Directive({
  standalone: true,
  selector: '[ubAccordionItem]',
  hostDirectives: [
    {
      directive: RdxAccordionItemDirective,
      inputs: ['disabled', 'value'],
    },
  ],
  host: {
    '[class]': 'computedClass()',
  },
})
export class UbAccordionItemDirective {
  class = input<ClassValue>()
  computedClass = computed(() => {
    return cn('border-b', this.class())
  })
}

@Component({
  standalone: true,
  selector: '[ubAccordionTrigger], ub-accordion-trigger',
  imports: [RdxAccordionHeaderDirective, RdxAccordionTriggerDirective, NgIconComponent],
  viewProviders: [provideIcons({ lucideChevronDown })],
  template: `
    <h3 rdxAccordionHeader class="flex">
        <button rdxAccordionTrigger [className]="computedClass()">
            <ng-content></ng-content>
            <ng-icon name="lucideChevronDown" class="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200"></ng-icon>
        </button>
    </h3>
    `,
})
export class UbAccordionTriggerDirective {
  class = input<ClassValue>()
  computedClass = computed(() => {
    return cn('flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>ng-icon]:rotate-180', this.class())
  })
}

@Component({
  standalone: true,
  selector: '[ubAccordionContent], ub-accordion-content',
  hostDirectives: [RdxAccordionContentDirective],
  host: {
    class:
            'overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',
  },
  template: `
        <div [className]="computedClass()">
            <ng-content></ng-content>
        </div>
    `,
})
export class UbAccordionContentDirective {
  class = input<ClassValue>()
  computedClass = computed(() => {
    return cn('pb-4 pt-0', this.class())
  })
}

Update the import paths to match your project setup.

Update tailwind.config.js

Add the following animations to your tailwind.config.js file:

/** @type {import('tailwindcss').Config} */
module.exports = {
  theme: {
    extend: {
      keyframes: {
        "accordion-down": {
            from: { height: "0" },
            to: { height: "var(--radix-accordion-content-height)" },
        },
        "accordion-up": {
            from: { height: "var(--radix-accordion-content-height)" },
            to: { height: "0" },
        },
      },
      animation: {
        "accordion-down": "accordion-down 0.2s ease-out",
        "accordion-up": "accordion-up 0.2s ease-out",
      },
    },
  },
}

Usage

import {
    UbAccordionDirective,
    UbAccordionItemDirective,
    UbAccordionTriggerDirective,
    UbAccordionContentDirective
} from '@/components/ui/accordion.directive';
<div ubAccordion class="w-full" orientation="vertical">
    <div ubAccordionItem value="item-1">
        <ub-accordion-trigger>Is it accessible?</ub-accordion-trigger>
        <div ubAccordionContent>
            Yes. It adheres to the WAI-ARIA design pattern.
        </div>
    </div>
</div>