Accordion
A collapsible content panel for organizing information in a compact space
Import
import { AvAccordionImports } from '@avesra/angular';Usage
import { Component } from '@angular/core';
import { AvAccordionImports } from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-accordion-basic-demo',
imports: [
AvAccordionImports,
AppIconComponent,
],
template: `<div class="mx-auto w-full max-w-md">
<av-accordion class="w-full max-w-md">
@for (item of defaultItems; track item.title; let index = $index) {
<av-accordion-item [id]="'default-' + index">
<h3 av-accordion-heading>
<button av-accordion-trigger>
@if (item.icon) {
<span class="me-3 inline-flex size-4 shrink-0 text-muted" aria-hidden="true">
<app-icon [icon]="item.icon" size="16" />
</span>
}
{{ item.title }}
<svg av-accordion-indicator></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>{{ item.content }}</div>
</div>
</av-accordion-item>
}
</av-accordion>
</div>`,
})
export class AccordionBasicDemo {
readonly defaultItems = [
{
title: 'How do I place an order?',
content:
"Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.",
icon: 'solar:bag-linear',
},
{
title: 'Can I modify or cancel my order?',
content:
"Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.",
icon: 'solar:bill-list-linear',
},
{
title: 'What payment methods do you accept?',
content: 'We accept all major credit cards, including Visa, Mastercard, and American Express.',
icon: 'solar:card-linear',
},
{
title: 'How much does shipping cost?',
content:
'Shipping costs vary based on your location and the size of your order. We offer free shipping for orders over $50.',
icon: 'solar:box-linear',
},
{
title: 'Do you ship internationally?',
content:
'Yes, we ship to most countries. Please check our shipping rates and policies for more information.',
icon: 'solar:global-linear',
},
{
title: 'How do I request a refund?',
content:
"If you're not satisfied with your purchase, you can request a refund within 30 days of purchase. Please contact our customer support team for assistance.",
icon: 'solar:refresh-linear',
},
];
}Anatomy
<av-accordion>
<av-accordion-item id="item-1">
<h3 av-accordion-heading>
<button av-accordion-trigger>
Section title
<svg av-accordion-indicator></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body></div>
</div>
</av-accordion-item>
</av-accordion>Examples
Surface
import { Component } from '@angular/core';
import { AvAccordionImports } from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-accordion-surface-demo',
imports: [
AvAccordionImports,
AppIconComponent,
],
template: `<div class="mx-auto w-full max-w-md">
<av-accordion class="w-full max-w-md" variant="surface">
@for (item of defaultItems; track item.title; let index = $index) {
<av-accordion-item [id]="'surface-' + index">
<h3 av-accordion-heading>
<button av-accordion-trigger>
@if (item.icon) {
<span class="me-3 inline-flex size-4 shrink-0 text-muted" aria-hidden="true">
<app-icon [icon]="item.icon" size="16" />
</span>
}
{{ item.title }}
<svg av-accordion-indicator></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>{{ item.content }}</div>
</div>
</av-accordion-item>
}
</av-accordion>
</div>`,
})
export class AccordionSurfaceDemo {
readonly defaultItems = [
{
title: 'How do I place an order?',
content:
"Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.",
icon: 'solar:bag-linear',
},
{
title: 'Can I modify or cancel my order?',
content:
"Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.",
icon: 'solar:bill-list-linear',
},
{
title: 'What payment methods do you accept?',
content: 'We accept all major credit cards, including Visa, Mastercard, and American Express.',
icon: 'solar:card-linear',
},
{
title: 'How much does shipping cost?',
content:
'Shipping costs vary based on your location and the size of your order. We offer free shipping for orders over $50.',
icon: 'solar:box-linear',
},
{
title: 'Do you ship internationally?',
content:
'Yes, we ship to most countries. Please check our shipping rates and policies for more information.',
icon: 'solar:global-linear',
},
{
title: 'How do I request a refund?',
content:
"If you're not satisfied with your purchase, you can request a refund within 30 days of purchase. Please contact our customer support team for assistance.",
icon: 'solar:refresh-linear',
},
];
}Without Separator
import { Component } from '@angular/core';
import { AvAccordionImports } from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-accordion-without-separator-demo',
imports: [
AvAccordionImports,
AppIconComponent,
],
template: `<div class="mx-auto w-full max-w-md">
<av-accordion hide-separator>
@for (item of defaultItems; track item.title; let index = $index) {
<av-accordion-item [id]="'no-sep-' + index">
<h3 av-accordion-heading>
<button av-accordion-trigger>
@if (item.icon) {
<span class="me-3 inline-flex size-4 shrink-0 text-muted" aria-hidden="true">
<app-icon [icon]="item.icon" size="16" />
</span>
}
{{ item.title }}
<svg av-accordion-indicator></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>{{ item.content }}</div>
</div>
</av-accordion-item>
}
</av-accordion>
</div>`,
})
export class AccordionWithoutSeparatorDemo {
readonly defaultItems = [
{
title: 'How do I place an order?',
content:
"Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.",
icon: 'solar:bag-linear',
},
{
title: 'Can I modify or cancel my order?',
content:
"Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.",
icon: 'solar:bill-list-linear',
},
{
title: 'What payment methods do you accept?',
content: 'We accept all major credit cards, including Visa, Mastercard, and American Express.',
icon: 'solar:card-linear',
},
];
}Multiple Expanded
import { Component } from '@angular/core';
import { AvAccordionImports } from '@avesra/angular';
@Component({
selector: 'app-accordion-multiple-demo',
imports: [
AvAccordionImports,
],
host: { class: 'flex w-full justify-center' },
template: `<av-accordion class="w-full max-w-md" allows-multiple>
<av-accordion-item id="getting-started">
<h3 av-accordion-heading>
<button av-accordion-trigger>
Getting Started
<svg av-accordion-indicator></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>
Learn the basics of Avesra and how to integrate it into your Angular project. This
section covers installation, setup, and your first component.
</div>
</div>
</av-accordion-item>
<av-accordion-item id="core-concepts">
<h3 av-accordion-heading>
<button av-accordion-trigger>
Core Concepts
<svg av-accordion-indicator></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>
Understand the fundamental concepts behind Avesra, including the compound component
pattern, styling with Tailwind CSS, and accessibility features.
</div>
</div>
</av-accordion-item>
<av-accordion-item id="advanced-usage">
<h3 av-accordion-heading>
<button av-accordion-trigger>
Advanced Usage
<svg av-accordion-indicator></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>
Explore advanced features like custom variants, theme customization, and integration
with other libraries in your Angular ecosystem.
</div>
</div>
</av-accordion-item>
<av-accordion-item id="best-practices">
<h3 av-accordion-heading>
<button av-accordion-trigger>
Best Practices
<svg av-accordion-indicator></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>
Follow our recommended best practices for building performant, accessible, and
maintainable applications with Avesra components.
</div>
</div>
</av-accordion-item>
</av-accordion>`,
})
export class AccordionMultipleDemo {}Disabled State
Entire accordion disabled
Individual items disabled
import { Component } from '@angular/core';
import { AvAccordionImports } from '@avesra/angular';
@Component({
selector: 'app-accordion-disabled-demo',
imports: [
AvAccordionImports,
],
template: `<div class="flex w-full flex-col items-center gap-8">
<div class="w-full max-w-md space-y-2">
<h3 class="text-sm font-medium text-muted">Entire accordion disabled</h3>
<av-accordion class="w-full max-w-md" disabled>
<av-accordion-item id="disabled-all-1">
<h3 av-accordion-heading>
<button av-accordion-trigger>
Disabled Item 1
<svg av-accordion-indicator></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>
This content cannot be accessed when the accordion is disabled.
</div>
</div>
</av-accordion-item>
<av-accordion-item id="disabled-all-2">
<h3 av-accordion-heading>
<button av-accordion-trigger>
Disabled Item 2
<svg av-accordion-indicator></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>
This content cannot be accessed when the accordion is disabled.
</div>
</div>
</av-accordion-item>
</av-accordion>
</div>
<div class="w-full max-w-md space-y-2">
<h3 class="text-sm font-medium text-muted">Individual items disabled</h3>
<av-accordion class="w-full max-w-md">
<av-accordion-item id="disabled-item-1">
<h3 av-accordion-heading>
<button av-accordion-trigger>
Active Item
<svg av-accordion-indicator></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>This item is active and can be toggled normally.</div>
</div>
</av-accordion-item>
<av-accordion-item id="disabled-item-2" disabled>
<h3 av-accordion-heading>
<button av-accordion-trigger>
Disabled Item
<svg av-accordion-indicator></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>
This content cannot be accessed when the item is disabled.
</div>
</div>
</av-accordion-item>
<av-accordion-item id="disabled-item-3">
<h3 av-accordion-heading>
<button av-accordion-trigger>
Another Active Item
<svg av-accordion-indicator></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>This item is also active and can be toggled.</div>
</div>
</av-accordion-item>
</av-accordion>
</div>
</div>`,
})
export class AccordionDisabledDemo {}Controlled
Expanded: getting-started
Learn the basics of Avesra and how to integrate it into your Angular project. This section covers installation, setup, and your first component.
import { Component, computed, signal } from '@angular/core';
import { AvAccordionImports, AvButtonComponent } from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-accordion-controlled-demo',
imports: [
AvAccordionImports,
AvButtonComponent,
AppIconComponent,
],
template: `<div class="w-full max-w-md">
<div class="mb-4 flex items-center justify-between">
<p class="text-sm text-muted">
Expanded: <strong>{{ expandedLabel() }}</strong>
</p>
<div class="flex gap-2">
<button
av-button
type="button"
variant="secondary"
size="sm"
aria-label="Previous item"
[disabled]="isPrevDisabled()"
(click)="onPrevious()"
>
<app-icon icon="solar:alt-arrow-up-linear" size="16" />
</button>
<button
av-button
type="button"
variant="secondary"
size="sm"
aria-label="Next item"
[disabled]="isNextDisabled()"
(click)="onNext()"
>
<app-icon icon="solar:alt-arrow-down-linear" size="16" />
</button>
</div>
</div>
<av-accordion [(expandedKeys)]="expandedKeys">
@for (item of items; track item.id) {
<av-accordion-item [id]="item.id">
<h3 av-accordion-heading>
<button av-accordion-trigger>
{{ item.title }}
<svg av-accordion-indicator></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>{{ item.content }}</div>
</div>
</av-accordion-item>
}
</av-accordion>
</div>`,
})
export class AccordionControlledDemo {
readonly items = [
{
id: 'getting-started',
title: 'Getting Started',
content:
'Learn the basics of Avesra and how to integrate it into your Angular project. This section covers installation, setup, and your first component.',
},
{
id: 'core-concepts',
title: 'Core Concepts',
content:
'Understand the fundamental concepts behind Avesra, including the compound component pattern, styling with Tailwind CSS, and accessibility features.',
},
{
id: 'advanced-usage',
title: 'Advanced Usage',
content:
'Explore advanced features like custom variants, theme customization, and integration with other libraries in your Angular ecosystem.',
},
];
readonly expandedKeys = signal<string[]>(['getting-started']);
readonly itemIds = this.items.map((item) => item.id);
readonly expandedLabel = computed(() => this.expandedKeys().join(', ') || 'none');
readonly isPrevDisabled = computed(() => {
const current = this.expandedKeys()[0];
return !current || this.itemIds.indexOf(current) <= 0;
});
readonly isNextDisabled = computed(() => {
const current = this.expandedKeys()[0];
const index = this.itemIds.indexOf(current);
return index < 0 || index >= this.itemIds.length - 1;
});
onPrevious(): void {
const current = this.expandedKeys()[0];
const index = this.itemIds.indexOf(current);
if (index > 0) {
this.expandedKeys.set([this.itemIds[index - 1]]);
}
}
onNext(): void {
const current = this.expandedKeys()[0];
const index = this.itemIds.indexOf(current);
if (index >= 0 && index < this.itemIds.length - 1) {
this.expandedKeys.set([this.itemIds[index + 1]]);
}
}
}Custom Indicator
import { Component, signal } from '@angular/core';
import { AvAccordionImports } from '@avesra/angular';
import { AppIconComponent } from '../../components/app-icon/app-icon.component';
@Component({
selector: 'app-accordion-custom-indicator-demo',
imports: [
AvAccordionImports,
AppIconComponent,
],
host: { class: 'flex w-full justify-center' },
template: `<av-accordion
class="w-full max-w-md"
variant="surface"
[(expandedKeys)]="expandedKeys"
>
<av-accordion-item id="1">
<h3 av-accordion-heading>
<button av-accordion-trigger>
Using Plus/Minus Icon
<span
av-accordion-indicator
class="inline-flex size-4 items-center justify-center data-[expanded=true]:!rotate-0"
>
@if (expandedKeys().includes('1')) {
<app-icon icon="solar:minus-circle-linear" size="16" />
} @else {
<app-icon icon="solar:add-circle-linear" size="16" />
}
</span>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>
This accordion uses a plus icon that transforms when expanded. The icon automatically
rotates 45 degrees to form an X.
</div>
</div>
</av-accordion-item>
<av-accordion-item id="2">
<h3 av-accordion-heading>
<button av-accordion-trigger>
Using Caret Icon
<span av-accordion-indicator class="inline-flex size-4 items-center justify-center">
<app-icon icon="solar:alt-arrow-down-linear" size="16" />
</span>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>
This item uses a caret icon for the indicator. The rotation animation is applied
automatically.
</div>
</div>
</av-accordion-item>
<av-accordion-item id="3">
<h3 av-accordion-heading>
<button av-accordion-trigger>
Using Arrow Icon
<span av-accordion-indicator class="inline-flex size-4 items-center justify-center">
<app-icon icon="solar:double-alt-arrow-down-linear" size="16" />
</span>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>
This item uses an arrow icon. Any icon you pass will receive the rotation animation when
the item expands.
</div>
</div>
</av-accordion-item>
</av-accordion>`,
})
export class AccordionCustomIndicatorDemo {
readonly expandedKeys = signal<string[]>([]);
}FAQ Layout
Frequently Asked Questions
Everything you need to know about licensing and usage.
General
Licensing
Support
import { Component } from '@angular/core';
import { AvAccordionImports } from '@avesra/angular';
@Component({
selector: 'app-accordion-custom-faq-demo',
imports: [
AvAccordionImports,
],
template: `<div class="flex w-full flex-col gap-6">
<div class="flex flex-col gap-1">
<h2 class="text-2xl font-bold">Frequently Asked Questions</h2>
<p class="mb-4 text-lg font-medium text-muted">
Everything you need to know about licensing and usage.
</p>
</div>
@for (category of categories; track category.title) {
<div>
<p class="text-md mb-2 font-medium text-muted">{{ category.title }}</p>
<av-accordion class="w-full" variant="surface">
@for (item of category.items; track item.title; let index = $index) {
<av-accordion-item [id]="category.title + '-' + index">
<h3 av-accordion-heading>
<button av-accordion-trigger>
{{ item.title }}
<svg av-accordion-indicator></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body>{{ item.content }}</div>
</div>
</av-accordion-item>
}
</av-accordion>
</div>
}
</div>`,
})
export class AccordionCustomFaqDemo {
readonly categories = [
{
"title": "General",
"items": [
{
"title": "How do I place an order?",
"content": "Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase."
},
{
"title": "Can I modify or cancel my order?",
"content": "Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes."
}
]
},
{
"title": "Licensing",
"items": [
{
"title": "How do I purchase a license?",
"content": "You can purchase a license directly from our website. Select the license type that fits your needs and proceed to checkout."
},
{
"title": "What is the difference between a standard and a pro license?",
"content": "A standard license is for personal use or small projects, while a pro license includes commercial use rights and priority support."
}
]
},
{
"title": "Support",
"items": [
{
"title": "How do I get support?",
"content": "You can reach our support team through the contact form on our website, or email us directly at [email protected]."
}
]
}
];
}Customization
Tailwind CSS
import { Component } from '@angular/core';
import { AvAccordionImports } from '@avesra/angular';
@Component({
selector: 'app-accordion-custom-styles-demo',
imports: [
AvAccordionImports,
],
host: { class: 'flex w-full justify-center' },
template: `<av-accordion class="w-full max-w-md rounded-2xl bg-surface/10" variant="surface">
@for (item of items; track item.title; let index = $index) {
<av-accordion-item [id]="'custom-styles-' + index" class="group/item">
<h3 av-accordion-heading>
<button
av-accordion-trigger
class="group flex items-center gap-2 transition-none hover:bg-surface"
>
<img
[alt]="item.title"
class="h-11 w-11 transition-[scale,rotate] duration-300 ease-out group-hover/item:scale-120 group-hover/item:-rotate-10 group-hover/item:drop-shadow-lg"
[src]="item.iconUrl"
/>
<div class="flex flex-col gap-0">
<span class="font-medium leading-5">{{ item.title }}</span>
<span class="font-normal leading-6 text-muted/80">{{ item.subtitle }}</span>
</div>
<svg av-accordion-indicator class="text-muted/50"></svg>
</button>
</h3>
<div av-accordion-panel>
<div av-accordion-body class="text-muted/80">{{ item.content }}</div>
</div>
</av-accordion-item>
}
</av-accordion>`,
})
export class AccordionCustomStylesDemo {
readonly items = [
{
"title": "Set Up Notifications",
"subtitle": "Receive account activity updates",
"content": "Stay informed about your account activity with real-time notifications.",
"iconUrl": "/images/icons/icon-3d-alarm-clock-red.png"
},
{
"title": "Secure Your Account",
"subtitle": "Enable two-factor authentication",
"content": "Protect your workspace by turning on two-factor authentication for sign-in.",
"iconUrl": "/images/icons/icon-3d-shield-check.png"
},
{
"title": "Launch Your Project",
"subtitle": "Create your first workspace",
"content": "Spin up a workspace, invite your team, and start shipping from one place.",
"iconUrl": "/images/icons/icon-3d-rocket-launch.png"
}
];
}Global CSS
To customize the Accordion component classes, you can use the @layer components directive. Learn more .
@layer components {
.av-accordion {
@apply rounded-xl bg-muted/40;
}
.av-accordion__trigger {
@apply text-lg font-semibold;
}
.av-accordion--surface {
@apply border-2 shadow-lg;
}
}Styling Reference
Avesra follows the BEM methodology to ensure component variants and states are reusable and easy to customize.
CSS Classes
The Accordion component uses these CSS classes:
Base Classes
.av-accordion— Base accordion container.av-accordion__body— Content body container.av-accordion__heading— Heading wrapper.av-accordion__indicator— Expand/collapse indicator icon.av-accordion__item— Individual accordion item.av-accordion__panel— Collapsible panel container.av-accordion__trigger— Clickable trigger button
Variant Classes
.av-accordion--surface— Surface variant with raised background
State Classes
.av-accordion__trigger[aria-expanded="true"]— Expanded state.av-accordion__panel[aria-hidden="false"]— Panel visible state
Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
- Hover:
:hoveror[data-hovered="true"]on trigger - Focus:
:focus-visibleor[data-focus-visible="true"]on trigger - Disabled:
:disabledor[aria-disabled="true"]on trigger - Expanded:
[aria-expanded="true"]on trigger
API Reference
See also Disclosure for single-section collapsible content.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'default' | 'surface' | 'default' | Visual style variant (av-accordion). |
hide-separator | boolean | false | Hides separators between accordion items (av-accordion). |
allows-multiple | boolean | false | Allows multiple items to be expanded at once (av-accordion). |
disabled | boolean | false | Disables all accordion items (av-accordion). |
expandedKeys | string[] | [] | Expanded item ids. Supports two-way binding with [(expandedKeys)] (av-accordion). |
id | string | — | Required unique accordion item id (av-accordion-item). |
disabled | boolean | false | Disables a single accordion item (av-accordion-item). |