アコーディオン
概要
アコーディオンは、関連するコンテンツを展開・折りたたみ可能なセクションに整理し、ページのスクロールを減らし、ユーザーが関連情報に集中するのを助けます。各セクションには、トリガーボタンとコンテンツパネルがあります。トリガーをクリックすると、関連するパネルの表示/非表示が切り替わります。
TS
import {Component} from '@angular/core';import { AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent,} from '@angular/aria/accordion';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],})export class App {}
HTML
<div ngAccordionGroup class="basic-accordion" [multiExpandable]="false"> <h3> <span ngAccordionTrigger panelId="q1" #trigger1="ngAccordionTrigger" [expanded]="true"> Which attribute tells assistive tech whether the panel is open or closed? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger1.expanded()" ></span> </span> </h3> <div ngAccordionPanel panelId="q1"> <ng-template ngAccordionContent> <p> Use <code>aria-expanded</code> on the button element. Set it to "true" when the content panel is visible and "false" when the content is hidden. This is a crucial state indicator for screen reader users. </p> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q2" #trigger2="ngAccordionTrigger"> How do you link the button to the content it controls? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger2.expanded()" ></span> </span> </h3> <div ngAccordionPanel panelId="q2"> <ng-template ngAccordionContent> <p> Use the <code>aria-controls</code> attribute on the button, and set its value to match the id of the related content panel. This establishes a programmatic relationship, allowing assistive technologies to jump directly to the relevant content. </p> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q3" #trigger3="ngAccordionTrigger"> What role should the heading element containing the accordion button have? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger3.expanded()" ></span> </span> </h3> <div ngAccordionPanel panelId="q3"> <ng-template ngAccordionContent> <p> The element containing the button should typically have <code>role="heading"</code> with an appropriate <code>aria-level</code> to define the structure. This ensures the accordion section is recognized as a section header, making the page structure navigable for users. </p> </ng-template> </div></div>
CSS
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font);}[ngAccordionGroup] { width: 500px;}h3 { font-size: 1rem; margin: 0; color: var(--secondary-contrast); box-sizing: border-box; position: relative;}h3:focus-within::before,h3:hover::before { content: ''; position: absolute; height: 100%; width: 2px; background-color: var(--vivid-pink); top: 0; left: 0;}h3:not(:first-of-type) { border-block-start: 1px solid var(--senary-contrast);}p { margin: 0; padding: 0 1.5rem 1.5rem 1.5rem; color: var(--tertiary-contrast); font-size: 0.875rem;}[ngAccordionTrigger] { display: flex; align-items: center; justify-content: space-between; outline: none; cursor: pointer; padding: 1.5rem;}[ngAccordionTrigger][aria-expanded='true'] { background-image: var(--pink-to-purple-horizontal-gradient); background-clip: text; color: transparent;}[ngAccordionTrigger][aria-disabled='true'] { opacity: 0.5; cursor: default;}.expand-icon { position: relative; width: 1rem; height: 1rem; flex-shrink: 0; margin-left: 1rem;}.expand-icon::before,.expand-icon::after { content: ''; position: absolute; width: 100%; height: 2px; top: 50%; background-color: var(--quaternary-contrast); transition: .3s ease-out;}.expand-icon::after { transform: rotate(90deg);}.expand-icon__expanded::before { transform: translateY(-50%) rotate(-90deg); opacity: 0;}.expand-icon__expanded::after { transform: translateY(-50%) rotate(0); background-color: var(--electric-violet);}
使い方
アコーディオンは、ユーザーが通常一度に1つのセクションを表示する必要がある場合に、コンテンツを論理的なグループに整理するのに適しています。
アコーディオンを使用する場合:
- 複数の質問と回答を持つFAQを表示する
- 長いフォームを管理しやすいセクションに整理する
- コンテンツの多いページでのスクロールを減らす
- 関連情報を段階的に開示する
アコーディオンを避けるべき場合:
- ナビゲーションメニューを構築する(代わりにMenuコンポーネントを使用してください)
- タブ付きインターフェースを作成する(代わりにTabsコンポーネントを使用してください)
- 単一の折りたたみ可能なセクションを表示する(代わりにdisclosureパターンを使用してください)
- ユーザーが複数のセクションを同時に見る必要がある(異なるレイアウトを検討してください)
機能
- 展開モード - 一度に1つまたは複数のパネルを開けるかどうかを制御します
- キーボードナビゲーション - 矢印キー、Home、Endを使用してトリガー間を移動します
- 遅延レンダリング - コンテンツはパネルが最初に展開されたときにのみ作成され、初期読み込みのパフォーマンスを向上させます
- 無効状態 - グループ全体または個々のトリガーを無効にします
- フォーカス管理 - 無効化されたアイテムがキーボードフォーカスを受け取れるかどうかを制御します
- プログラムによる制御 - コンポーネントのコードからパネルを展開、折りたたみ、または切り替えます
- RTLサポート - 右から左へ記述する言語を自動的にサポートします
例
単一展開モード
[multiExpandable]="false"を設定すると、一度に開けるパネルが1つだけになります。新しいパネルを開くと、以前に開いていたパネルは自動的に閉じます。
TS
import {Component} from '@angular/core';import { AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent,} from '@angular/aria/accordion';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],})export class App {}
HTML
<div ngAccordionGroup class="basic-accordion" [multiExpandable]="false"> <h3> <span ngAccordionTrigger panelId="q1" #trigger1="ngAccordionTrigger" [expanded]="true"> Which attribute tells assistive tech whether the panel is open or closed? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger1.expanded()" ></span> </span> </h3> <div ngAccordionPanel panelId="q1"> <ng-template ngAccordionContent> <p> Use <code>aria-expanded</code> on the button element. Set it to "true" when the content panel is visible and "false" when the content is hidden. This is a crucial state indicator for screen reader users. </p> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q2" #trigger2="ngAccordionTrigger"> How do you link the button to the content it controls? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger2.expanded()" ></span> </span> </h3> <div ngAccordionPanel panelId="q2"> <ng-template ngAccordionContent> <p> Use the <code>aria-controls</code> attribute on the button, and set its value to match the id of the related content panel. This establishes a programmatic relationship, allowing assistive technologies to jump directly to the relevant content. </p> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q3" #trigger3="ngAccordionTrigger"> What role should the heading element containing the accordion button have? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger3.expanded()" ></span> </span> </h3> <div ngAccordionPanel panelId="q3"> <ng-template ngAccordionContent> <p> The element containing the button should typically have <code>role="heading"</code> with an appropriate <code>aria-level</code> to define the structure. This ensures the accordion section is recognized as a section header, making the page structure navigable for users. </p> </ng-template> </div></div>
CSS
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font);}[ngAccordionGroup] { width: 500px;}h3 { font-size: 1rem; margin: 0; color: var(--secondary-contrast); box-sizing: border-box; position: relative;}h3:focus-within::before,h3:hover::before { content: ''; position: absolute; height: 100%; width: 2px; background-color: var(--vivid-pink); top: 0; left: 0;}h3:not(:first-of-type) { border-block-start: 1px solid var(--senary-contrast);}p { margin: 0; padding: 0 1.5rem 1.5rem 1.5rem; color: var(--tertiary-contrast); font-size: 0.875rem;}[ngAccordionTrigger] { display: flex; align-items: center; justify-content: space-between; outline: none; cursor: pointer; padding: 1.5rem;}[ngAccordionTrigger][aria-expanded='true'] { background-image: var(--pink-to-purple-horizontal-gradient); background-clip: text; color: transparent;}[ngAccordionTrigger][aria-disabled='true'] { opacity: 0.5; cursor: default;}.expand-icon { position: relative; width: 1rem; height: 1rem; flex-shrink: 0; margin-left: 1rem;}.expand-icon::before,.expand-icon::after { content: ''; position: absolute; width: 100%; height: 2px; top: 50%; background-color: var(--quaternary-contrast); transition: .3s ease-out;}.expand-icon::after { transform: rotate(90deg);}.expand-icon__expanded::before { transform: translateY(-50%) rotate(-90deg); opacity: 0;}.expand-icon__expanded::after { transform: translateY(-50%) rotate(0); background-color: var(--electric-violet);}
TS
import {Component} from '@angular/core';import { AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent,} from '@angular/aria/accordion';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],})export class App {}
HTML
<div ngAccordionGroup class="material-accordion" [multiExpandable]="false"> <h3> <span ngAccordionTrigger panelId="q1" #trigger1="ngAccordionTrigger"> Which attribute tells assistive tech whether the panel is open or closed? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger1.expanded()" translate="no">keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel panelId="q1"> <ng-template ngAccordionContent> <p> Use <code>aria-expanded</code> on the button element. Set it to "true" when the content panel is visible and "false" when the content is hidden. This is a crucial state indicator for screen reader users. </p> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q2" #trigger2="ngAccordionTrigger" [expanded]="true"> How do you link the button to the content it controls? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger2.expanded()" translate="no">keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel panelId="q2"> <ng-template ngAccordionContent> <p> Use the <code>aria-controls</code> attribute on the button, and set its value to match the id of the related content panel. This establishes a programmatic relationship, allowing assistive technologies to jump directly to the relevant content. </p> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q3" #trigger3="ngAccordionTrigger"> What role should the heading element containing the accordion button have? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger3.expanded()" translate="no">keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel panelId="q3"> <ng-template ngAccordionContent> <p> The element containing the button should typically have <code>role="heading"</code> with an appropriate <code>aria-level</code> to define the structure. This ensures the accordion section is recognized as a section header, making the page structure navigable for users. </p> </ng-template> </div></div>
CSS
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font);}[ngAccordionGroup] { width: 500px;}h3 { font-size: 1rem; margin: 0; color: var(--secondary-contrast); box-sizing: border-box; border-block-start: 1px solid var(--quinary-contrast); border-inline: 1px solid var(--quinary-contrast);}h3:focus-within,h3:hover { background-color: var(--septenary-contrast);}h3:first-of-type { border-radius: 1rem 1rem 0 0;}h3:last-of-type { border-block-end: 1px solid var(--quinary-contrast); border-radius: 0 0 1rem 1rem;}h3:last-of-type:has([aria-expanded='true']) { border-block-end: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0;}p { margin: 0; padding: 0 1.5rem 1.5rem 1.5rem; color: var(--tertiary-contrast); font-size: 0.875rem; border-inline: 1px solid var(--quinary-contrast); border-radius: 0 0 1rem 1rem; border-block-end: 1px solid var(--quinary-contrast); margin-block-end: 1rem;}[ngAccordionTrigger] { display: flex; align-items: center; justify-content: space-between; outline: none; cursor: pointer; padding: 1.5rem;}[ngAccordionTrigger][aria-disabled='true'] { opacity: 0.5; cursor: default;}.expand-icon { font-size: 1.5rem; color: var(--quaternary-contrast); transition: transform .3s ease-out;}.expand-icon__expanded { transform: rotate(180deg);}
TS
import {Component} from '@angular/core';import { AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent,} from '@angular/aria/accordion';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],})export class App {}
HTML
<div ngAccordionGroup class="retro-accordion" [multiExpandable]="false"> <h3> <span ngAccordionTrigger panelId="q1" #trigger1="ngAccordionTrigger"> Unlock Treasure Box <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{trigger1.expanded() ? 'lock_open_right' : 'lock'}}</span > </span> </h3> <div ngAccordionPanel panelId="q1"> <ng-template ngAccordionContent> <div class="treasure-box">👻</div> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q2" #trigger2="ngAccordionTrigger"> Unlock Treasure Box <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{trigger2.expanded() ? 'lock_open_right' : 'lock'}}</span > </span> </h3> <div ngAccordionPanel panelId="q2"> <ng-template ngAccordionContent> <div class="treasure-box">💎💎💎</div> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q3" #trigger3="ngAccordionTrigger"> Unlock Treasure Box <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{trigger3.expanded() ? 'lock_open_right' : 'lock'}}</span > </span> </h3> <div ngAccordionPanel panelId="q3"> <ng-template ngAccordionContent> <div class="treasure-box">🐸 ribbit...ribbit...</div> </ng-template> </div></div>
CSS
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');:host { display: flex; justify-content: center; font-family: 'Press Start 2P'; --retro-button-color: color-mix(in srgb, var(--symbolic-yellow) 90%, var(--gray-1000)); --retro-button-text-color: color-mix(in srgb, var(--symbolic-yellow) 10%, white); --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-flat-shadow: 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700);}[ngAccordionGroup] { width: 500px;}h3 { font-size: 1rem; margin: 0; color: var(--retro-button-text-color); background-color: var(--retro-button-color); box-shadow: var(--retro-clickable-shadow); transition: transform 0.1s, box-shadow 0.1s;}h3:focus-within,h3:hover { box-shadow: var(--retro-pressed-shadow); transform: translate(1px, 1px); outline-offset: 6px; outline: 4px dashed color-mix(in srgb, var(--hot-pink) 60%, transparent);}h3:has([aria-disabled='true']) { opacity: 0.5; cursor: default;}.treasure-box { margin: 0; padding: 1rem; display: flex; justify-content: center; align-items: center; height: 5rem; background-color: var(--septenary-contrast); box-shadow: var(--retro-flat-shadow);}[ngAccordionTrigger] { display: flex; align-items: center; justify-content: space-between; outline: none; cursor: pointer; padding: 1.5rem;}[ngAccordionPanel] { padding: 1rem;}
このモードは、FAQや、ユーザーに一度に1つの回答に集中してもらいたい場合に適しています。
複数展開モード
[multiExpandable]="true"を設定すると、複数のパネルを同時に開くことができます。ユーザーは他のパネルを閉じることなく、必要なだけパネルを展開できます。
TS
import {Component} from '@angular/core';import { AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent,} from '@angular/aria/accordion';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],})export class App {}
HTML
<div ngAccordionGroup class="basic-accordion"> <h3> <span ngAccordionTrigger panelId="q1" #trigger1="ngAccordionTrigger" [expanded]="true"> Which attribute tells assistive tech whether the panel is open or closed? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger1.expanded()" ></span> </span> </h3> <div ngAccordionPanel panelId="q1"> <ng-template ngAccordionContent> <p> Use <code>aria-expanded</code> on the button element. Set it to "true" when the content panel is visible and "false" when the content is hidden. This is a crucial state indicator for screen reader users. </p> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q2" #trigger2="ngAccordionTrigger"> How do you link the button to the content it controls? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger2.expanded()" ></span> </span> </h3> <div ngAccordionPanel panelId="q2"> <ng-template ngAccordionContent> <p> Use the <code>aria-controls</code> attribute on the button, and set its value to match the id of the related content panel. This establishes a programmatic relationship, allowing assistive technologies to jump directly to the relevant content. </p> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q3" #trigger3="ngAccordionTrigger" [expanded]="true"> What role should the heading element containing the accordion button have? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger3.expanded()" ></span> </span> </h3> <div ngAccordionPanel panelId="q3"> <ng-template ngAccordionContent> <p> The element containing the button should typically have <code>role="heading"</code> with an appropriate <code>aria-level</code> to define the structure. This ensures the accordion section is recognized as a section header, making the page structure navigable for users. </p> </ng-template> </div></div>
CSS
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font);}[ngAccordionGroup] { width: 500px;}h3 { font-size: 1rem; margin: 0; color: var(--secondary-contrast); box-sizing: border-box; position: relative;}h3:focus-within::before,h3:hover::before { content: ''; position: absolute; height: 100%; width: 2px; background-color: var(--vivid-pink); top: 0; left: 0;}h3:not(:first-of-type) { border-block-start: 1px solid var(--senary-contrast);}p { margin: 0; padding: 0 1.5rem 1.5rem 1.5rem; color: var(--tertiary-contrast); font-size: 0.875rem;}[ngAccordionTrigger] { display: flex; align-items: center; justify-content: space-between; outline: none; cursor: pointer; padding: 1.5rem;}[ngAccordionTrigger][aria-expanded='true'] { background-image: var(--pink-to-purple-horizontal-gradient); background-clip: text; color: transparent;}[ngAccordionTrigger][aria-disabled='true'] { opacity: 0.5; cursor: default;}.expand-icon { position: relative; width: 1rem; height: 1rem; flex-shrink: 0; margin-left: 1rem;}.expand-icon::before,.expand-icon::after { content: ''; position: absolute; width: 100%; height: 2px; top: 50%; background-color: var(--quaternary-contrast); transition: .3s ease-out;}.expand-icon::after { transform: rotate(90deg);}.expand-icon__expanded::before { transform: translateY(-50%) rotate(-90deg); opacity: 0;}.expand-icon__expanded::after { transform: translateY(-50%) rotate(0); background-color: var(--electric-violet);}
TS
import {Component} from '@angular/core';import { AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent,} from '@angular/aria/accordion';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],})export class App {}
HTML
<div ngAccordionGroup class="material-accordion"> <h3> <span ngAccordionTrigger panelId="q1" #trigger1="ngAccordionTrigger" [expanded]="true"> Which attribute tells assistive tech whether the panel is open or closed? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger1.expanded()" translate="no">keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel panelId="q1"> <ng-template ngAccordionContent> <p> Use <code>aria-expanded</code> on the button element. Set it to "true" when the content panel is visible and "false" when the content is hidden. This is a crucial state indicator for screen reader users. </p> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q2" #trigger2="ngAccordionTrigger"> How do you link the button to the content it controls? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger2.expanded()" translate="no">keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel panelId="q2"> <ng-template ngAccordionContent> <p> Use the <code>aria-controls</code> attribute on the button, and set its value to match the id of the related content panel. This establishes a programmatic relationship, allowing assistive technologies to jump directly to the relevant content. </p> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q3" #trigger3="ngAccordionTrigger" [expanded]="true"> What role should the heading element containing the accordion button have? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger3.expanded()" translate="no">keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel panelId="q3"> <ng-template ngAccordionContent> <p> The element containing the button should typically have <code>role="heading"</code> with an appropriate <code>aria-level</code> to define the structure. This ensures the accordion section is recognized as a section header, making the page structure navigable for users. </p> </ng-template> </div></div>
CSS
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font);}[ngAccordionGroup] { width: 500px;}h3 { font-size: 1rem; margin: 0; color: var(--secondary-contrast); box-sizing: border-box; border-block-start: 1px solid var(--quinary-contrast); border-inline: 1px solid var(--quinary-contrast);}h3:focus-within,h3:hover { background-color: var(--septenary-contrast);}h3:first-of-type { border-radius: 1rem 1rem 0 0;}h3:last-of-type { border-block-end: 1px solid var(--quinary-contrast); border-radius: 0 0 1rem 1rem;}h3:last-of-type:has([aria-expanded='true']) { border-block-end: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0;}p { margin: 0; padding: 0 1.5rem 1.5rem 1.5rem; color: var(--tertiary-contrast); font-size: 0.875rem; border-inline: 1px solid var(--quinary-contrast); border-radius: 0 0 1rem 1rem; border-block-end: 1px solid var(--quinary-contrast); margin-block-end: 1rem;}[ngAccordionTrigger] { display: flex; align-items: center; justify-content: space-between; outline: none; cursor: pointer; padding: 1.5rem;}[ngAccordionTrigger][aria-disabled='true'] { opacity: 0.5; cursor: default;}.expand-icon { font-size: 1.5rem; color: var(--quaternary-contrast); transition: transform .3s ease-out;}.expand-icon__expanded { transform: rotate(180deg);}
TS
import {Component} from '@angular/core';import { AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent,} from '@angular/aria/accordion';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],})export class App {}
HTML
<div ngAccordionGroup class="retro-accordion"> <h3> <span ngAccordionTrigger panelId="q1" #trigger1="ngAccordionTrigger"> Unlock Treasure Box <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{trigger1.expanded() ? 'lock_open_right' : 'lock'}}</span > </span> </h3> <div ngAccordionPanel panelId="q1"> <ng-template ngAccordionContent> <div class="treasure-box">👻</div> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q2" #trigger2="ngAccordionTrigger"> Unlock Treasure Box <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{trigger2.expanded() ? 'lock_open_right' : 'lock'}}</span > </span> </h3> <div ngAccordionPanel panelId="q2"> <ng-template ngAccordionContent> <div class="treasure-box">💎💎💎</div> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q3" #trigger3="ngAccordionTrigger"> Unlock Treasure Box <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{trigger3.expanded() ? 'lock_open_right' : 'lock'}}</span > </span> </h3> <div ngAccordionPanel panelId="q3"> <ng-template ngAccordionContent> <div class="treasure-box">🐸 ribbit...ribbit...</div> </ng-template> </div></div>
CSS
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');:host { display: flex; justify-content: center; font-family: 'Press Start 2P'; --retro-button-color: color-mix(in srgb, var(--symbolic-yellow) 90%, var(--gray-1000)); --retro-button-text-color: color-mix(in srgb, var(--symbolic-yellow) 10%, white); --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-flat-shadow: 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700);}[ngAccordionGroup] { width: 500px;}h3 { font-size: 1rem; margin: 0; color: var(--retro-button-text-color); background-color: var(--retro-button-color); box-shadow: var(--retro-clickable-shadow); transition: transform 0.1s, box-shadow 0.1s;}h3:focus-within,h3:hover { box-shadow: var(--retro-pressed-shadow); transform: translate(1px, 1px); outline-offset: 6px; outline: 4px dashed color-mix(in srgb, var(--hot-pink) 60%, transparent);}h3:has([aria-disabled='true']) { opacity: 0.5; cursor: default;}.treasure-box { margin: 0; padding: 1rem; display: flex; justify-content: center; align-items: center; height: 5rem; background-color: var(--septenary-contrast); box-shadow: var(--retro-flat-shadow);}[ngAccordionTrigger] { display: flex; align-items: center; justify-content: space-between; outline: none; cursor: pointer; padding: 1.5rem;}[ngAccordionPanel] { padding: 1rem;}
このモードは、フォームのセクションや、ユーザーが複数のパネルにわたるコンテンツを比較する必要がある場合に便利です。
NOTE: multiExpandable入力はデフォルトでtrueです。単一展開の動作が必要な場合は、明示的にfalseに設定してください。
無効化されたアコーディオンアイテム
disabled入力を使用して特定のトリガーを無効にします。アコーディオンのグループでsoftDisabled入力を使用し、キーボードナビゲーション中に無効化されたアイテムの動作を制御します。
TS
import {Component} from '@angular/core';import { AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent,} from '@angular/aria/accordion';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],})export class App {}
HTML
<div ngAccordionGroup class="basic-accordion"> <h3> <span ngAccordionTrigger panelId="q1" #trigger1="ngAccordionTrigger" [expanded]="true"> Which attribute tells assistive tech whether the panel is open or closed? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger1.expanded()" ></span> </span> </h3> <div ngAccordionPanel panelId="q1"> <ng-template ngAccordionContent> <p> Use <code>aria-expanded</code> on the button element. Set it to "true" when the content panel is visible and "false" when the content is hidden. This is a crucial state indicator for screen reader users. </p> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q2" #trigger2="ngAccordionTrigger" disabled> How do you link the button to the content it controls? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger2.expanded()" ></span> </span> </h3> <div ngAccordionPanel panelId="q2"> <ng-template ngAccordionContent> <p> Use the <code>aria-controls</code> attribute on the button, and set its value to match the id of the related content panel. This establishes a programmatic relationship, allowing assistive technologies to jump directly to the relevant content. </p> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q3" #trigger3="ngAccordionTrigger"> What role should the heading element containing the accordion button have? <span aria-hidden="true" class="expand-icon" [class.expand-icon__expanded]="trigger3.expanded()" ></span> </span> </h3> <div ngAccordionPanel panelId="q3"> <ng-template ngAccordionContent> <p> The element containing the button should typically have <code>role="heading"</code> with an appropriate <code>aria-level</code> to define the structure. This ensures the accordion section is recognized as a section header, making the page structure navigable for users. </p> </ng-template> </div></div>
CSS
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font);}[ngAccordionGroup] { width: 500px;}h3 { font-size: 1rem; margin: 0; color: var(--secondary-contrast); box-sizing: border-box; position: relative;}h3:focus-within::before,h3:hover::before { content: ''; position: absolute; height: 100%; width: 2px; background-color: var(--vivid-pink); top: 0; left: 0;}h3:not(:first-of-type) { border-block-start: 1px solid var(--senary-contrast);}p { margin: 0; padding: 0 1.5rem 1.5rem 1.5rem; color: var(--tertiary-contrast); font-size: 0.875rem;}[ngAccordionTrigger] { display: flex; align-items: center; justify-content: space-between; outline: none; cursor: pointer; padding: 1.5rem;}[ngAccordionTrigger][aria-expanded='true'] { background-image: var(--pink-to-purple-horizontal-gradient); background-clip: text; color: transparent;}[ngAccordionTrigger][aria-disabled='true'] { opacity: 0.5; cursor: default;}.expand-icon { position: relative; width: 1rem; height: 1rem; flex-shrink: 0; margin-left: 1rem;}.expand-icon::before,.expand-icon::after { content: ''; position: absolute; width: 100%; height: 2px; top: 50%; background-color: var(--quaternary-contrast); transition: .3s ease-out;}.expand-icon::after { transform: rotate(90deg);}.expand-icon__expanded::before { transform: translateY(-50%) rotate(-90deg); opacity: 0;}.expand-icon__expanded::after { transform: translateY(-50%) rotate(0); background-color: var(--electric-violet);}
TS
import {Component} from '@angular/core';import { AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent,} from '@angular/aria/accordion';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],})export class App {}
HTML
<div ngAccordionGroup class="material-accordion"> <h3> <span ngAccordionTrigger panelId="q1" #trigger1="ngAccordionTrigger" [expanded]="true"> Which attribute tells assistive tech whether the panel is open or closed? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger1.expanded()" translate="no">keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel panelId="q1"> <ng-template ngAccordionContent> <p> Use <code>aria-expanded</code> on the button element. Set it to "true" when the content panel is visible and "false" when the content is hidden. This is a crucial state indicator for screen reader users. </p> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q2" #trigger2="ngAccordionTrigger" disabled> How do you link the button to the content it controls? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger2.expanded()" translate="no">keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel panelId="q2"> <ng-template ngAccordionContent> <p> Use the <code>aria-controls</code> attribute on the button, and set its value to match the id of the related content panel. This establishes a programmatic relationship, allowing assistive technologies to jump directly to the relevant content. </p> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q3" #trigger3="ngAccordionTrigger"> What role should the heading element containing the accordion button have? <span aria-hidden="true" class="material-symbols-outlined expand-icon" [class.expand-icon__expanded]="trigger3.expanded()" translate="no">keyboard_arrow_up</span > </span> </h3> <div ngAccordionPanel panelId="q3"> <ng-template ngAccordionContent> <p> The element containing the button should typically have <code>role="heading"</code> with an appropriate <code>aria-level</code> to define the structure. This ensures the accordion section is recognized as a section header, making the page structure navigable for users. </p> </ng-template> </div></div>
CSS
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');:host { display: flex; justify-content: center; font-family: var(--inter-font);}[ngAccordionGroup] { width: 500px;}h3 { font-size: 1rem; margin: 0; color: var(--secondary-contrast); box-sizing: border-box; border-block-start: 1px solid var(--quinary-contrast); border-inline: 1px solid var(--quinary-contrast);}h3:focus-within,h3:hover { background-color: var(--septenary-contrast);}h3:first-of-type { border-radius: 1rem 1rem 0 0;}h3:last-of-type { border-block-end: 1px solid var(--quinary-contrast); border-radius: 0 0 1rem 1rem;}h3:last-of-type:has([aria-expanded='true']) { border-block-end: 0; border-bottom-left-radius: 0; border-bottom-right-radius: 0;}p { margin: 0; padding: 0 1.5rem 1.5rem 1.5rem; color: var(--tertiary-contrast); font-size: 0.875rem; border-inline: 1px solid var(--quinary-contrast); border-radius: 0 0 1rem 1rem; border-block-end: 1px solid var(--quinary-contrast); margin-block-end: 1rem;}[ngAccordionTrigger] { display: flex; align-items: center; justify-content: space-between; outline: none; cursor: pointer; padding: 1.5rem;}[ngAccordionTrigger][aria-disabled='true'] { opacity: 0.5; cursor: default;}.expand-icon { font-size: 1.5rem; color: var(--quaternary-contrast); transition: transform .3s ease-out;}.expand-icon__expanded { transform: rotate(180deg);}
TS
import {Component} from '@angular/core';import { AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent,} from '@angular/aria/accordion';@Component({ selector: 'app-root', templateUrl: 'app.component.html', styleUrl: 'app.component.css', imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent],})export class App {}
HTML
<div ngAccordionGroup class="retro-accordion"> <h3> <span ngAccordionTrigger panelId="q1" #trigger1="ngAccordionTrigger"> Unlock Treasure Box <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{trigger1.expanded() ? 'lock_open_right' : 'lock'}}</span > </span> </h3> <div ngAccordionPanel panelId="q1"> <ng-template ngAccordionContent> <div class="treasure-box">👻</div> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q2" #trigger2="ngAccordionTrigger" disabled> Unlock Treasure Box <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{trigger2.expanded() ? 'lock_open_right' : 'lock'}}</span > </span> </h3> <div ngAccordionPanel panelId="q2"> <ng-template ngAccordionContent> <div class="treasure-box">💎💎💎</div> </ng-template> </div> <h3> <span ngAccordionTrigger panelId="q3" #trigger3="ngAccordionTrigger" [expanded]="true"> Unlock Treasure Box <span aria-hidden="true" class="material-symbols-outlined" translate="no">{{trigger3.expanded() ? 'lock_open_right' : 'lock'}}</span > </span> </h3> <div ngAccordionPanel panelId="q3"> <ng-template ngAccordionContent> <div class="treasure-box">🐸 ribbit...ribbit...</div> </ng-template> </div></div>
CSS
@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined');@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');:host { display: flex; justify-content: center; font-family: 'Press Start 2P'; --retro-button-color: color-mix(in srgb, var(--symbolic-yellow) 90%, var(--gray-1000)); --retro-button-text-color: color-mix(in srgb, var(--symbolic-yellow) 10%, white); --retro-shadow-light: color-mix(in srgb, var(--retro-button-color) 90%, #fff); --retro-shadow-dark: color-mix(in srgb, var(--retro-button-color) 90%, #000); --retro-elevated-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-flat-shadow: 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700); --retro-clickable-shadow: inset 4px 4px 0px 0px var(--retro-shadow-light), inset -4px -4px 0px 0px var(--retro-shadow-dark), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 8px 8px 0px 0px var(--gray-700); --retro-pressed-shadow: inset 4px 4px 0px 0px var(--retro-shadow-dark), inset -4px -4px 0px 0px var(--retro-shadow-light), 4px 0px 0px 0px var(--gray-700), 0px 4px 0px 0px var(--gray-700), -4px 0px 0px 0px var(--gray-700), 0px -4px 0px 0px var(--gray-700), 0px 0px 0px 0px var(--gray-700);}[ngAccordionGroup] { width: 500px;}h3 { font-size: 1rem; margin: 0; color: var(--retro-button-text-color); background-color: var(--retro-button-color); box-shadow: var(--retro-clickable-shadow); transition: transform 0.1s, box-shadow 0.1s;}h3:focus-within,h3:hover { box-shadow: var(--retro-pressed-shadow); transform: translate(1px, 1px); outline-offset: 6px; outline: 4px dashed color-mix(in srgb, var(--hot-pink) 60%, transparent);}h3:has([aria-disabled='true']) { opacity: 0.5; cursor: default;}.treasure-box { margin: 0; padding: 1rem; display: flex; justify-content: center; align-items: center; height: 5rem; background-color: var(--septenary-contrast); box-shadow: var(--retro-flat-shadow);}[ngAccordionTrigger] { display: flex; align-items: center; justify-content: space-between; outline: none; cursor: pointer; padding: 1.5rem;}[ngAccordionPanel] { padding: 1rem;}
[softDisabled]="true"(デフォルト)の場合、無効化されたアイテムはフォーカスを受け取れますが、アクティブにはできません。[softDisabled]="false"の場合、無効化されたアイテムはキーボードナビゲーション中に完全にスキップされます。
コンテンツの遅延レンダリング
ngAccordionContentディレクティブをng-templateで使用すると、パネルが最初に展開されるまでコンテンツのレンダリングを遅延させることができます。これにより、画像、チャート、または複雑なコンポーネントなどの重いコンテンツを持つアコーディオンのパフォーマンスが向上します。
<div ngAccordionGroup> <div> <button ngAccordionTrigger panelId="item-1"> Trigger Text </button> <div ngAccordionPanel panelId="item-1"> <ng-template ngAccordionContent> <!-- このコンテンツは、パネルが最初に開かれたときにのみレンダリングされます --> <img src="large-image.jpg" alt="Description"> <app-expensive-component /> </ng-template> </div> </div></div>
デフォルトでは、パネルが折りたたまれた後もコンテンツはDOMに残ります。パネルが閉じたときにDOMからコンテンツを削除するには、[preserveContent]="false"を設定します。
API
AccordionGroup
アコーディオンアイテムのグループのキーボードナビゲーションと展開動作を管理するコンテナディレクティブです。
Inputs
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
disabled |
boolean |
false |
グループ内のすべてのトリガーを無効にします |
multiExpandable |
boolean |
true |
複数のパネルを同時に展開できるかどうか |
softDisabled |
boolean |
true |
trueの場合、無効化されたアイテムはフォーカス可能です。falseの場合、スキップされます |
wrap |
boolean |
false |
キーボードナビゲーションが最後のアイテムから最初のアイテムへ、またはその逆にラップするかどうか |
Methods
| メソッド | パラメータ | 説明 |
|---|---|---|
expandAll |
none | すべてのパネルを展開します(multiExpandableがtrueの場合のみ機能します) |
collapseAll |
none | すべてのパネルを折りたたみます |
AccordionTrigger
パネルの表示/非表示を切り替えるボタン要素に適用されるディレクティブです。
Inputs
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
id |
string |
auto | トリガーの一意の識別子 |
panelId |
string |
— | **必須。**関連付けられたパネルのpanelIdと一致する必要があります |
disabled |
boolean |
false |
このトリガーを無効にします |
expanded |
boolean |
false |
パネルが展開されているかどうか(双方向バインディングをサポート) |
シグナル
| プロパティ | 型 | 説明 |
|---|---|---|
active |
Signal<boolean> |
トリガーが現在フォーカスを持っているかどうか |
Methods
| メソッド | パラメータ | 説明 |
|---|---|---|
expand |
none | 関連付けられたパネルを展開します |
collapse |
none | 関連付けられたパネルを折りたたみます |
toggle |
none | パネルの展開状態を切り替えます |
AccordionPanel
折りたたみ可能なコンテンツを含む要素に適用されるディレクティブです。
Inputs
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
id |
string |
auto | パネルの一意の識別子 |
panelId |
string |
— | **必須。**関連付けられたトリガーのpanelIdと一致する必要があります |
preserveContent |
boolean |
true |
パネルが折りたたまれた後もコンテンツをDOMに保持するかどうか |
シグナル
| プロパティ | 型 | 説明 |
|---|---|---|
visible |
Signal<boolean> |
パネルが現在展開されているかどうか |
Methods
| メソッド | パラメータ | 説明 |
|---|---|---|
expand |
none | このパネルを展開します |
collapse |
none | このパネルを折りたたみます |
toggle |
none | 展開状態を切り替えます |
AccordionContent
遅延レンダリングを有効にするために、アコーディオンパネル内のng-templateに適用される構造ディレクティブです。
このディレクティブには、input、output、メソッドはありません。ng-template要素に適用してください:
<div ngAccordionPanel panelId="item-1"> <ng-template ngAccordionContent> <!-- Content here is lazily rendered --> </ng-template></div>