diff --git a/packages/angular/cli/lib/examples/angular-aria/01-accordion.md b/packages/angular/cli/lib/examples/angular-aria/01-accordion.md new file mode 100644 index 000000000000..cf7c3fc94914 --- /dev/null +++ b/packages/angular/cli/lib/examples/angular-aria/01-accordion.md @@ -0,0 +1,199 @@ +--- +title: Accordion +summary: Demonstrates how to create an accessible accordion component using @angular/aria directives. +keywords: + - Accordion + - AccordionGroup + - AccordionTrigger + - AccordionPanel + - AccordionContent + - ngAccordionGroup + - ngAccordionTrigger + - ngAccordionPanel + - ngAccordionContent +required_packages: + - '@angular/aria/accordion' +related_concepts: + - 'Accessibility' + - 'A11y' + - 'UI Patterns' + - 'Aria' +experimental: true +--- + +## Purpose + +The `@angular/aria/accordion` accordion directives provide the foundational logic and accessibility features for creating accordion-style UI elements. Accordion are used to organize related content into expandable and collapsible sections with a trigger button and a content panel. + +## When to Use + +Use the Angular Aria accordion directives when you need to display a list of headers that can show or hide associated content panels. This pattern is ideal for FAQs, long-form content segmentation, or progressively disclosing information to reduce page scrolling. Avoid using accordions for general navigation menus, tabbed interfaces, or whenever multiple sections of content must be simultaneously visible. + +## Key Concepts + +- **AccordionGroup**: A directive that acts as the container for a set of accordion items. It manages the overall state and interactions of the accordion, such as keyboard navigation and expansion mode. +- **AccordionTrigger**: A directive that represents the trigger button for an accordion item. It controls the expansion state of an associated `ngAccordionPanel`. +- **AccordionPanel**: A directive that represents the content panel of an accordion item. It is controlled by an associated `ngAccordionTrigger`. +- **AccordionContent**: A structural directive that marks the `ng-template` to be used as the content for an `ngAccordionPanel`. This content can be lazily loaded. + +## Example Files + +This example demonstrates a basic, accessible accordion with two items that allows only one panel to be open at a time. + +### accordion-example.ts + +This file defines the accordion component, imports the necessary `@angular/aria/accordion` directives, and provides the data for the accordion items. + +```typescript +import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { + AccordionGroup, + AccordionTrigger, + AccordionPanel, + AccordionContent, +} from '@angular/aria/accordion'; + +@Component({ + selector: 'accordion-example', + templateUrl: 'accordion-example.html', + styleUrls: ['accordion-example.css'], + imports: [AccordionGroup, AccordionTrigger, AccordionPanel, AccordionContent], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AccordionExample {} +``` + +### accordion-example.html + +This template structures the accordion using the `ngAccordionGroup`, `ngAccordionTrigger`, `ngAccordionPanel`, and `ngAccordionContent` directives, binding the necessary ARIA attributes for accessibility. + +```html +

Accordion Examples

+ +
+

+ + Accordion Heading 1 + + +

+
+ +

Accordion Content Here

+
+
+ +

+ + Accordion Heading 2 + + +

+
+ +

More Accordion Content Here

+
+
+
+``` + +### accordion-example.css + +This file provides basic styling to make the component visually function like an accordion, including focus indicators, icons, and transitions. Note that there are no disabled styles. + +```css +@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); + +[ngAccordionGroup] { + width: 300px; +} + +[ngAccordionTrigger] { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + margin: 0; + padding: 12px 16px; +} + +h3 { + margin: 0; + position: relative; +} + +h3:focus-within::before, +h3:hover::before { + content: ''; + position: absolute; + height: 100%; + width: 2px; + background-color: blue; + top: 0; + left: 0; +} + +h3:not(:first-of-type) { + border-block-start: 1px solid lightgrey; +} + +p { + padding: 1rem 2rem; +} + +[ngAccordionTrigger] svg { + width: 24px; + height: 24px; + transition: transform 0.2s ease-in-out; + transform: rotate(90deg); + pointer-events: none; +} + +[ngAccordionTrigger][aria-expanded='true'] svg { + transform: rotate(-90deg); +} + +.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: black; + transition: 0.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); +} +``` + +## Usage Notes + +- The core link between the header and the content is established by ensuring the `[ngAccordionTrigger]` panelId and the `[ngAccordionPanel]` panelId are identical (e.g., item-id). +- The `[ngAccordionContent]` directive is applied to an `ng-template` to enable deferred rendering for performance optimization. +- Disable triggers using the `disabled` input. diff --git a/packages/angular/cli/lib/examples/angular-aria/02-combobox-autocomplete.md b/packages/angular/cli/lib/examples/angular-aria/02-combobox-autocomplete.md new file mode 100644 index 000000000000..918dff7c5c7e --- /dev/null +++ b/packages/angular/cli/lib/examples/angular-aria/02-combobox-autocomplete.md @@ -0,0 +1,246 @@ +--- +title: Combobox with Autocomplete +summary: Demonstrates how to create an accessible combobox with autocomplete functionality using @angular/aria directives. +keywords: + - Combobox + - Autocomplete + - Listbox + - Option + - ngCombobox + - ngComboboxInput + - ngListbox + - ngOption +required_packages: + - '@angular/aria/combobox' + - '@angular/cdk/overlay' + - '@angular/aria/listbox' +related_concepts: + - 'Accessibility' + - 'A11y' + - 'UI Patterns' + - 'Forms' + - 'Aria' +experimental: true +--- + +## Purpose + +The combobox with autocomplete assists with scrolling fatigue by allowing users to filter large selections instantly through text input. It utilizes `@angular/aria/combobox` and also `@angular/aria/listbox` to provide the necessary logic and accessibility features for a combobox with autocomplete. Ultimately, it transforms overwhelming lists into a fast, searchable interface that minimizes input errors. + +## When to Use + +Use the Angular Aria combobox directives to implement a text input that provides a list of suggested, filterable options—the common autocomplete pattern for search fields and typeaheads. This pattern is ideal when the option list is long (over 20 items), users know what they are looking for, and speed is a priority, as typing is faster than scrolling. Conversely, avoid using it for short lists (under 10 options) or when users need to browse unfamiliar options, where a standard dropdown or list provides better visibility. + +## Key Concepts + +- **Combobox**: The main directive that acts as a container for the entire combobox widget, coordinating the interactions between the input and the listbox. +- **ComboboxInput**: directive connects an input element to the combobox. +- **ComboboxPopupContainer**: directive wraps the popup content and manages its display. +- **Listbox**: directive wraps the popup content and manages its display. +- **Option**: directive marks an item within a listbox. +- **OverlayModule**: A directive from the Angular CDK used to display the listbox of options next to the combobox input. + +## Example Files + +This example demonstrates a combobox that filters a list of fruits based on user input. + +### combobox-autocomplete-example.ts + +This file defines the combobox component, imports the necessary `@angular/aria/combobox` directives, and contains the logic for filtering the list of options. + +```typescript +import { + afterRenderEffect, + signal, + Component, + computed, + viewChild, + viewChildren, +} from '@angular/core'; +import { OverlayModule } from '@angular/cdk/overlay'; +import { Combobox, ComboboxInput, ComboboxPopupContainer } from '@angular/aria/combobox'; +import { Listbox, Option } from '@angular/aria/listbox'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'combobox-autocomplete-example', + templateUrl: 'combobox-autocomplete-example.html', + styleUrls: ['combobox-autocomplete-example.css'], + imports: [ + Combobox, + ComboboxInput, + ComboboxPopupContainer, + Listbox, + Option, + OverlayModule, + FormsModule, + ], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ComboboxAutocompleteExample { + options = viewChildren>(Option); + combobox = viewChild>(Combobox); + query = signal(''); + + fruits = computed(() => + Fruits.filter((fruit) => fruit.toLowerCase().startsWith(this.query().toLowerCase())), + ); + + constructor() { + // Scrolls to the active item when the active option changes. + afterRenderEffect(() => { + if (this.combobox()?.expanded()) { + const option = this.options().find((opt) => opt.active()); + option?.element.scrollIntoView({ block: 'nearest' }); + } + }); + } +} +const Fruits = [ + 'Apple', + 'Avocado', + 'Banana', + 'Blueberry', + 'Cantaloupe', + 'Cherry', + 'Grapefruit', + 'Kiwi', + 'Lemon', + 'Mango', + 'Orange', + 'Peach', + 'Pear', + 'Pineapple', + 'Plum', + 'Strawberry', + 'Watermelon', +]; +``` + +### combobox-autocomplete-example.html + +This template structures the combobox, connecting the input field to the list of options and handling the dynamic filtering. + +```html +
+
+ search + +
+ + +
+ @if (fruits().length === 0) { +
No results found
+ } +
+ @for (fruit of fruits(); track fruit) { +
+ {{fruit}} + check +
+ } +
+
+
+
+
+``` + +### combobox-autocomplete-example.css + +This file provides basic styling for the combobox, including positioning the listbox and highlighting active options. + +```css +@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); + +.manual { + display: flex; + position: relative; + align-items: center; + width: fit-content; +} + +[ngCombobox]:has([aria-expanded='false']) .combobox-popup { + display: none; +} + +.combobox-popup { + width: 100%; + border: 1px solid #ccc; + border-radius: 4px; +} + +.material-symbols-outlined { + pointer-events: none; +} +.search-icon { + left: 0.75rem; + position: absolute; +} + +[ngComboboxInput] { + width: 13rem; + border-radius: 0.25rem; + padding: 0.75rem 0.5rem 0.75rem 2.5rem; +} + +[ngListbox] { + height: 100%; + display: flex; + overflow: auto; + max-height: 200px; + flex-direction: column; +} + +[ngOption] { + display: flex; + cursor: pointer; + align-items: center; + padding: 0 1rem; + margin: 1px; + gap: 2px; + min-height: 2.25rem; + border-radius: 0.5rem; +} + +[ngOption][data-active='true'], +[ngOption]:hover { + background: #eaeaea; +} + +[ngOption][data-active='true'] { + outline-offset: -2px; + outline: 2px solid grey; +} + +[ngOption][aria-selected='true'] { + background: #e8f0fe; +} + +[ngOption]:not([aria-selected='true']) .check-icon { + display: none; +} + +.option-label { + flex: 1; +} + +.no-results { + padding: 1rem; +} +``` + +## Usage Notes + +- The `[(ngModel)]` binding on the input directly updates the `query` signal, enabling two-way data binding with `FormsModule`. +- The `cdkConnectedOverlay` directive used to position the `ngListbox` directly beneath the `ngComboboxInput`. This is integrated to use the Popover API. +- This uses manual selection keeps the typed text unchanged while users navigate the suggestion list and selection occurs explicityly by enter or click. This can be changed to auto-select or highlight. diff --git a/packages/angular/cli/lib/examples/angular-aria/03-select.md b/packages/angular/cli/lib/examples/angular-aria/03-select.md new file mode 100644 index 000000000000..eea562a74218 --- /dev/null +++ b/packages/angular/cli/lib/examples/angular-aria/03-select.md @@ -0,0 +1,248 @@ +--- +title: Select +summary: Demonstrates how to create an accessible select-only combobox using @angular/aria directives, where the user must choose from a predefined list of options. +keywords: + - Select + - Readonly + - Combobox + - Listbox + - Option + - ngCombobox + - ngComboboxInput + - ngListbox + - ngOption +required_packages: + - '@angular/aria/combobox' + - '@angular/aria/listbox' + - '@angular/cdk/overlay' +related_concepts: + - 'Accessibility' + - 'A11y' + - 'UI Patterns' + - 'Forms' + - 'Aria' +experimental: true +--- + +## Purpose + +The single-selection dropdown pattern restricts user input to a predefined set of options, ensuring that only valid data is selected. It utilizes `@angular/aria/combobox` and also `@angular/aria/listbox` to provide the necessary logic and accessibility features for a single-select dropdown. + +## When to Use + +The Select pattern works best when users need to choose a single value from a familiar, fixed set of fewer than 20 options. It is ideal for standard form fields—such as category, state, or status selection—where users can quickly scan clear, distinct labels without the need for filtering or search. However, this pattern should be avoided if the list exceeds 20 items or requires text input whereas the autocomplete pattern provides the necessary filtering. Additionally, use the Multiselect pattern when multiple choices are required, or Radio Buttons for very small sets (2–3 options) to ensure immediate visibility of all choices. + +## Key Concepts + +- **Combobox**: The main directive that acts as a container for the entire combobox widget, coordinating the interactions between the input and the listbox. +- **ComboboxInput**: directive connects an input element to the combobox. +- **ComboboxPopupContainer**: directive wraps the popup content and manages its display. +- **Listbox**: directive wraps the popup content and manages its display. +- **Option**: directive marks an item within a listbox. +- **OverlayModule**: A directive from the Angular CDK used to display the listbox of options next to the combobox input. + +## Example Files + +This example demonstrates a select-only combobox for selecting a few items for a status. + +### select-only-example.ts + +This file defines the basic select component with selectable items of different task statuses. + +```typescript +import { Combobox, ComboboxInput, ComboboxPopupContainer } from '@angular/aria/combobox'; +import { Listbox, Option } from '@angular/aria/listbox'; +import { + afterRenderEffect, + ChangeDetectionStrategy, + Component, + computed, + viewChild, + viewChildren, +} from '@angular/core'; +import { OverlayModule } from '@angular/cdk/overlay'; + +@Component({ + selector: 'select-only-example', + templateUrl: 'select-only-example.html', + styleUrls: ['select-only-example.css'], + imports: [Combobox, ComboboxInput, ComboboxPopupContainer, Listbox, Option, OverlayModule], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class SelectExample { + listbox = viewChild>(Listbox); + options = viewChildren>(Option); + combobox = viewChild>(Combobox); + + items = ['To Do', 'In Progress', 'Blocked', 'In Review', 'Done', 'Obsolete']; + + displayValue = computed(() => { + const values = this.listbox()?.values() || []; + return values.length ? values[0] : 'Select an Option'; + }); + + constructor() { + afterRenderEffect(() => { + const option = this.options().find((opt) => opt.active()); + option?.element.scrollIntoView({ block: 'nearest' }); + }); + } +} +``` + +### select-only-example.html + +This template structures a `readonly` Combobox that functions as a selectable interface, revealing a list of items upon interaction. The readonly attribute on ngCombobox prevents text input while preserving keyboard navigation. It leverages the CDK Popup and Popover APIs to manage the overlay, ensuring robust accessibility and correct positioning. + +```html +
+
+
+ {{ displayValue() }} +
+ + arrow_drop_down +
+ + + +
+
+ @for (item of items; track item) { +
+ {{item}} + +
+ } +
+
+
+
+
+``` + +### select-only-example.css + +This file provides basic styling for the `select-only` combobox, listbox, and options. + +```css +.select { + position: relative; + display: flex; + align-items: center; + border: 1px solid black; + border-radius: 4px; + width: fit-content; +} + +.select:focus-within { + outline-offset: -2px; + outline: 2px solid grey; +} + +.arrow, +.select-value { + position: absolute; + pointer-events: none; +} + +.select-value { + display: flex; + gap: 1rem; + left: 1rem; + width: calc(100% - 4rem); +} + +.arrow { + font-size: 1.25rem; + opacity: 0.875; +} + +.arrow { + right: 1rem; + transition: transform 0.2s ease-in-out; +} + +[ngComboboxInput] { + cursor: pointer; + padding: 0.7rem 3rem; + opacity: 0; +} + +[ngComboboxInput][aria-expanded='true'] + .arrow { + transform: rotate(180deg); +} + +[ngCombobox]:has([aria-expanded='false']) .select-popup { + display: none; +} + +.select-popup { + width: 100%; + margin-top: 2px; + padding: 0.1rem; + max-height: 11rem; + border-radius: 4px; + background-color: #fff; + border: 1px solid black; +} + +[ngListbox] { + gap: 2px; + width: 100%; + height: 100%; + display: flex; + overflow: auto; + flex-direction: column; +} + +[ngOption] { + display: flex; + cursor: pointer; + align-items: center; + margin: 1px; + gap: 1rem; + padding: 0.7rem 1rem; + border-radius: 4px; +} + +[ngOption][aria-disabled='true'] { + cursor: default; + opacity: 0.5; + background-color: grey; +} + +[ngOption]:hover { + background: #eaeaea; +} + +[ngOption][data-active='true'] { + outline-offset: -1px; + outline: 1px solid grey; +} + +[ngOption][aria-selected='true'] { + background: #e8f0fe; +} + +[ngOption]:not([aria-selected='true']) .option-check { + display: none; +} + +.option-text { + flex: 1; +} + +.option-check { + font-size: 1rem; +} +``` + +## Usage Notes + +- The `readonly` attribute on `ngCombobox` is necessary to prevent text input while also keeping the keyboard navigation. Similar to native `select` element. +- Add the `disabled` attribute to `ngCombobox` to disable the entire select. +- The select pattern uses `ngListbox` for the list and `ngOption` for each selectable option. diff --git a/packages/angular/cli/lib/examples/angular-aria/04-menu-bar.md b/packages/angular/cli/lib/examples/angular-aria/04-menu-bar.md new file mode 100644 index 000000000000..c291bf3d078b --- /dev/null +++ b/packages/angular/cli/lib/examples/angular-aria/04-menu-bar.md @@ -0,0 +1,159 @@ +--- +title: Menu Bar +summary: Demonstrates how to create an accessible menu bar with nested menus using @angular/aria/menu directives. +keywords: + - Menu Bar + - Menu + - MenuItem + - ngMenuBar + - ngMenu + - ngMenuItem +required_packages: + - '@angular/aria/menu' + - '@angular/cdk/overlay' +related_concepts: + - 'Accessibility' + - 'A11y' + - 'Navigation' + - 'UI Patterns' + - 'Aria' +experimental: true +--- + +## Purpose + +The `@angular/aria/menu` module provides a set of directives for an accessible menu experiences. Menubars organize commands for application navigation or contextual actions, offering a structured way to present a list of commands or options to the user. + +## When to Use + +Menubars are effective for creating persistent, horizontal navigation that organizes application commands into logical, discoverable categories like "File" or "Edit" making them ideal for desktop-style interfaces. You should use them when you need a stable top-level structure that remains visible across the application, but you should avoid them for simple standalone action lists, context menus, or mobile interfaces where horizontal space is constrained; in those cases, standard menus, trigger-based dropdowns, or sidebar navigation patterns are more appropriate. + +## Key Concepts + +- **MenuBar**: A directive that applies the `menubar` ARIA role to its host element. It acts as a container for top-level menus. +- **Menu**: A directive that applies the `menu` ARIA role. It represents the actual dropdown menu content. +- **MenuItem**: A directive that applies the `menuitem` ARIA role. It represents an individual, actionable item within a menu. +- **OverlayModule**: A directive from the Angular CDK used to display a popup menu of menuitems. + +## Example Files + +This example demonstrates a simple menu bar with nested menus for file operations. + +### menu-bar-example.ts + +This file defines the menubar component example. + +```typescript +import { Component, ChangeDetectionStrategy, viewChild } from '@angular/core'; +import { OverlayModule } from '@angular/cdk/overlay'; +import { Menu, MenuBar, MenuItem, MenuContent } from '@angular/aria/menu'; + +@Component({ + selector: 'menu-bar-example', + templateUrl: 'menu-bar-example.html', + styleUrls: ['menu-bar-example.css'], + imports: [Menu, MenuBar, MenuItem, MenuContent, OverlayModule], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MenuBarExample { + fileMenu = viewChild>('fileMenu'); + editMenu = viewChild>('editMenu'); +} +``` + +### menu-bar-example.html + +This template structures the menu bar and its nested menus. It utilizes the `cdkConnectedOverlay` and popover API. + +```html +
+
File
+ +
+ +
New
+
Open
+
Exit
+
+
+
+ +
Edit
+ +
+ +
Cut
+
Copy
+
Paste
+
+
+
+
+``` + +### menu-bar-example.css + +This file provides basic styling for the menu bar and menus. + +```css +[ngMenuBar] { + display: flex; + cursor: pointer; + gap: 0.25rem; + padding: 0.25rem; + border-radius: 0.5rem; + width: fit-content; + background: white; + border: 1px solid lightgrey; +} + +[ngMenu] { + margin: 0; + width: 5rem; + padding: 0.25rem; + border-radius: 0.5rem; + border: 1px solid #ccc; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +[ngMenu][data-visible='false'] { + display: none; +} + +[ngMenuItem] { + cursor: pointer; + align-items: center; + gap: 0.5rem; + padding: 0.5rem; + font-size: 0.875rem; + outline: none; +} + +[ngMenuItem][data-active='true']:focus-within, +[ngMenuItem][data-active='true']:hover, +[ngMenuItem][data-active='true'][aria-expanded='true'] { + background: #eaeaea; +} + +[ngMenuItem]:hover, +[ngMenuItem]:focus { + outline: 2px solid #4285f4; + border-radius: 0.5rem; +} +``` + +## Usage Notes + +- The `ngMenuBar` directive is applied to the main container, establishing the ARIA `menubar` role. +- Control the direction for RTL support by setting `dir="rtl"` on `ngMenuBar`. +- Nested menus are created by adding submenus using the `[submenu]="subMenuRef()"` on a `ngMenuItem`. +- Add `disabled` to menu items in order to disable the menu items. +- Use the `cdkConnectedOverlay` to use the popover api and defer the content using `ngMenuContent`. diff --git a/packages/angular/cli/lib/examples/angular-aria/05-menu-trigger.md b/packages/angular/cli/lib/examples/angular-aria/05-menu-trigger.md new file mode 100644 index 000000000000..8141b870f54d --- /dev/null +++ b/packages/angular/cli/lib/examples/angular-aria/05-menu-trigger.md @@ -0,0 +1,131 @@ +--- +title: Menu Trigger +summary: Demonstrates how to use the ngMenuTrigger directive to create a standalone menu. +keywords: + - MenuTrigger + - Menu + - MenuItem + - ngMenu + - ngMenuItem + - ngMenuTrigger +required_packages: + - '@angular/aria/menu' +related_concepts: + - 'Accessibility' + - 'A11y' + - 'UI Patterns' + - 'Aria' +experimental: true +--- + +## Purpose + +The `ngMenuTrigger` directive is a key part of the `@angular/aria/menu` module, allowing you to create standalone menus. This is a common UI pattern for context menus, action menus, or any other menu that is opened by a button click. + +## When to Use + +Use the `ngMenuTrigger` directive when you need to create a dropdown menu by using the trigger button with a menu. This is ideal for situations where you have a single button that needs to open a list of options for application menus (File, Edit), toolbar dropdowns, or settings. Avoid this for navigation, data entry, or content organization; instead, utilize Navigation landmarks for site structure, the select combobox component for form select, and Tabs or Accordions for switching or collapsing content panels. + +## Key Concepts + +- **Menu**: A directive that applies the `menu` ARIA role. It represents the actual dropdown menu content. +- **MenuItem**: A directive that applies the `menuitem` ARIA role. It represents an individual, actionable item within a menu. +- **MenuTrigger**: A directive used to connect a trigger element (e.g., a button) to an `ng-template` that contains the `ngMenu` to be opened. +- **OverlayModule**: A directive from the Angular CDK used to display a popup menu of menuitems. + +## Example Files + +This example demonstrates a simple menu trigger that opens a menu of actions. + +### menu-trigger-example.ts + +This file defines the menu trigger component. + +```typescript +import { Component, ChangeDetectionStrategy, viewChild } from '@angular/core'; +import { OverlayModule } from '@angular/cdk/overlay'; +import { Menu, MenuItem, MenuTrigger, MenuContent } from '@angular/aria/menu'; + +@Component({ + selector: 'menu-trigger-example', + templateUrl: 'menu-trigger-example.html', + styleUrls: ['menu-trigger-example.css'], + imports: [Menu, MenuItem, MenuTrigger, MenuContent, OverlayModule], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class MenuTriggerExample { + optionMenu = viewChild>('optionMenu'); +} +``` + +### menu-trigger-example.html + +This template structures the menu trigger and its corresponding menu. + +```html + + +
+ +
Option 1
+
Option 2
+
Option 3
+
+
+
+``` + +### menu-trigger-example.css + +This file provides basic styling for the menu trigger. + +```css +[ngMenuTrigger] { + padding: 0.6rem 2rem; + border-radius: 0.5rem; + cursor: pointer; +} + +[ngMenu] { + margin: 0; + width: 10rem; + padding: 0.25rem; + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); +} + +[ngMenuItem] { + display: flex; + cursor: pointer; + align-items: center; + gap: 0.5rem; + padding: 0.5rem; + font-size: 0.875rem; + border-radius: 0.25rem; + outline: none; +} + +[ngMenuTrigger]:hover, +[ngMenuItem][data-active='true'] { + background: #eaeaea; +} + +[ngMenuItem]:focus { + outline: 2px solid #4285f4; +} +``` + +## Usage Notes + +- The `ngMenuTrigger` directive is applied to a `button` element, and its value is set to the `ng-template` that contains the `ngMenu`. `ngMenu` is a required part of the `ngMenuTrigger`. +- Add `disabled` to menu items in order to disable the menuitems. +- Add `wrap` whether you want the keyboard to wrap around at the edges. +- Nested menus are created by adding submenus using the `[submenu]="subMenuRef()"` on a `ngMenuItem`. +- Use the `cdkConnectedOverlay` to use the popover api and defer the content using `ngMenuContent`. diff --git a/packages/angular/cli/lib/examples/angular-aria/06-tabs.md b/packages/angular/cli/lib/examples/angular-aria/06-tabs.md new file mode 100644 index 000000000000..eaacc4c3003c --- /dev/null +++ b/packages/angular/cli/lib/examples/angular-aria/06-tabs.md @@ -0,0 +1,139 @@ +--- +title: Tabs +summary: Demonstrates how to create an accessible tabbed interface using @angular/aria/tabs directives. +keywords: + - Tabs + - TabList + - Tab + - TabPanel + - TabContent + - ngTabs + - ngTabList + - ngTab + - ngTabPanel + - ngTabContent +required_packages: + - '@angular/aria/tabs' +related_concepts: + - 'Accessibility' + - 'A11y' + - 'UI Patterns' + - 'Aria' +experimental: true +--- + +## Purpose + +The `@angular/aria/tabs` module provides a set of directives for building accessible tabbed interfaces. Tabs are a common UI pattern for organizing and navigating between different sections of content within a limited space. + +## When to Use + +Use the Angular Aria tabs directives when you need to present multiple sections of content in a single area, allowing users to switch between them. This is ideal for dashboards, settings panels, product details, or any scenario where content needs to be logically grouped and easily navigable without requiring a full page reload. Tabs should be avoided for sequential workflows like wizards, primary page navigation, single-section content, or scenarios requiring more than eight categories. + +## Key Concepts + +- **Tabs**: The main container directive for the entire tabbed interface. It orchestrates the interaction between the tab list and tab panels. +- **TabList**: A directive that applies the `tablist` ARIA role to its host element. It contains the individual `ngTab` elements. +- **Tab**: A directive that applies the `tab` ARIA role. It represents a single, selectable tab within the `ngTabList`. +- **TabPanel**: A directive that applies the `tabpanel` ARIA role. It acts as a container for the content associated with a specific `ngTab`. +- **TabContent**: A directive used within `ngTabPanel` to defer the rendering of the tab's content until the tab is activated. + +## Example Files + +This example demonstrates a basic tabbed interface with three tabs. + +### aria-tabs-example.ts + +This file defines the tabs component and manages the selected tab state. + +```typescript +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { Tab, Tabs, TabList, TabPanel, TabContent } from '@angular/aria/tabs'; + +@Component({ + selector: 'tabs-example', + templateUrl: 'tabs-example.html', + styleUrls: ['tabs-example.css'], + imports: [TabList, Tab, Tabs, TabPanel, TabContent], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class TabsExample {} +``` + +### tabs-example.html + +This template structures the tabs, tab list, and tab panels. + +```html +
+
    +
  • Tab 1
  • +
  • Tab 2
  • +
  • Tab 3
  • +
+ +
+ +

Content for Tab 1

+

This is the content for the first tab.

+
+
+
+ +

Content for Tab 2

+

This is the content for the second tab.

+
+
+
+ +

Content for Tab 3

+

This is the content for the third tab.

+
+
+
+``` + +### aria-tabs-example.css + +This file provides basic styling for the tabs. + +```css +[ngTabList] { + display: flex; + justify-content: space-between; +} + +[ngTab] { + cursor: pointer; + padding: 16px; + display: flex; + flex-grow: 1; + cursor: pointer; + align-items: center; + border: 1px solid black; + border-radius: 8px 8px 0 0; +} + +[ngTab][aria-selected='true'] { + border-bottom: none; + background: #eaeaea; +} + +[ngTabPanel][tabindex='0'] { + height: 200px; + background: #eaeaea; + padding: 16px; + border: 1px solid black; + border-top: none; +} +``` + +## Usage Notes + +- The `ngTabs` directive acts as the main container for the tabbed interface. +- The `ngTabList` contains the `ngTab` elements, and its `[(selectedTab)]` binding is used to manage the currently active tab. +- Each `ngTab` has a `value` input that corresponds to the `value` of its associated `ngTabPanel`. +- The `ngTabPanel` elements contain the content for each tab. The `ngTabContent` directive within `ngTabPanel` ensures that content is only rendered when its tab is active. +- Set the `[selectionMode]="'follow'"` on the tab list to enable selected tab following focus, where teh tab changes as you navigate. +- Change the `[orientation]="'vertical'"` on the tab list for vertical tabs. +- Use the `disabled` input on any tabs that you want disabled. diff --git a/packages/angular/cli/lib/examples/angular-aria/07-toolbar.md b/packages/angular/cli/lib/examples/angular-aria/07-toolbar.md new file mode 100644 index 000000000000..cd1cd799a8a1 --- /dev/null +++ b/packages/angular/cli/lib/examples/angular-aria/07-toolbar.md @@ -0,0 +1,214 @@ +--- +title: Toolbar +summary: Demonstrates how to create an accessible toolbar with widgets and widget groups using @angular/aria/toolbar directives. +keywords: + - Toolbar + - ToolbarWidget + - ToolbarWidgetGroup + - ngToolbar + - ngToolbarWidget + - ngToolbarWidgetGroup +required_packages: + - '@angular/aria/toolbar' +related_concepts: + - 'Accessibility' + - 'A11y' + - 'UI Patterns' + - 'Aria' +experimental: true +--- + +## Purpose + +The `@angular/aria/toolbar` module provides directives for building accessible toolbars. A toolbar is a container for a group of widgets (like buttons, toggles, radio groups) that provide quick access to common actions or controls. + +## When to Use + +Use the Angular Aria toolbar directives when you need to group related controls or actions in a visually distinct and accessible bar. This is common in applications with rich editing features, document viewers, or any interface where users frequently interact with a set of tools. It helps organize the UI and provides a consistent way for users to access functionality. Toolbars should be avoided for where a simple button group is sufficient, unrelated controls, or a complex nested hiearchies are needed. + +## Key Concepts + +- **Toolbar**: The main container directive for the toolbar. It applies the `toolbar` ARIA role and manages navigation between its widgets. +- **ToolbarWidget**: A directive applied to individual interactive elements (e.g., buttons, inputs) within the toolbar. It makes the element a navigable widget within the toolbar's focus management system. +- **ToolbarWidgetGroup**: A directive used to group related `ngToolbarWidget` elements. This is useful for complex widgets like radio groups or sets of related buttons that should be navigated as a single unit within the toolbar. + +## Example Files + +This example demonstrates a simple toolbar with action buttons and a grouped set of alignment options. + +### toolbar-example.ts + +This file defines the toolbar component. + +```typescript +import { Component, signal } from '@angular/core'; +import { + Toolbar as NgToolbar, + ToolbarWidget as NgToolbarWidget, + ToolbarWidgetGroup as NgToolbarWidgetGroup, +} from '@angular/aria/toolbar'; + +@Component({ + selector: 'toolbar-example', + templateUrl: 'toolbar-example.html', + styleUrls: ['toolbar-example.css'], + imports: [Toolbar, ToolbarWidget, ToolbarWidgetGroup], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ToolbarExample {} +``` + +### toolbar-example.html + +This template structures the toolbar with buttons and a widget group. + +```html +
+
+ + +
+ +
+ +
+ + + +
+ +
+ +
+ + + +
+
+``` + +### toolbar-example.css + +This file provides basic styling for the toolbar. + +```css +@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); + +:host { + border: 1px solid black; + border-radius: 0.5rem; + display: block; + width: fit-content; +} + +[ngToolbar] { + gap: 1.5rem; + padding: 0.5rem 1rem; + display: flex; +} + +.group { + gap: 0.5rem; + display: flex; +} + +.separator { + width: 1px; + background: black; +} + +button { + cursor: pointer; + opacity: 0.875; + font-size: 1.25rem; + border-radius: 0.5rem; + padding: 0.5rem; + background-color: transparent; + border: 1px solid transparent; +} + +button:focus { + outline: 2px solid #4285f4; +} + +button:focus, +button:hover, +button:active { + background: #e8f0fe; +} + +button[aria-pressed='true'], +button[aria-checked='true'] { + color: darkblue; + background: #e8f0fe; +} +``` + +## Usage Notes + +- The `ngToolbar` directive is applied to the main container, establishing the ARIA `toolbar` role. +- Control the direction for RTL support by setting `dir="rtl"` on `ngToolbar`. +- Individual action buttons are marked with `ngToolbarWidget`. `[value]` input is required. +- A group of related controls, such as alignment options, can be wrapped in an `ngToolbarWidgetGroup`. +- The `multi` input of `ngToolbarWidgetGroup` controls whether multiple widgets within teh group can be selected simultaneously. diff --git a/packages/angular/cli/lib/examples/angular-aria/08-tree-single-select.md b/packages/angular/cli/lib/examples/angular-aria/08-tree-single-select.md new file mode 100644 index 000000000000..62f369ad51c0 --- /dev/null +++ b/packages/angular/cli/lib/examples/angular-aria/08-tree-single-select.md @@ -0,0 +1,223 @@ +--- +title: Single-Select Tree +summary: Demonstrates how to create an accessible single-select tree component using @angular/aria/tree directives. +keywords: + - Tree + - TreeItem + - TreeItemGroup + - ngTree + - ngTreeItem + - ngTreeItemGroup + - Single selection +required_packages: + - '@angular/aria/tree' +related_concepts: + - 'Accessibility' + - 'A11y' + - 'Navigation' + - 'UI Patterns' + - 'Aria' +experimental: true +--- + +## Purpose + +The `@angular/aria/tree` module provides directives for building accessible tree view components. A tree view is a hierarchical list of data where items can expand to reveal children or collapse to hide them. This example focuses on a single-selection tree, where only one item can be active at a time. + +## When to Use + +Use the Angular Aria tree directives when you need to display hierarchical data where users need to select a single item from a nested list. This is ideal for file browsers, organizational charts, navigation menus with nested categories, or any scenario where a clear parent-child relationship exists between items and only one selection is allowed. + +## Key Concepts + +- **Tree**: The main container directive for the tree view. It applies the `tree` ARIA role and manages the overall state and navigation of the tree. +- **TreeItem**: A directive applied to individual items within the tree. It applies the `treeitem` ARIA role and handles selection, expansion, and focus for that item. +- **TreeItemGroup**: A directive applied to an `ng-template` that contains child `ngTreeItem` elements. It represents a collapsible group of items within the tree. + +## Example Files + +This example demonstrates a single-select tree for navigating a file system structure. + +### tree-single-select-example.ts + +This file defines the single-select tree component, provides a TreeNode type and example data, and manages the selected tree item. + +```typescript +import { Component, signal, ChangeDetectionStrategy } from '@angular/core'; +import { NgTemplateOutlet } from '@angular/common'; +import { Tree, TreeItem, TreeItemGroup } from '@angular/aria/tree'; + +export type TreeNode = { + name: string; + value: string; + children?: TreeNode[]; + disabled?: boolean; + expanded?: boolean; +}; + +const NODES: TreeNode[] = [ + { + name: 'Pitted Fruits', + value: 'pitted-group', + children: [ + { name: 'Peach', value: 'peach-item' }, + { name: 'Plum', value: 'plum-item' }, + ], + expanded: true, + }, + { + name: 'Grapes', + value: 'grape-group', + children: [ + { + name: 'Green Grapes', + value: 'green-grapes', + children: [ + { name: 'Thompson Seedless', value: 'thompson-item' }, + { name: 'Cotton Candy', value: 'cotton-candy-item', disabled: true }, + ], + }, + { + name: 'Red Grapes', + value: 'red-grapes', + children: [{ name: 'Crimson Seedless', value: 'crimson-item' }], + }, + ], + expanded: true, + }, + { name: 'Kiwi', value: 'kiwi-item' }, +]; + +@Component({ + selector: 'tree-single-select-example', + templateUrl: 'tree-single-select-example.html', + styleUrls: ['tree-single-select-example.css'], + imports: [Tree, TreeItem, TreeItemGroup, NgTemplateOutlet], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AriaTreeSingleSelectExample { + nodes: TreeNode[] = NODES; + + readonly selected = signal(['peach-item']); +} +``` + +### tree-single-select-example.html + +This template structures the tree with nested items and groups. + +```html +
    + +
+ + + @for (node of nodes; track node.value) { +
  • + + + {{ node.name }} + +
  • + + @if (node.children) { +
      + + + +
    + } } +
    +``` + +### tree-single-select-example.css + +This file provides basic styling for the single-select tree. + +```css +@import url('https://fonts.googleapis.com/icon?family=Material+Symbols+Outlined'); + +[ngTree] { + padding: 10px; + border: 1px solid black; + border-radius: 4px; + min-width: 24rem; +} + +[ngTreeItem] { + cursor: pointer; + list-style: none; + text-decoration: none; + display: flex; + align-items: center; + gap: 1rem; + padding: 0.3rem 1rem; +} + +[ngTreeItem]:hover, +[ngTreeItem]:focus { + background-color: #e8f0fe; +} + +[ngTreeItem]:focus { + outline: 1px solid black; +} + +[ngTreeItem][aria-selected='true'], +[ngTreeItem][aria-selected='true'] .expand-icon { + color: darkblue; +} + +[ngTreeItem] > .material-symbols-outlined { + margin: 0; + width: 24px; +} + +[ngTreeItem] > .material-symbols-outlined:first-child { + transition: transform 0.2s ease; +} + +[ngTreeItem][aria-expanded='true'] > .material-symbols-outlined:first-child { + transform: rotate(90deg); +} + +[ngTreeItem] > .material-symbols-outlined:last-child { + visibility: hidden; + margin-left: auto; +} + +[ngTreeItem][aria-current] > .material-symbols-outlined:last-child, +[ngTreeItem][aria-selected='true'] > .material-symbols-outlined:last-child { + visibility: visible; +} + +[ngTreeItem][aria-disabled='true'] { + opacity: 0.5; + cursor: not-allowed; +} +``` + +## Usage Notes + +- When the `ngTree` directive is applied, it establishes the ARIA `tree` role. The `[(value)]` binding manages the currently selected item(s). +- Each node in the tree is marked with `ngTreeItem`. The `[value]` input provides a unique identifier for the item, and `[parent]` connects it to its parent `ngTree` or `ngTreeItemGroup`. +- The `value` input is required for `ngTreeItem`. +- Nested lists of children are wrapped in an `ng-template` with the `ngTreeItemGroup` directive. The `[ownedBy]` input links the group to its parent `ngTreeItem`. diff --git a/packages/angular/cli/lib/examples/angular-aria/09-tree-navigation.md b/packages/angular/cli/lib/examples/angular-aria/09-tree-navigation.md new file mode 100644 index 000000000000..853acfe5b953 --- /dev/null +++ b/packages/angular/cli/lib/examples/angular-aria/09-tree-navigation.md @@ -0,0 +1,182 @@ +--- +title: Navigation Tree +summary: Demonstrates how to create an accessible navigation tree using @angular/aria/tree directives, suitable for file system navigation, folders and document hierarchies, and side navigation with nested sections. +keywords: + - Tree + - TreeItem + - TreeItemGroup + - Navigation + - ngTree + - ngTreeItem + - ngTreeItemGroup +required_packages: + - '@angular/aria/tree' +related_concepts: + - 'Accessibility' + - 'A11y' + - 'Navigation' + - 'UI Patterns' + - 'Aria' +experimental: true +--- + +## Purpose + +The `@angular/aria/tree` module can be configured to function as a navigation tree, which is a common pattern for website navigation, documentation tables of contents, or file system explorers. By using the `nav` input, the tree's semantics are adjusted to better suit navigation, including the use of `aria-current` to indicate the currently active page or section. + +## When to Use + +Use the Angular Aria tree directives when you have a hierarchical structure of links or sections that the user needs to navigate. This is ideal for sidebars in documentation sites, nested menus in an application, or any scenario where the tree's primary purpose is to guide the user to different content areas rather than just selecting data. + +## Key Concepts + +- **Tree**: The main container directive. When the `[nav]="true"` input is used, it adapts its behavior for navigation. +- **TreeItem**: Represents a single link or section in the navigation tree. It will automatically have `aria-current="page"` applied when its `value` matches the `ngTree`'s `value`, indicating the current page. +- **TreeItemGroup**: Used to group related `ngTreeItem` elements, creating collapsible sections in the navigation tree. + +## Example Files + +This example demonstrates a navigation tree for a simple documentation website. + +### tree-navigation-example.ts + +This file defines the tree navigation component and the navigation structure. + +```typescript +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { NgTemplateOutlet } from '@angular/common'; +import { Tree, TreeItem, TreeItemGroup } from '@angular/aria/tree'; + +interface NavNode { + name: string; + children?: NavNode[]; +} + +const DOC_STRUCTURE: NavNode[] = [ + { name: 'Introduction' }, + { name: 'Getting Started', children: [{ name: 'Installation' }, { name: 'First Component' }] }, + { + name: 'Core Concepts', + children: [ + { name: 'Templates' }, + { name: 'Dependency Injection' }, + { name: 'Lifecycle Hooks' }, + ], + }, + { name: 'Advanced Topics', children: [{ name: 'Animations' }, { name: 'Internationalization' }] }, +]; + +@Component({ + selector: 'tree-navigation-example', + templateUrl: 'tree-navigation-example.html', + styleUrls: ['tree-navigation-example.css'], + imports: [Tree, TreeItem, TreeItemGroup, NgTemplateOutlet], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AriaTreeNavigationExample { + readonly structure = DOC_STRUCTURE; + currentPage = signal('Installation'); +} +``` + +### tree-navigation-example.html + +This template structures the navigation tree and a simulated content area. + +```html +
    + +
    +

    {{ currentPage() }}

    +

    Content for the "{{ currentPage() }}" page goes here.

    +
    +
    +``` + +### tree-navigation-example.css + +This file provides styling for the navigation tree and layout. + +```css +.container { + display: flex; + gap: 20px; + font-family: sans-serif; +} + +nav { + width: 250px; + border-right: 1px solid #ccc; +} + +[ngTree] { + list-style: none; + padding: 0; + margin: 0; +} + +[ngTreeItem] { + padding: 8px 12px; + cursor: pointer; + display: flex; + align-items: center; + border-radius: 4px; +} + +[ngTreeItem][aria-current='page'] { + background-color: #d0e0ff; + font-weight: bold; +} + +[ngTreeItem]:not([aria-current='page']):hover { + background-color: #f0f0f0; +} + +[ngTreeItem][aria-expanded='true']::before { + content: '▼'; + margin-right: 5px; +} + +[ngTreeItem][aria-expanded='false']::before { + content: '►'; + margin-right: 5px; +} + +[ngTreeItem][aria-expanded='true'][aria-haspopup='false']::before, +[ngTreeItem][aria-expanded='false'][aria-haspopup='false']::before { + content: ''; +} + +[ngTreeItemGroupContent] { + list-style: none; + padding-left: 20px; + margin: 0; +} + +main { + flex-grow: 1; +} +``` + +## Usage Notes + +- The `[nav]="true"` input on the `ngTree` directive is essential for a navigation tree. It changes the tree's semantics to be more appropriate for navigation, including the use of `aria-current`. +- The `[(value)]` binding on `ngTree` tracks the currently selected page. When a `ngTreeItem`'s `value` matches the `ngTree`'s `value`, it is marked as the current page. +- The `aria-current="page"` attribute is automatically applied to the active `ngTreeItem`. You can use this attribute in your CSS to highlight the current page, as shown in the example. +- The `value` input is required for `ngTreeItem`.