Skip to content
Navigation

Accordion

Vertically stacked sections that expand one (or several) at a time. For a single independent section use Collapsible; for switching between peer views use Tabs.

Added 14 October 2026

React

Codecomponents/react/accordion-demo.tsx
import {Accordion, AccordionContent, AccordionItem, AccordionTrigger} from '@stroeer-labs/design-library/react';

interface FaqItem {
    value: string;
    question: string;
    answer: string;
    disabled?: boolean;
}

interface FaqSection {
    label: string;
    defaultValue?: Array<string>;
    items: Array<FaqItem>;
}

const sections: Array<FaqSection> = [
    {
        label: 'Single open (default)',
        items: [
            {
                value: 'formats',
                question: 'Which ad formats are supported?',
                answer: 'Billboard, sitebar and mobile banner formats are supported out of the box. Video and special formats are available on request.',
            },
            {
                value: 'budget',
                question: 'How is the daily budget spent?',
                answer: 'Delivery is paced evenly across the day. Unspent budget from quiet hours is redistributed to peak traffic windows, so the daily total is never exceeded.',
            },
            {
                value: 'pause',
                question: 'Can I pause a running campaign?',
                answer: 'Yes — pausing takes effect within a few minutes. The remaining budget stays reserved and delivery resumes where it left off.',
            },
        ],
    },
    {
        label: 'Default open',
        defaultValue: ['schedule'],
        items: [
            {
                value: 'schedule',
                question: 'When does my campaign start?',
                answer: 'Delivery starts on the booked start date at 00:00, once the creative has passed review.',
            },
            {
                value: 'targeting',
                question: 'Which regions can I target?',
                answer: 'Targeting is available per federal state, city or postcode area, and can be combined freely.',
            },
            {
                value: 'reporting',
                question: 'Where do I find reports?',
                answer: 'Impressions, clicks and spend are updated hourly in the campaign dashboard.',
            },
        ],
    },
    {
        label: 'Disabled item',
        items: [
            {
                value: 'invoice',
                question: 'How am I invoiced?',
                answer: 'Invoices are issued monthly, after the delivery period ends.',
            },
            {
                value: 'cancellation',
                question: 'What are the cancellation terms?',
                answer: 'This section is unavailable.',
                disabled: true,
            },
            {
                value: 'support',
                question: 'How do I reach support?',
                answer: 'Use the in-app chat or write to your account team — replies within one business day.',
            },
        ],
    },
];

export function AccordionDemo() {
    return (
        <div className="space-y-6">
            {sections.map((section) => (
                <div key={section.label} className="space-y-2">
                    <p className="text-xs font-medium text-muted-foreground">{section.label}</p>
                    <Accordion defaultValue={section.defaultValue} className="w-full max-w-md">
                        {section.items.map((item) => (
                            <AccordionItem key={item.value} value={item.value} disabled={item.disabled}>
                                <AccordionTrigger>{item.question}</AccordionTrigger>
                                <AccordionContent>
                                    <p className="text-muted-foreground">{item.answer}</p>
                                </AccordionContent>
                            </AccordionItem>
                        ))}
                    </Accordion>
                </div>
            ))}
        </div>
    );
}

Angular

Codecomponents/angular/accordion-demo.ts
import '@angular/compiler';
import {Component} from '@angular/core';
import {NgIcon, provideIcons} from '@ng-icons/core';
import {lucideChevronDown} from '@ng-icons/lucide';
import {HlmAccordionImports} from '@stroeer-labs/design-library/angular';

interface FaqItem {
    question: string;
    answer: string;
    opened?: boolean;
    disabled?: boolean;
}

interface FaqSection {
    label: string;
    items: Array<FaqItem>;
}

@Component({
    selector: 'dl-accordion-demo',
    standalone: true,
    imports: [HlmAccordionImports, NgIcon],
    viewProviders: [provideIcons({lucideChevronDown})],
    template: `
    <div class="space-y-6">
      @for (section of sections; track section.label) {
        <div class="space-y-2">
          <p class="text-xs font-medium text-muted-foreground">{{ section.label }}</p>
          <div hlmAccordion class="w-full max-w-md">
            @for (item of section.items; track item.question) {
              <div hlmAccordionItem [isOpened]="item.opened ?? false" [disabled]="item.disabled ?? false">
                <h3 hlmAccordionHeader>
                  <button hlmAccordionTrigger>
                    {{ item.question }}
                    <ng-icon hlmAccordionIcon name="lucideChevronDown" aria-hidden="true" />
                  </button>
                </h3>
                <hlm-accordion-content>
                  <p class="text-muted-foreground">{{ item.answer }}</p>
                </hlm-accordion-content>
              </div>
            }
          </div>
        </div>
      }
    </div>
  `,
})
export class AccordionDemoComponent {
    protected readonly sections: Array<FaqSection> = [
        {
            label: 'Single open (default)',
            items: [
                {
                    question: 'Which ad formats are supported?',
                    answer: 'Billboard, sitebar and mobile banner formats are supported out of the box. Video and special formats are available on request.',
                },
                {
                    question: 'How is the daily budget spent?',
                    answer: 'Delivery is paced evenly across the day. Unspent budget from quiet hours is redistributed to peak traffic windows, so the daily total is never exceeded.',
                },
                {
                    question: 'Can I pause a running campaign?',
                    answer: 'Yes — pausing takes effect within a few minutes. The remaining budget stays reserved and delivery resumes where it left off.',
                },
            ],
        },
        {
            label: 'Default open',
            items: [
                {
                    question: 'When does my campaign start?',
                    answer: 'Delivery starts on the booked start date at 00:00, once the creative has passed review.',
                    opened: true,
                },
                {
                    question: 'Which regions can I target?',
                    answer: 'Targeting is available per federal state, city or postcode area, and can be combined freely.',
                },
                {
                    question: 'Where do I find reports?',
                    answer: 'Impressions, clicks and spend are updated hourly in the campaign dashboard.',
                },
            ],
        },
        {
            label: 'Disabled item',
            items: [
                {
                    question: 'How am I invoiced?',
                    answer: 'Invoices are issued monthly, after the delivery period ends.',
                },
                {
                    question: 'What are the cancellation terms?',
                    answer: 'This section is unavailable.',
                    disabled: true,
                },
                {
                    question: 'How do I reach support?',
                    answer: 'Use the in-app chat or write to your account team — replies within one business day.',
                },
            ],
        },
    ];
}