A vertically stacked set of interactive headings that each reveal a section of content.
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' aesthetic. </div> </div> <div ubAccordionItem value="item-3"> <ub-accordion-trigger>Is it animated?</ub-accordion-trigger> <div ubAccordionContent> Yes. It'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' aesthetic. </div> </div> <div ubAccordionItem value="item-3"> <ub-accordion-trigger>Is it animated?</ub-accordion-trigger> <div ubAccordionContent> Yes. It's animated by default, but you can disable it if you prefer. </div> </div> </div> `, }) export class AccordionDemoDefault { }
npx shadcn-ng@latest add accordion
npm install @ng-icons/core @ng-icons/lucide
import type { ClassValue } from 'clsx' 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' @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()) }) }
import type { ClassValue } from 'clsx' 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' @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()) }) }
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", }, }, }, }
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>