or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

accessibility.mdaccordion.mdcollections-data.mddialogs.mddrag-drop.mdindex.mdlistbox.mdmenus.mdobservers.mdoverlays.mdplatform-utilities.mdportals.mdscrolling.mdtesting.mdtext-fields.md
tile.json

accordion.mddocs/

Accordion

The Angular CDK Accordion module provides a flexible accordion component system with support for single/multiple panel expansion, programmatic control, and accessibility features following the ARIA accordion pattern.

Capabilities

Accordion Container

The main accordion component that manages the state of multiple accordion items.

/**
 * Directive for creating accordion containers
 */
class CdkAccordion implements OnDestroy, OnChanges {
  /**
   * Whether multiple accordion panels can be expanded simultaneously
   */
  multi: boolean;

  /**
   * Unique identifier for the accordion
   */
  id: string;

  /**
   * Whether the accordion should close all panels when destroyed
   */
  closeAll: boolean;

  /**
   * Whether keyboard navigation is disabled
   */
  hideToggle: boolean;

  /**
   * Whether to display visual toggle indicators
   */
  displayMode: 'default' | 'flat';

  /**
   * Toggle position for accordion items
   */
  togglePosition: 'before' | 'after';

  /**
   * Stream that emits when the state of the accordion changes
   */
  readonly _stateChanges: Subject<SimpleChanges>;

  /**
   * Stream that emits true/false when openAll/closeAll is triggered
   */
  readonly _openCloseAllActions: Subject<boolean>;

  /**
   * Opens all enabled accordion items in a multi-accordion
   */
  openAll(): void;

  /**
   * Closes all enabled accordion items in a multi-accordion
   */
  closeAll(): void;
}

Usage Example:

import { Component } from '@angular/core';
import { CdkAccordionModule } from '@angular/cdk/accordion';

@Component({
  template: `
    <cdk-accordion 
      [multi]="allowMultiple"
      class="example-accordion">
      
      <cdk-accordion-item #item1="cdkAccordionItem">
        <div class="accordion-item-header" (click)="item1.toggle()">
          <span>Section 1</span>
          <span class="toggle-icon" [class.expanded]="item1.expanded">▼</span>
        </div>
        <div class="accordion-item-body" *ngIf="item1.expanded">
          <p>Content for section 1. This content is only visible when the accordion item is expanded.</p>
          <p>You can put any content here including other components, forms, or complex layouts.</p>
        </div>
      </cdk-accordion-item>

      <cdk-accordion-item #item2="cdkAccordionItem" [disabled]="isSecondDisabled">
        <div class="accordion-item-header" (click)="item2.toggle()">
          <span>Section 2 {{ isSecondDisabled ? '(Disabled)' : '' }}</span>
          <span class="toggle-icon" [class.expanded]="item2.expanded">▼</span>
        </div>
        <div class="accordion-item-body" *ngIf="item2.expanded">
          <p>Content for section 2.</p>
          <button (click)="doSomething()">Action Button</button>
        </div>
      </cdk-accordion-item>

      <cdk-accordion-item #item3="cdkAccordionItem">
        <div class="accordion-item-header" (click)="item3.toggle()">
          <span>Section 3</span>
          <span class="toggle-icon" [class.expanded]="item3.expanded">▼</span>
        </div>
        <div class="accordion-item-body" *ngIf="item3.expanded">
          <p>Content for section 3.</p>
          <ul>
            <li>List item 1</li>
            <li>List item 2</li>
            <li>List item 3</li>
          </ul>
        </div>
      </cdk-accordion-item>
    </cdk-accordion>

    <div class="controls">
      <button (click)="accordion.openAll()" [disabled]="!allowMultiple">Open All</button>
      <button (click)="accordion.closeAll()">Close All</button>
      <button (click)="toggleMultiple()">
        {{ allowMultiple ? 'Single' : 'Multiple' }} Mode
      </button>
      <button (click)="toggleSecondDisabled()">
        {{ isSecondDisabled ? 'Enable' : 'Disable' }} Section 2
      </button>
    </div>
  `,
  styles: [`
    .example-accordion {
      display: block;
      max-width: 500px;
      margin-bottom: 16px;
    }
    
    .accordion-item-header {
      background: #f5f5f5;
      border: 1px solid #ddd;
      padding: 12px 16px;
      cursor: pointer;
      display: flex;
      justify-content: space-between;
      align-items: center;
      user-select: none;
    }
    
    .accordion-item-header:hover {
      background: #eeeeee;
    }
    
    .accordion-item-body {
      border: 1px solid #ddd;
      border-top: none;
      padding: 16px;
      background: white;
    }
    
    .toggle-icon {
      transition: transform 0.2s;
    }
    
    .toggle-icon.expanded {
      transform: rotate(180deg);
    }
    
    .controls {
      display: flex;
      gap: 8px;
      flex-wrap: wrap;
    }
    
    .controls button {
      padding: 8px 16px;
    }
  `],
  standalone: true,
  imports: [CdkAccordionModule]
})
export class AccordionExample {
  allowMultiple = false;
  isSecondDisabled = false;

  @ViewChild(CdkAccordion) accordion!: CdkAccordion;

  toggleMultiple() {
    this.allowMultiple = !this.allowMultiple;
  }

  toggleSecondDisabled() {
    this.isSecondDisabled = !this.isSecondDisabled;
  }

  doSomething() {
    console.log('Action button clicked in accordion item 2');
  }
}

Accordion Item

Individual accordion panel component with expand/collapse functionality.

/**
 * Directive for individual accordion items
 */
class CdkAccordionItem implements OnDestroy {
  /**
   * Whether the accordion item is expanded
   */
  expanded: boolean;

  /**
   * Whether the accordion item is disabled
   */
  disabled: boolean;

  /**
   * Unique identifier for the accordion item
   */
  id: string;

  /**
   * Event emitted when the accordion item is opened
   */
  readonly opened: EventEmitter<void>;

  /**
   * Event emitted when the accordion item is closed
   */
  readonly closed: EventEmitter<void>;

  /**
   * Event emitted when the accordion item is destroyed
   */
  readonly destroyed: EventEmitter<void>;

  /**
   * Event emitted when the expanded state changes
   */
  readonly expandedChange: EventEmitter<boolean>;

  /**
   * Reference to the parent accordion
   */
  accordion: CdkAccordion;

  /**
   * Open the accordion item
   */
  open(): void;

  /**
   * Close the accordion item
   */
  close(): void;

  /**
   * Toggle the accordion item's expanded state
   */
  toggle(): void;

  /**
   * Get the expanded state
   */
  getExpandedState(): boolean;
}

Advanced Accordion Example

import { Component, QueryList, ViewChildren } from '@angular/core';
import { CdkAccordionItem } from '@angular/cdk/accordion';

interface AccordionSection {
  id: string;
  title: string;
  content: string;
  disabled?: boolean;
  icon?: string;
}

@Component({
  template: `
    <div class="accordion-container">
      <div class="accordion-header">
        <h2>FAQ Accordion</h2>
        <div class="header-controls">
          <button (click)="expandAll()" [disabled]="!multiExpand">Expand All</button>
          <button (click)="collapseAll()">Collapse All</button>
          <label>
            <input type="checkbox" [(ngModel)]="multiExpand"> Multiple Expand
          </label>
        </div>
      </div>

      <cdk-accordion [multi]="multiExpand" class="faq-accordion">
        <cdk-accordion-item 
          *ngFor="let section of sections; trackBy: trackBySection"
          #accordionItem="cdkAccordionItem"
          [disabled]="section.disabled"
          (opened)="onSectionOpened(section)"
          (closed)="onSectionClosed(section)"
          (expandedChange)="onExpandedChange(section, $event)"
          class="faq-item">
          
          <div 
            class="faq-header"
            [class.expanded]="accordionItem.expanded"
            [class.disabled]="section.disabled"
            (click)="accordionItem.toggle()"
            (keydown.enter)="accordionItem.toggle()"
            (keydown.space)="accordionItem.toggle(); $event.preventDefault()"
            tabindex="0"
            role="button"
            [attr.aria-expanded]="accordionItem.expanded"
            [attr.aria-controls]="'panel-' + section.id"
            [attr.id]="'header-' + section.id">
            
            <div class="faq-title">
              <span class="faq-icon" *ngIf="section.icon">{{ section.icon }}</span>
              <span>{{ section.title }}</span>
            </div>
            
            <div class="faq-controls">
              <span class="expand-indicator" [class.rotated]="accordionItem.expanded">
                ▼
              </span>
            </div>
          </div>

          <div 
            *ngIf="accordionItem.expanded"
            class="faq-content"
            [attr.id]="'panel-' + section.id"
            [attr.aria-labelledby]="'header-' + section.id"
            role="region">
            <div class="content-inner">
              <p>{{ section.content }}</p>
              <div class="content-actions">
                <button class="link-button" (click)="markAsHelpful(section)">
                  👍 Helpful
                </button>
                <button class="link-button" (click)="reportIssue(section)">
                  🚩 Report Issue
                </button>
              </div>
            </div>
          </div>
        </cdk-accordion-item>
      </cdk-accordion>

      <div class="accordion-footer">
        <p>{{ getExpandedCount() }} of {{ sections.length }} sections expanded</p>
      </div>
    </div>
  `,
  styles: [`
    .accordion-container {
      max-width: 600px;
      margin: 0 auto;
    }
    
    .accordion-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 16px;
      padding-bottom: 16px;
      border-bottom: 1px solid #e0e0e0;
    }
    
    .header-controls {
      display: flex;
      gap: 12px;
      align-items: center;
    }
    
    .faq-accordion {
      border: 1px solid #e0e0e0;
      border-radius: 8px;
      overflow: hidden;
    }
    
    .faq-item:not(:last-child) {
      border-bottom: 1px solid #e0e0e0;
    }
    
    .faq-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 16px 20px;
      background: #fafafa;
      cursor: pointer;
      transition: background-color 0.2s;
      outline: none;
    }
    
    .faq-header:hover:not(.disabled) {
      background: #f0f0f0;
    }
    
    .faq-header:focus {
      background: #e3f2fd;
      box-shadow: inset 0 0 0 2px #2196f3;
    }
    
    .faq-header.expanded {
      background: #e8f5e8;
    }
    
    .faq-header.disabled {
      opacity: 0.6;
      cursor: not-allowed;
    }
    
    .faq-title {
      display: flex;
      align-items: center;
      gap: 8px;
      font-weight: 500;
    }
    
    .faq-icon {
      font-size: 18px;
    }
    
    .expand-indicator {
      transition: transform 0.2s ease;
      font-size: 12px;
    }
    
    .expand-indicator.rotated {
      transform: rotate(180deg);
    }
    
    .faq-content {
      background: white;
    }
    
    .content-inner {
      padding: 20px;
    }
    
    .content-actions {
      margin-top: 12px;
      display: flex;
      gap: 16px;
    }
    
    .link-button {
      background: none;
      border: none;
      color: #2196f3;
      cursor: pointer;
      padding: 4px 0;
      font-size: 14px;
    }
    
    .link-button:hover {
      text-decoration: underline;
    }
    
    .accordion-footer {
      margin-top: 16px;
      text-align: center;
      color: #666;
      font-size: 14px;
    }
  `]
})
export class AdvancedAccordionExample {
  @ViewChildren(CdkAccordionItem) accordionItems!: QueryList<CdkAccordionItem>;
  
  multiExpand = false;

  sections: AccordionSection[] = [
    {
      id: '1',
      title: 'How do I reset my password?',
      content: 'To reset your password, click on the "Forgot Password" link on the login page and follow the instructions sent to your email.',
      icon: '🔐'
    },
    {
      id: '2', 
      title: 'How can I update my profile information?',
      content: 'You can update your profile by going to Settings > Profile and editing the fields you want to change.',
      icon: '👤'
    },
    {
      id: '3',
      title: 'What payment methods do you accept?',
      content: 'We accept all major credit cards, PayPal, and bank transfers. Premium users also have access to additional payment options.',
      icon: '💳'
    },
    {
      id: '4',
      title: 'How do I contact customer support?',
      content: 'You can reach our customer support team through the chat widget, email at support@example.com, or by calling 1-800-SUPPORT.',
      icon: '📞',
      disabled: false
    },
    {
      id: '5',
      title: 'Is my data secure?',
      content: 'Yes, we use industry-standard encryption and security practices to protect your data. All communications are encrypted and we never share your personal information.',
      icon: '🛡️'
    }
  ];

  expandAll() {
    this.accordionItems.forEach(item => {
      if (!item.disabled) {
        item.open();
      }
    });
  }

  collapseAll() {
    this.accordionItems.forEach(item => {
      item.close();
    });
  }

  onSectionOpened(section: AccordionSection) {
    console.log(`Section "${section.title}" opened`);
  }

  onSectionClosed(section: AccordionSection) {
    console.log(`Section "${section.title}" closed`);
  }

  onExpandedChange(section: AccordionSection, expanded: boolean) {
    console.log(`Section "${section.title}" ${expanded ? 'expanded' : 'collapsed'}`);
  }

  markAsHelpful(section: AccordionSection) {
    console.log(`Marked section "${section.title}" as helpful`);
  }

  reportIssue(section: AccordionSection) {
    console.log(`Reported issue with section "${section.title}"`);
  }

  getExpandedCount(): number {
    return this.accordionItems?.filter(item => item.expanded).length || 0;
  }

  trackBySection(index: number, section: AccordionSection): string {
    return section.id;
  }
}

Injection Token

/**
 * Injection token for accordion instances
 */
const CDK_ACCORDION: InjectionToken<CdkAccordion>;

CDK Accordion Module

/**
 * Angular module that includes all CDK accordion directives
 */
class CdkAccordionModule {}