From 733dae91c9a9058fb8a3ad5ff0ec2828c8c593d3 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Fri, 31 Oct 2025 14:18:54 -0600 Subject: [PATCH 1/3] docs: add examples for aria directives --- src/dev-app/BUILD.bazel | 2 + src/dev-app/aria-docs-examples/BUILD.bazel | 23 ++ .../aria-docs-examples/aria-docs-examples.css | 147 +++++++++++ .../aria-docs-examples.html | 242 ++++++++++++++++++ .../aria-docs-examples/aria-docs-examples.ts | 61 +++++ .../autocomplete/BUILD.bazel | 23 ++ .../autocomplete/autocomplete-docs-demo.css | 56 ++++ .../autocomplete/autocomplete-docs-demo.html | 23 ++ .../autocomplete/autocomplete-docs-demo.ts | 66 +++++ src/dev-app/dev-app/dev-app-layout.ts | 2 + src/dev-app/routes.ts | 13 + 11 files changed, 658 insertions(+) create mode 100644 src/dev-app/aria-docs-examples/BUILD.bazel create mode 100644 src/dev-app/aria-docs-examples/aria-docs-examples.css create mode 100644 src/dev-app/aria-docs-examples/aria-docs-examples.html create mode 100644 src/dev-app/aria-docs-examples/aria-docs-examples.ts create mode 100644 src/dev-app/aria-docs-examples/autocomplete/BUILD.bazel create mode 100644 src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.css create mode 100644 src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.html create mode 100644 src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.ts diff --git a/src/dev-app/BUILD.bazel b/src/dev-app/BUILD.bazel index 3b10fb1d0a62..9de187b1630b 100644 --- a/src/dev-app/BUILD.bazel +++ b/src/dev-app/BUILD.bazel @@ -27,6 +27,8 @@ ng_project( "//src/cdk/overlay", "//src/dev-app/aria-accordion", "//src/dev-app/aria-combobox", + "//src/dev-app/aria-docs-examples", + "//src/dev-app/aria-docs-examples/autocomplete:autocomplete-docs-demo", "//src/dev-app/aria-grid", "//src/dev-app/aria-listbox", "//src/dev-app/aria-menu", diff --git a/src/dev-app/aria-docs-examples/BUILD.bazel b/src/dev-app/aria-docs-examples/BUILD.bazel new file mode 100644 index 000000000000..be9226f0b225 --- /dev/null +++ b/src/dev-app/aria-docs-examples/BUILD.bazel @@ -0,0 +1,23 @@ +load("//tools:defaults.bzl", "ng_project") + +package(default_visibility = ["//visibility:public"]) + +ng_project( + name = "aria-docs-examples", + srcs = glob(["**/*.ts"]), + assets = [ + "aria-docs-examples.html", + "aria-docs-examples.css", + ], + deps = [ + "//:node_modules/@angular/core", + "//src/components-examples/aria/accordion", + "//src/components-examples/aria/combobox", + "//src/components-examples/aria/grid", + "//src/components-examples/aria/listbox", + "//src/components-examples/aria/menu", + "//src/components-examples/aria/tabs", + "//src/components-examples/aria/toolbar", + "//src/components-examples/aria/tree", + ], +) diff --git a/src/dev-app/aria-docs-examples/aria-docs-examples.css b/src/dev-app/aria-docs-examples/aria-docs-examples.css new file mode 100644 index 000000000000..3c984f1da14a --- /dev/null +++ b/src/dev-app/aria-docs-examples/aria-docs-examples.css @@ -0,0 +1,147 @@ +/** Accordion */ + +[ngAccordionTrigger] { + display: inline-flex; + background: inherit; + border: none; + align-items: center; + font-size: inherit; + height: 36px; +} + +[ngAccordionTrigger] svg { + width: 24px; + height: 24px; + transition: transform 0.2s ease-in-out; + transform: rotate(90deg); +} + +[ngAccordionTrigger][aria-expanded='true'] svg { + transform: rotate(-90deg); +} + +/** Grid */ + +[ngGrid] { + display: flex; + flex-direction: column; +} + +[ngGridRow] { + display: flex; + align-items: center; + height: 36px; +} + +[ngGridCell] { + padding: 8px; +} + +button[ngGridCellWidget] { + display: flex; + height: 36px; + width: 36px; + padding: 8px; +} + +/** Combobox */ + +[ngCombobox] .cdk-overlay-pane { + background: light-dark(white, black); + border: 1px solid; + padding: 8px; +} + +[ngCombobox] [ngOption][aria-selected='true'] { + font-weight: bold; +} + +[ngCombobox]:focus-within [ngOption][data-active='true'] { + border: 1px solid; +} + +/** Listbox */ + +[ngListbox] { + list-style-type: none; + padding: 0; +} + +[ngOption] { + display: flex; + align-items: center; + cursor: pointer; +} + +[ngListbox]:focus-within [ngOption][data-active='true'] { + font-weight: bold; +} + +[ngOption] .demo-selection-indicator { + font-size: 2rem; +} + +[ngOption][aria-selected='false'] .demo-selection-indicator::before { + content: '-'; +} + +[ngOption][aria-selected='true'] .demo-selection-indicator::before { + content: '✓'; +} + +/** Menu */ + +[ngMenuItem] { + display: flex; + align-items: center; + cursor: pointer; + height: 36px; +} + +/** Radio */ + +[ngRadioGroup] { + list-style-type: none; + padding: 0; +} + +[ngRadioButton] { + display: flex; + align-items: center; + height: 36px; + cursor: pointer; +} + +[ngRadioButton] .demo-selection-indicator { + padding-right: 8px; +} + +[ngRadioButton][aria-checked='true'] .demo-selection-indicator::before { + content: '◉'; +} + +[ngRadioButton][aria-checked='false'] .demo-selection-indicator::before { + content: '◯'; +} + +[ngTabList] { + list-style-type: none; + display: flex; + padding: 0; +} + +[ngToolbar] { + display: flex; + height: 36px; + align-items: center; +} + +[ngToolbar] [ngRadioGroup] { + display: flex; +} + +/* Tree */ + +[ngTreeItem][aria-expanded='false'] > [ngTreeItem] { + display: none; +} diff --git a/src/dev-app/aria-docs-examples/aria-docs-examples.html b/src/dev-app/aria-docs-examples/aria-docs-examples.html new file mode 100644 index 000000000000..f0070dbba80c --- /dev/null +++ b/src/dev-app/aria-docs-examples/aria-docs-examples.html @@ -0,0 +1,242 @@ +

Accordion

+ +
+

+ +

+
+ +

+ Angular Aria ensures your components start with an accessible-first + foundation. +

+
+
+ +

+ +

+
+ +

Bring your own styles and design, however you want.

+
+
+ +

+ +

+
+ +

Automatically stay up-to-date with the latest best practices.

+
+
+
+ +

Combobox

+ +
+ + + + +
+
+ Apple +
+
Banana
+
Orange
+
+
+
+
+ +

Grid

+ +
+
+
Yoshka's
+
+ +
+
+ +
+
No-name
+
+ +
+
+ +
+
Charleston
+
+ +
+
+
+ +

Listbox

+ + + +

Menu

+ +
+
Circles
+
Hangouts
+
Photos
+
Events
+
+ +

Radio

+ + + +

Tabs

+ +
+ + +
+ Smooth like butter +
+ +
+ Goodbye Holo - welcome Material +
+ +
+ Less pressing. More swiping +
+
+ +

Toolbar

+ +
+ + + + +
+ +

Tree

+ +
+
+ Animals +
+ + +
Dog
+
Cat
+
+ +
Broccoli
+
diff --git a/src/dev-app/aria-docs-examples/aria-docs-examples.ts b/src/dev-app/aria-docs-examples/aria-docs-examples.ts new file mode 100644 index 000000000000..bdd22f2aea8d --- /dev/null +++ b/src/dev-app/aria-docs-examples/aria-docs-examples.ts @@ -0,0 +1,61 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ChangeDetectionStrategy, signal, Component, ViewEncapsulation} from '@angular/core'; +import {OverlayModule} from '@angular/cdk/overlay'; +import { + AccordionGroup, + AccordionTrigger, + AccordionPanel, + AccordionContent, +} from '@angular/aria/accordion'; +import {Combobox, ComboboxInput, ComboboxPopupContainer} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; +import {Grid, GridRow, GridCell, GridCellWidget} from '@angular/aria/grid'; +import {Menu, MenuItem} from '@angular/aria/menu'; +import {Tab, Tabs, TabList, TabPanel, TabContent} from '@angular/aria/tabs'; +import {Toolbar, ToolbarWidget} from '@angular/aria/toolbar'; +import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree'; + +@Component({ + templateUrl: 'aria-docs-examples.html', + styleUrl: 'aria-docs-examples.css', + imports: [ + AccordionGroup, + AccordionTrigger, + AccordionPanel, + AccordionContent, + Combobox, + ComboboxInput, + ComboboxPopupContainer, + Listbox, + Option, + Grid, + GridRow, + GridCell, + GridCellWidget, + Menu, + MenuItem, + TabList, + Tab, + Tabs, + TabPanel, + TabContent, + Toolbar, + ToolbarWidget, + Tree, + TreeItem, + TreeItemGroup, + OverlayModule, + ], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AriaDocsExamples { + open = signal(false); +} diff --git a/src/dev-app/aria-docs-examples/autocomplete/BUILD.bazel b/src/dev-app/aria-docs-examples/autocomplete/BUILD.bazel new file mode 100644 index 000000000000..9b184e943680 --- /dev/null +++ b/src/dev-app/aria-docs-examples/autocomplete/BUILD.bazel @@ -0,0 +1,23 @@ +load("//tools:defaults.bzl", "ng_project") + +package(default_visibility = ["//visibility:public"]) + +ng_project( + name = "autocomplete-docs-demo", + srcs = glob(["**/*.ts"]), + assets = [ + "autocomplete-docs-demo.html", + "autocomplete-docs-demo.css", + ], + deps = [ + "//:node_modules/@angular/core", + "//src/components-examples/aria/accordion", + "//src/components-examples/aria/combobox", + "//src/components-examples/aria/grid", + "//src/components-examples/aria/listbox", + "//src/components-examples/aria/menu", + "//src/components-examples/aria/tabs", + "//src/components-examples/aria/toolbar", + "//src/components-examples/aria/tree", + ], +) diff --git a/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.css b/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.css new file mode 100644 index 000000000000..45fafe13bdff --- /dev/null +++ b/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.css @@ -0,0 +1,56 @@ +[ngComboboxInput] { + width: 300px; + padding: 8px; + border-width: 1px; + border-radius: 4px; + border-style: solid; +} + +[ngCombobox]:focus-within [ngComboboxInput] { + border-color: blue; +} + +[ngOption] { + cursor: pointer; + padding: 4px; + border-radius: 4px; + display: flex; + justify-content: space-between; +} + +[ngOption][aria-selected='true'] { + content: '✓'; + background: #ededff; + color: #5e5eff; +} + +[ngOption][aria-selected='true']::after { + content: '✓'; +} + +[ngOption][data-active='true'], +[ngOption]:hover { + background: #eaeaea; +} + +[ngOption]:active { + background: #e5e5e5; +} + +[popover] { + margin: 0; + padding: 0; + border: none; +} + +[ngListbox] { + overflow: auto; + max-height: 300px; + border: 1px solid; + width: 100%; + padding: 4px; + border-radius: 4px; + /* Would be nice to inherit background but + its set to none by the cdk-overlay-popover */ + background: white; +} diff --git a/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.html b/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.html new file mode 100644 index 000000000000..423b581caca4 --- /dev/null +++ b/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.html @@ -0,0 +1,23 @@ +
+ + + + +
+ @for (biomeOption of biomeOptions(); track biomeOption) { +
+ {{biomeOption.name}} +
+ } @empty { + No matching biomes. + } +
+
+
+
diff --git a/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.ts b/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.ts new file mode 100644 index 000000000000..f96f321a3712 --- /dev/null +++ b/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.ts @@ -0,0 +1,66 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import { + ChangeDetectionStrategy, + signal, + Component, + ViewEncapsulation, + viewChild, + afterRenderEffect, +} from '@angular/core'; +import {OverlayModule} from '@angular/cdk/overlay'; +import {Combobox, ComboboxPopupContainer, ComboboxInput} from '@angular/aria/combobox'; +import {Listbox, Option} from '@angular/aria/listbox'; + +const biomes = [ + {name: 'Aquatic', id: 'aqu'}, + {name: 'Forest', id: 'for'}, + {name: 'Grassland', id: 'gra'}, + {name: 'Desert', id: 'des'}, + {name: 'Tunda', id: 'tun'}, +]; + +@Component({ + templateUrl: 'autocomplete-docs-demo.html', + styleUrl: 'autocomplete-docs-demo.css', + imports: [Combobox, ComboboxInput, ComboboxPopupContainer, Listbox, Option, OverlayModule], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class AutocompleteDocsDemo { + listbox = viewChild>(Listbox); + combobox = viewChild>(Combobox); + + initialValue = biomes[2]; + biomeOptions = signal(biomes); + currentSelection = signal([this.initialValue.id]); + + filterBiomes(inputValue: string) { + this.biomeOptions.set( + biomes.filter(b => b.name.toLowerCase().includes(inputValue.toLowerCase())), + ); + } + + resetOptions() { + this.biomeOptions.set(biomes); + } + + constructor() { + afterRenderEffect(() => { + if (this.combobox()?.expanded()) { + this.listbox()?.scrollActiveItemIntoView(); + } + }); + afterRenderEffect(() => { + if (this.combobox()?.expanded()) { + this.resetOptions(); + } + }); + } +} diff --git a/src/dev-app/dev-app/dev-app-layout.ts b/src/dev-app/dev-app/dev-app-layout.ts index a4db2af3a141..cc852302904c 100644 --- a/src/dev-app/dev-app/dev-app-layout.ts +++ b/src/dev-app/dev-app/dev-app-layout.ts @@ -62,6 +62,8 @@ export class DevAppLayout { navItems = [ {name: 'Examples', route: '/examples'}, {name: 'CDK Dialog', route: '/cdk-dialog'}, + {name: 'Aria Docs Autocomplete', route: '/aria-docs-autocomplete'}, + {name: 'Aria Docs Examples', route: '/aria-docs-examples'}, {name: 'Aria Accordion', route: '/aria-accordion'}, {name: 'Aria Combobox', route: '/aria-combobox'}, {name: 'Aria Grid', route: '/aria-grid'}, diff --git a/src/dev-app/routes.ts b/src/dev-app/routes.ts index e823b79b68a3..6c04adc9821f 100644 --- a/src/dev-app/routes.ts +++ b/src/dev-app/routes.ts @@ -7,6 +7,7 @@ */ import {Routes} from '@angular/router'; +import {AutocompleteDocsDemo} from './aria-docs-examples/autocomplete/autocomplete-docs-demo'; import {DevApp404} from './dev-app/dev-app-404'; import {DevAppHome} from './dev-app/dev-app-home'; @@ -44,6 +45,18 @@ export const DEV_APP_ROUTES: Routes = [ path: 'aria-combobox', loadComponent: () => import('./aria-combobox/combobox-demo').then(m => m.ComboboxDemo), }, + { + path: 'aria-docs-examples', + loadComponent: () => + import('./aria-docs-examples/aria-docs-examples').then(m => m.AriaDocsExamples), + }, + { + path: 'aria-docs-autocomplete', + loadComponent: () => + import('./aria-docs-examples/autocomplete/autocomplete-docs-demo').then( + m => m.AutocompleteDocsDemo, + ), + }, { path: 'aria-grid', loadComponent: () => import('./aria-grid/grid-demo').then(m => m.GridDemo), From fb7d2a7a1461be076e760df6ccb10c84263384fe Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Mon, 3 Nov 2025 05:31:28 -0700 Subject: [PATCH 2/3] refactor: redo combobox and remove view encapsulation --- .../aria-docs-examples/aria-docs-examples.html | 13 ++++--------- .../aria-docs-examples/aria-docs-examples.ts | 1 - .../autocomplete/autocomplete-docs-demo.html | 2 -- .../autocomplete/autocomplete-docs-demo.ts | 3 +-- 4 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/dev-app/aria-docs-examples/aria-docs-examples.html b/src/dev-app/aria-docs-examples/aria-docs-examples.html index f0070dbba80c..07e340600990 100644 --- a/src/dev-app/aria-docs-examples/aria-docs-examples.html +++ b/src/dev-app/aria-docs-examples/aria-docs-examples.html @@ -58,17 +58,13 @@

Combobox

-
+
+ #inputElement/> - + [cdkConnectedOverlay]="{origin: inputElement, usePopover: true, matchWidth: true}" + [cdkConnectedOverlayOpen]="combobox.expanded()">
Apple @@ -76,7 +72,6 @@

Combobox

Banana
Orange
-
diff --git a/src/dev-app/aria-docs-examples/aria-docs-examples.ts b/src/dev-app/aria-docs-examples/aria-docs-examples.ts index bdd22f2aea8d..f6bf54e6dfa9 100644 --- a/src/dev-app/aria-docs-examples/aria-docs-examples.ts +++ b/src/dev-app/aria-docs-examples/aria-docs-examples.ts @@ -32,7 +32,6 @@ import {Tree, TreeItem, TreeItemGroup} from '@angular/aria/tree'; AccordionContent, Combobox, ComboboxInput, - ComboboxPopupContainer, Listbox, Option, Grid, diff --git a/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.html b/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.html index 423b581caca4..8c1b124edd7b 100644 --- a/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.html +++ b/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.html @@ -8,7 +8,6 @@ -
@for (biomeOption of biomeOptions(); track biomeOption) {
@@ -18,6 +17,5 @@ No matching biomes. }
-
diff --git a/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.ts b/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.ts index f96f321a3712..4f703a78c7a1 100644 --- a/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.ts +++ b/src/dev-app/aria-docs-examples/autocomplete/autocomplete-docs-demo.ts @@ -29,8 +29,7 @@ const biomes = [ @Component({ templateUrl: 'autocomplete-docs-demo.html', styleUrl: 'autocomplete-docs-demo.css', - imports: [Combobox, ComboboxInput, ComboboxPopupContainer, Listbox, Option, OverlayModule], - encapsulation: ViewEncapsulation.None, + imports: [Combobox, ComboboxInput, Listbox, Option, OverlayModule], changeDetection: ChangeDetectionStrategy.OnPush, }) export class AutocompleteDocsDemo { From c698dc1d023d51f2473691b0281a5d989f7b7b02 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Mon, 3 Nov 2025 05:32:04 -0700 Subject: [PATCH 3/3] refactor: remove hash to combobox input --- src/dev-app/aria-docs-examples/aria-docs-examples.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dev-app/aria-docs-examples/aria-docs-examples.html b/src/dev-app/aria-docs-examples/aria-docs-examples.html index 07e340600990..59a6f405c1ca 100644 --- a/src/dev-app/aria-docs-examples/aria-docs-examples.html +++ b/src/dev-app/aria-docs-examples/aria-docs-examples.html @@ -59,8 +59,7 @@

Combobox

- +