Docs
Accordion
Accordion
A vertically stacked set of interactive headings that each reveal a section of content.
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' 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 { }
Installation
npx shadcn-ng@latest add accordion
npm
yarn
pnpm
bun
Install the following dependencies:
npm install @ng-icons/core @ng-icons/lucide
npm
yarn
pnpm
bun
Copy and paste the following code into your project.
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, NgIconComponent],
hostDirectives: [RdxAccordionTriggerDirective],
viewProviders: [provideIcons({ lucideChevronDown })],
template: `
<h3 rdxAccordionHeader class="flex group">
<button [class]="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 group-data-[state=open]:[&>*>svg]: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, NgIconComponent],
hostDirectives: [RdxAccordionTriggerDirective],
viewProviders: [provideIcons({ lucideChevronDown })],
template: `
<h3 rdxAccordionHeader class="flex group">
<button [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 group-data-[state=open]:[&>*>svg]: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())
})
}
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>