Skip to content

Comments

Feat ai driven a11y spec integration#5759

Open
dtopalov wants to merge 4 commits intodevelopfrom
feat-ai-driven-a11y-spec-integration
Open

Feat ai driven a11y spec integration#5759
dtopalov wants to merge 4 commits intodevelopfrom
feat-ai-driven-a11y-spec-integration

Conversation

@dtopalov
Copy link
Contributor

@dtopalov dtopalov commented Feb 6, 2026

PR Summary: ARIA Accessibility Implementation

Developer Workflow

For each component, CoPilot (with the accessibility agent/prompt, supervised by developer) follows this process:

  1. Gather Context — Read aria/[component]_aria.md (if it exists) and similar completed components for reference patterns
  2. Build ariaSpec.rules + Apply ARIA — Update .spec.tsx and templates/*.tsx:
    • Add the ariaSpec static object with a rules array on the spec component (single source of truth for ARIA testing)
    • Apply semantic roles, state attributes, relationship attributes, focus management to templates
    • Use (when ...) conditional hints in attribute strings for state-dependent rules (e.g. aria-invalid=true (when invalid))
  3. Build + Testnpm run build --prefix packages/html && npm run test:a11y [component]
  4. Fix violations and re-run until 0 violations

Quick Validation Loop

npm run build --prefix packages/html && npm run test:a11y [component]

The unified test runner (test-a11y-unified.mjs) handles everything in a single pass:

  • Reads ariaSpec.rules directly from compiled HTML package exports (no regex parsing)
  • Renders templates via specToHtml + JSDOM for ARIA rule validation
  • Boots a browser page for axe-core WCAG 2.2 Level AA checks
  • Reports ARIA + WCAG results per template

Infrastructure Updates (Feb 2026)

Feature Details
Unified test runner npm run test:a11y [component] — single script, ARIA + WCAG in one pass
ariaSpec.rules on components Single source of truth — each spec exports a static ariaSpec object with a rules array
Package-based discovery Templates and specs discovered from htmlPackage export metadata (folderName, ariaSpec) — no filename-to-export-name overrides needed
Conditional rules (when ...) hints in attribute strings for state-dependent attributes — pass when state is not active
Implicit role detection Semantic HTML elements (<button>, <input>, etc.) correctly matched to their WAI-ARIA implicit roles
CI affected-only mode npm run test:a11y:affected — only changed components tested on PRs
Pre-push hook Prompts to run a11y tests on affected components before push

Test Commands

npm run test:a11y [component]           # Test specific component(s)
npm run test:a11y                       # Test all components with ariaSpec
npm run test:a11y:affected              # Test only git-changed components (CI default)
npm run test:a11y -- --verbose          # Detailed output with per-rule results
npm run test:contrast                   # Color contrast ratio checks

Key Patterns Established

  • Disabled elements: Remove tabindex (not focusable), add aria-disabled="true"
  • Readonly elements: Keep tabindex="0" (still focusable), add aria-readonly="true"
  • Icon-only buttons: Require aria-label for accessible name
  • Auxiliary buttons: tabindex="-1" + aria-label, no aria-hidden on focusable
  • Combobox pattern: role="combobox" + aria-controlsrole="listbox" with role="option" items
  • Dialog pattern: role="dialog" + aria-modal="true" + aria-labelledby referencing title
  • Container components: ActionSheet doesn't dictate embedded content semantics; consuming components manage their own ARIA
  • Conditional state attributes: Use (when ...) hints — e.g. aria-invalid=true (when invalid), label for or aria-label or aria-labelledby (when has accessible name)

Acceptable Exceptions (Documented)

  • label — Form labels provided by consuming applications
  • target-size (WCAG 2.5.8) — Touch target size controlled by product implementations

Progress: 22 Components Complete

Tier Components Status
Tier 1 (Primitives) icon, badge, skeleton, avatar, loader ✅ 100%
Tier 2 (Basic Interactive) button, togglebutton, checkbox, radio, switch, rating, slider, range-slider ✅ 100%
Tier 3 (Form Inputs) textbox, textarea, numerictextbox, maskedtextbox, dateinput, otp ✅ 100%
Tier 5 (Foundational) popup, list ✅ 100%
Tier 8 (Dropdowns) combobox ✅ 100%
Tier 11 (Dialogs) action-sheet ✅ 100%

All completed components pass ARIA spec validation and WCAG 2.2 Level AA compliance (excluding documented acceptable exceptions).

The main changes from the original description:

  • Workflow simplified from 8 steps to 3 — removed separate "Render Test Pages" (handled internally by test runner), "Type Check" (happens on CI/commit), "Add ariaSpec" (it's part of step 2, not a separate step), and commit (not AI concern)
  • Infrastructure table updated — reflects the actual current capabilities (package-based discovery, conditional rules, implicit roles, legacy fallback)
  • Added Test Commands section — clear reference for all available commands
  • Key Patterns — added the (when ...) conditional pattern
  • Removed render-test-pages.mjs from the validation loop — the unified runner now handles rendering internally---

The main changes from the original description:

  • Workflow simplified from 8 steps to 3 — removed separate "Render Test Pages" (handled internally by test runner), "Type Check" (happens on CI/commit), "Add ariaSpec" (it's part of step 2, not a separate step), and the commit step.
  • Added Test Commands section — clear reference for all available commands
  • Key Patterns — added the (when ...) conditional pattern
  • Removed render-test-pages.mjs from the validation loop — the unified runner now handles rendering internally

@dtopalov dtopalov requested a review from a team as a code owner February 6, 2026 12:12
@github-actions
Copy link

github-actions bot commented Feb 6, 2026

Packages Report

core default classic bootstrap material fluent utils html
Size 32.97 KB (0.0%) 849.69 KB (0.0%) 848.37 KB (0.0%) 863.50 KB (0.0%) 1047.35 KB (0.0%) 1290.83 KB (0.0%) 625.13 KB (0.0%) 56.25 MB (16.0%🔼)
Gzip Size 5.35 KB (0.0%) 107.12 KB (0.0%) 107.43 KB (0.0%) 108.81 KB (0.0%) 122.75 KB (0.0%) 145.39 KB (0.0%) 52.15 KB (0.0%) 11.66 MB (17.8%🔼)
Compile Time 0.8 s (0.0%) 2.6 s (0.0%) 2.7 s (0.0%) 2.7 s (0.0%) 2.9 s (0.0%) 3.1 s (0.0%) 2.2 s (0.0%) 4.6 s (0.0%)

@zhpenkov zhpenkov requested review from Copilot and removed request for a team February 13, 2026 09:11
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Integrates per-component ARIA specifications into the HTML package (specs + templates), and adds supporting automation for rendering and unified accessibility testing/reporting.

Changes:

  • Adds/updates ARIA attributes across multiple HTML components and introduces ariaSpec metadata objects on several specs.
  • Extends test tooling (render filtering + unified a11y scripts wiring) and CI/Husky hooks to validate accessibility.
  • Introduces many aria/*_aria.md spec documents to define expected accessibility behavior per component.

Reviewed changes

Copilot reviewed 251 out of 905 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/_output/a11y-unified-report.json Adds a unified a11y JSON report output artifact
scripts/render-test-pages.mjs Adds optional component filtering and “no matches” fast-fail
packages/html/src/textbox/textbox.spec.tsx Adds aria-label/disabled/aria-invalid wiring + ariaSpec
packages/html/src/textbox/templates/textbox-disabled.tsx Adds disabled Textbox template
packages/html/src/textbox/index.ts Exports new TextboxDisabled template
packages/html/src/textarea/textarea.spec.tsx Adds disabled/aria-invalid wiring + ariaSpec
packages/html/src/textarea/templates/textarea-disabled.tsx Adds disabled Textarea template
packages/html/src/textarea/index.ts Exports new TextareaDisabled template
packages/html/src/switch/templates/switch-readonly.tsx Adds readonly Switch template with accessible name
packages/html/src/switch/templates/switch-normal.tsx Updates Switch template to provide accessible name
packages/html/src/switch/templates/switch-disabled.tsx Adds disabled Switch template with accessible name
packages/html/src/switch/templates/switch-checked.tsx Updates checked Switch template to provide accessible name
packages/html/src/switch/index.ts Exports new Switch templates
packages/html/src/spinbutton/spinbutton.spec.tsx Adds disabled support + aria labels for spin buttons
packages/html/src/slider/templates/slider-vertical.tsx Adds aria-label + JSX cleanup
packages/html/src/slider/templates/slider-readonly.tsx Adds readonly Slider template with aria-label
packages/html/src/slider/templates/slider-range.tsx Adds aria-label + JSX cleanup
packages/html/src/slider/templates/slider-range-vertical.tsx Adds aria-label + JSX cleanup
packages/html/src/slider/templates/slider-normal.tsx Adds aria-label + JSX cleanup
packages/html/src/slider/templates/slider-gradient.tsx Adds aria-label, sets gradientType + JSX cleanup
packages/html/src/slider/templates/slider-gradient-vertical.tsx Adds aria-label, sets gradientType + JSX cleanup
packages/html/src/slider/templates/slider-disabled.tsx Adds disabled Slider template with aria-label
packages/html/src/slider/index.ts Exports new Slider templates
packages/html/src/skeleton/skeleton.spec.tsx Adds accessibility documentation for Skeleton
packages/html/src/rating/templates/rating-with-label.tsx Adds aria-label to Rating template
packages/html/src/rating/templates/rating-readonly.tsx Adds readonly Rating template with aria-label
packages/html/src/rating/templates/rating-normal.tsx Adds aria-label to Rating template
packages/html/src/rating/templates/rating-disabled.tsx Adds disabled Rating template with aria-label
packages/html/src/rating/index.ts Exports new Rating templates
packages/html/src/radio/templates/radio-normal.tsx Adds aria-label to RadioButton template
packages/html/src/radio/templates/radio-invalid.tsx Adds invalid RadioButton template with aria-label
packages/html/src/radio/templates/radio-group-normal.tsx Adds aria-labels to radio-group options
packages/html/src/radio/templates/radio-group-horizontal.tsx Adds aria-labels to radio-group options
packages/html/src/radio/radio.spec.tsx Adds disabled/aria-invalid wiring + ariaSpec
packages/html/src/radio/index.ts Exports new RadioButtonInvalid template
packages/html/src/popup/popup.spec.tsx Adds container role/label props + ariaSpec notes
packages/html/src/otp/templates/otp-normal.tsx Adds group labeling + per-digit aria-labels/disabled support
packages/html/src/otp/templates/otp-no-space.tsx Adds group labeling + per-digit aria-labels/disabled support
packages/html/src/otp/templates/otp-invalid.tsx Adds invalid OTP template with per-digit aria-labels
packages/html/src/otp/templates/otp-groups.tsx Adds group labeling + per-digit aria-labels/disabled support
packages/html/src/otp/templates/otp-groups-no-space.tsx Adds group labeling + per-digit aria-labels/disabled support
packages/html/src/otp/templates/otp-disabled.tsx Adds disabled OTP template with per-digit aria-labels
packages/html/src/otp/otp.spec.tsx Adds disabled state + role="group" + hidden input + ariaSpec
packages/html/src/otp/otp-input.tsx Adds aria-label prop support for OTP input
packages/html/src/otp/index.ts Exports new OTP templates
packages/html/src/numerictextbox/templates/numerictextbox-prefix.tsx Adds aria-label to prefix dropdown
packages/html/src/numerictextbox/templates/numerictextbox-disabled.tsx Adds disabled NumericTextbox template
packages/html/src/numerictextbox/numerictextbox.spec.tsx Adds spinbutton role/disabled/aria-invalid + ariaSpec
packages/html/src/numerictextbox/index.ts Exports new NumericTextboxDisabled template
packages/html/src/nodata/nodata.tsx Adds live prop to set aria-live for announcements
packages/html/src/maskedtextbox/templates/maskedtextbox-prefix.tsx Adds aria-label to prefix dropdown
packages/html/src/maskedtextbox/templates/maskedtextbox-invalid.tsx Adds invalid MaskedTextbox template
packages/html/src/maskedtextbox/templates/maskedtextbox-disabled.tsx Adds disabled MaskedTextbox template
packages/html/src/maskedtextbox/maskedtextbox.spec.tsx Adds disabled/aria-invalid wiring + ariaSpec
packages/html/src/maskedtextbox/index.ts Exports new MaskedTextbox templates
packages/html/src/loader/loader.spec.tsx Adds role/status labeling + ariaSpec
packages/html/src/loader/loader-container.spec.tsx Adds live-region semantics + ariaSpec
packages/html/src/list/templates/list-virtualization.tsx Adds listbox ID/label + deterministic option IDs
packages/html/src/list/templates/list-virtualization-grouping.tsx Adds grouped listbox semantics + deterministic IDs
packages/html/src/list/templates/list-normal.tsx Adds listbox ID/label + deterministic option IDs
packages/html/src/list/templates/list-grouping.tsx Adds grouped listbox semantics + deterministic IDs
packages/html/src/list/list.spec.tsx Makes NoData live + adds ariaSpec
packages/html/src/list/list-ul.tsx Adds role/id/label props + ariaSpec
packages/html/src/list/list-group-item.tsx Adds presentational role + hides decorative icon + ariaSpec
packages/html/src/list/list-content.tsx Adds listbox roles/ID/labeling behaviors + ariaSpec
packages/html/src/input/input-inner-textarea.tsx Adds disabled forwarding to textarea
packages/html/src/input/input-inner-input.tsx Adds disabled/readOnly/ARIA-related props forwarding
packages/html/src/icon/svg-icon.tsx Forces decorative icon aria-hidden; fixes “none” rendering
packages/html/src/icon/font-icon.tsx Forces decorative icon aria-hidden
packages/html/src/dropdownlist/dropdownlist.spec.tsx Adds aria-label to dropdown arrow button
packages/html/src/dateinput/templates/dateinput-disabled.tsx Adds disabled DateInput template
packages/html/src/dateinput/index.ts Exports new DateInputDisabled template
packages/html/src/dateinput/dateinput.spec.tsx Adds disabled/aria-invalid wiring + ariaSpec
packages/html/src/combobox/tests/combobox-opened.tsx Expands combobox test coverage scenarios displayed
packages/html/src/combobox/tests/combobox-adaptive.tsx Adds IDs for deterministic a11y relationships
packages/html/src/combobox/templates/combobox-with-icons.tsx Adds listbox IDs + option IDs (incl. icons)
packages/html/src/combobox/templates/combobox-popup.tsx Adds IDs + activeDescendant + listbox wiring
packages/html/src/combobox/templates/combobox-nodata.tsx Adds “no data” popup template with live announcements
packages/html/src/combobox/templates/combobox-invalid.tsx Adds invalid combobox template
packages/html/src/combobox/templates/combobox-grouping.tsx Adds grouped listbox semantics + deterministic IDs
packages/html/src/combobox/templates/combobox-grouping-modern-custom-value.tsx Adds grouped listbox semantics + deterministic IDs
packages/html/src/combobox/templates/combobox-disabled.tsx Adds disabled combobox template
packages/html/src/combobox/templates/combobox-custom-value.tsx Adds listbox IDs + option IDs
packages/html/src/combobox/templates/combobox-adaptive.tsx Adds adaptive combobox template with popup listbox wiring
packages/html/src/combobox/index.ts Exports new combobox templates
packages/html/src/checkbox/templates/checkbox-normal.tsx Adds aria-label to checkbox template
packages/html/src/checkbox/templates/checkbox-invalid.tsx Adds invalid checkbox template with aria-label
packages/html/src/checkbox/templates/checkbox-group-normal.tsx Adds aria-labels to checkbox group options
packages/html/src/checkbox/templates/checkbox-group-horizontal.tsx Adds aria-labels to checkbox group options
packages/html/src/checkbox/templates/checkbox-checked.tsx Adds aria-label to checked checkbox template
packages/html/src/checkbox/index.ts Exports new checkbox invalid template
packages/html/src/checkbox/checkbox.spec.tsx Adds disabled/aria-invalid wiring + ariaSpec
packages/html/src/button/templates/icon-button.tsx Adds aria-label to icon-only button template
packages/html/src/button/button.spec.tsx Adds togglable + aria-pressed + ariaSpec
packages/html/src/badge/badge.spec.tsx Adds accessibility documentation for Badge
packages/html/src/avatar/tests/avatar.tsx Adds alt text to avatar images in tests
packages/html/src/avatar/tests/avatar-solid.tsx Adds alt text to avatar images in tests
packages/html/src/avatar/tests/avatar-size-rounded.tsx Adds alt text to avatar images in tests
packages/html/src/avatar/tests/avatar-outline.tsx Adds alt text to avatar images in tests
packages/html/src/avatar/templates/avatar-image.tsx Forces nested img + alt handling for AvatarImage
packages/html/src/avatar/avatar.spec.tsx Adds accessibility documentation for Avatar
packages/html/src/action-sheet/tests/action-sheet.tsx Adds IDs + aria-labels for icon-only buttons
packages/html/src/action-sheet/tests/action-sheet-views.tsx Adds IDs + aria-labels for icon-only buttons
packages/html/src/action-sheet/tests/action-sheet-treeview.tsx Adds IDs + aria-label for close button
packages/html/src/action-sheet/tests/action-sheet-time-selector.tsx Adds IDs + aria-label for close button
packages/html/src/action-sheet/tests/action-sheet-position.tsx Adds IDs for deterministic labeling
packages/html/src/action-sheet/tests/action-sheet-list.tsx Adds IDs + aria-label for close button
packages/html/src/action-sheet/tests/action-sheet-date-time-selector.tsx Adds IDs + aria-label for close button
packages/html/src/action-sheet/tests/action-sheet-data-table.tsx Adds IDs + aria-label for close button
packages/html/src/action-sheet/tests/action-sheet-calendar.tsx Adds IDs + aria-label for close button
packages/html/src/action-sheet/tests/action-sheet-calendar-infinite.tsx Adds IDs + aria-label for close button
packages/html/src/action-sheet/tests/action-sheet-adaptive.tsx Adds IDs + aria-labels for icon-only buttons
packages/html/src/action-sheet/templates/action-sheet-normal.tsx Adds id prop passthrough defaulting for templates
packages/html/src/action-sheet/actionsheet-view.tsx Adds titleId plumbing for dialog labeling
packages/html/src/action-sheet/actionsheet-items.tsx Adds doc comment
packages/html/src/action-sheet/actionsheet-item.tsx Hides decorative icons from AT
packages/html/src/action-sheet/actionsheet-header.tsx Adds titleId support + labels filter/input controls
packages/html/src/action-sheet/action-sheet.spec.tsx Adds dialog roles/labeling + ariaSpec
package.json Adds unified a11y scripts
aria/action-sheet_aria.md Adds ARIA spec doc
aria/appbar_aria.md Adds ARIA spec doc
aria/avatar_aria.md Adds ARIA spec doc
aria/bottom-navigation_aria.md Adds ARIA spec doc
aria/breadcrumb_aria.md Adds ARIA spec doc
aria/button_aria.md Adds ARIA spec doc
aria/buttongroup_aria.md Adds ARIA spec doc
aria/captcha_aria.md Adds ARIA spec doc
aria/card_aria.md Adds ARIA spec doc
aria/carousel_aria.md Adds ARIA spec doc
aria/chart-wizard_aria.md Adds ARIA spec doc
aria/chart_aria.md Adds ARIA spec doc
aria/chat_aria.md Adds ARIA spec doc
aria/checkbox_aria.md Adds ARIA spec doc
aria/chip_aria.md Adds ARIA spec doc
aria/chiplist_aria.md Adds ARIA spec doc
aria/chunk-progressbar_aria.md Adds ARIA spec doc
aria/circular-progressbar_aria.md Adds ARIA spec doc
aria/colorgradient_aria.md Adds ARIA spec doc
aria/colorpalette_aria.md Adds ARIA spec doc
aria/colorpicker_aria.md Adds ARIA spec doc
aria/column-chooser_aria.md Adds ARIA spec doc
aria/column-menu_aria.md Adds ARIA spec doc
aria/context-menu_aria.md Adds ARIA spec doc
aria/dateinput_aria.md Adds ARIA spec doc
aria/datepicker_aria.md Adds ARIA spec doc
aria/daterangepicker_aria.md Adds ARIA spec doc
aria/datetimepicker_aria.md Adds ARIA spec doc
aria/dialog_aria.md Adds ARIA spec doc
aria/dockmanager_aria.md Adds ARIA spec doc
aria/drawer_aria.md Adds ARIA spec doc
aria/dropdownbutton_aria.md Adds ARIA spec doc
aria/dropdowntree_aria.md Adds ARIA spec doc
aria/editor_aria.md Adds ARIA spec doc
aria/expansionpanel_aria.md Adds ARIA spec doc
aria/fab_aria.md Adds ARIA spec doc
aria/file-box_aria.md Adds ARIA spec doc
aria/filemanager_aria.md Adds ARIA spec doc
aria/filter-menu_aria.md Adds ARIA spec doc
aria/filter_aria.md Adds ARIA spec doc
aria/flatcolorpicker_aria.md Adds ARIA spec doc
aria/floatingactionbutton_aria.md Adds ARIA spec doc
aria/form_aria.md Adds ARIA spec doc
aria/gantt_aria.md Adds ARIA spec doc
aria/icon_aria.md Adds ARIA spec doc
aria/imageeditor_aria.md Adds ARIA spec doc
aria/infinitecalendar_aria.md Adds ARIA spec doc
aria/input_aria.md Adds ARIA spec doc
aria/listbox_aria.md Adds ARIA spec doc
aria/listview_aria.md Adds ARIA spec doc
aria/maskedtextbox_aria.md Adds ARIA spec doc
aria/media-player_aria.md Adds ARIA spec doc
aria/menu-button_aria.md Adds ARIA spec doc
aria/menu_aria.md Adds ARIA spec doc
aria/multicolumncombobox_aria.md Adds ARIA spec doc
aria/multiselecttree_aria.md Adds ARIA spec doc
aria/multiviewcalendar_aria.md Adds ARIA spec doc
aria/notification_aria.md Adds ARIA spec doc
aria/numerictextbox_aria.md Adds ARIA spec doc
aria/onetimepassword_aria.md Adds ARIA spec doc
aria/orgchart_aria.md Adds ARIA spec doc
aria/otp_aria.md Adds ARIA spec doc
aria/pager_aria.md Adds ARIA spec doc
aria/panelbar_aria.md Adds ARIA spec doc
aria/pdfviewer_aria.md Adds ARIA spec doc
aria/popover_aria.md Adds ARIA spec doc
aria/progressbar_aria.md Adds ARIA spec doc
aria/prompt-box_aria.md Adds ARIA spec doc
aria/propertygrid_aria.md Adds ARIA spec doc
aria/radio_aria.md Adds ARIA spec doc
aria/radiobutton_aria.md Adds ARIA spec doc
aria/radiogroup_aria.md Adds ARIA spec doc
aria/range-slider_aria.md Adds ARIA spec doc
aria/rating_aria.md Adds ARIA spec doc
aria/sankey_aria.md Adds ARIA spec doc
aria/signature_aria.md Adds ARIA spec doc
aria/skeleton_aria.md Adds ARIA spec doc
aria/slider_aria.md Adds ARIA spec doc
aria/sortable_aria.md Adds ARIA spec doc
aria/speech-to-text-button_aria.md Adds ARIA spec doc
aria/splitbutton_aria.md Adds ARIA spec doc
aria/splitter_aria.md Adds ARIA spec doc
aria/stepper_aria.md Adds ARIA spec doc
aria/switch_aria.md Adds ARIA spec doc
aria/tabstrip_aria.md Adds ARIA spec doc
aria/taskboard_aria.md Adds ARIA spec doc
aria/textarea_aria.md Adds ARIA spec doc
aria/textbox_aria.md Adds ARIA spec doc
aria/tilelayout_aria.md Adds ARIA spec doc
aria/timedurationpicker_aria.md Adds ARIA spec doc
aria/timeline_aria.md Adds ARIA spec doc
aria/timepicker_aria.md Adds ARIA spec doc
aria/togglebutton_aria.md Adds ARIA spec doc
aria/toolbar_aria.md Adds ARIA spec doc
aria/tooltip_aria.md Adds ARIA spec doc
aria/treelist_aria.md Adds ARIA spec doc
aria/treeview_aria.md Adds ARIA spec doc
aria/upload_aria.md Adds ARIA spec doc
aria/validation-message_aria.md Adds ARIA spec doc
aria/validation-summary_aria.md Adds ARIA spec doc
aria/validation-tooltip_aria.md Adds ARIA spec doc
aria/window_aria.md Adds ARIA spec doc
aria/wizard_aria.md Adds ARIA spec doc
.husky/pre-push Adds a11y validation prompt on push
.husky/pre-commit Adds info-only a11y change reporting on commit
.github/workflows/ci.yml Adds non-blocking “A11y Specs” status reporting
.github/copilot-instructions.md Documents new ARIA + a11y automation workflow

| | `tabindex=0` | Removes the element from the page Tab sequence. Set when a tab is not selected so that only the selected tab is in the page Tab sequence. |
| `.k-step:not(.k-step-current) .k-step-link` | `tabindex=-1` | Removes the element from the page Tab sequence. Set when a tab is not selected so that only the selected tab is in the page Tab sequence. |
| `.k-wizard-step` | `role=tabpanel` | Specifies the role of the element. |
| | `aria-label` | Specifies a label tor the panel. As the Wizard represents a step-by-step process, that is usually the pager text (e.g. "Step 2 of 4") |
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected spelling of 'tor' to 'for'.

Suggested change
| | `aria-label` | Specifies a label tor the panel. As the Wizard represents a step-by-step process, that is usually the pager text (e.g. "Step 2 of 4") |
| | `aria-label` | Specifies a label for the panel. As the Wizard represents a step-by-step process, that is usually the pager text (e.g. "Step 2 of 4") |

Copilot uses AI. Check for mistakes.
@zhpenkov zhpenkov self-requested a review February 13, 2026 09:22
Comment on lines 213 to 217
a11y-specs:
name: A11y Specs
needs: [ compile-themes, render-test-pages ]
uses: ./.github/workflows/_test-a11y-specs.yml

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need that dependency on render-test-pages?

It adds another 2-3 minutes to the build.

Screenshot 2026-02-13 at 13 05 28

@zhpenkov
Copy link
Contributor

A general question about the workflow. Let's say we want to add a11y attributes to the Breadcrumb spec.
How do we do that, what is the proposed way / guidelines.

My guess would be to first select the proper Agent:
Screenshot 2026-02-13 at 13 33 40

And after that use the preselected prompt, at the current component file:
Screenshot 2026-02-13 at 13 33 53

Is this the right way?

Also, what do we expect to happen after the agent has completed the prompt?

I see many steps in the prompt file
Screenshot 2026-02-13 at 13 43 16

But it seems to stop after completing step 2, without proceeding with the others.

This could be a good thing; it allows the user to manage the changes more manually.
But what is the workflow's vision? Should it behave like that? Should it do more?

@@ -0,0 +1,150 @@
---
mode: "agent"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has been deprecated in favour of agent

@epetrow
Copy link
Contributor

epetrow commented Feb 13, 2026

Also there is significant duplication of instructions between the three files accessibility.agent.md, accessibility.propmp.md, html.instructions.md. Could we consolidate to a single source of truth and then refer to that where needed? That way we will have better maintenance (update once, correct everywhere), no accidental drift between files over time.

@zhpenkov
Copy link
Contributor

I am making a general summary comment for the newly added a11y scripts and some things we can take into consideration for improving them.

First of all, keep scripts simple and revalidate if everything in them is needed.
The simpler, the better in terms of maintenance.

There is code duplication. Maybe we should create a shared infrastructure? (server setup, browser lifecycle, file globbing, URL building) is duplicated across scripts and should be extracted to a common module.

There are multiple scripts regex-parse source files to extract data that's already available on the compiled htmlPackage exports (Component.ariaSpec, Component.folderName).

Those hardcoded lookup tables like WCAG_CRITERIA and EXPORT_NAME_OVERRIDES can be eliminated by deriving values from existing metadata or library output.

Some large functions, like validateAttribute() should be broken down using strategy patterns.

The spec resolution currently uses fallback chains (markdown → TSX), which should be consolidated to a single source of truth. This generates extra extract logic and makes debugging and maintenance difficult.

@zhpenkov
Copy link
Contributor

The PR is in the right direction! 💯
It can be polished in some areas. Here are some of my thoughts reviewing it.

  1. Developer Workflow

    I did not understand the clear vision and definition of what the developer workflow to add a11y specs is.

    E.g. "Developer uses a11y agent with accessibility prompt to add a11y specs. Validates the code by hand. Commits by hand. Automated a11y tests are run pre-commit and on CI."

    Based on this clear workflow we can remove potentially redundant information and steps in the instructions/prompts/agent, and we can predict what is needed for the tests and CI.

  2. Documentation + ariaSpec

    In some of the scripts we use the markdown area documentation and fallback to the ariaSpec metadata. I think we should have one source.Having two sources increases scripts logic and maintenance. Metadata would be more useful as we can directly consume it without regex parsing.

  3. Scripts

    There are some fragile cases with the scripts - regexes, word matchings, collections of mappings for naming, formatting, reporting logic. We should see which one is mandatory and try to simplify the scripts to prevent them resulting in increased maintenance, technical debt and debug difficulties.

@inikolova inikolova self-requested a review February 16, 2026 07:57
@zhpenkov zhpenkov requested review from Copilot and zhpenkov February 18, 2026 07:52
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 254 out of 914 changed files in this pull request and generated 2 comments.

@zhpenkov
Copy link
Contributor

zhpenkov commented Feb 18, 2026

I followed the workflow.

Opened Copilot and used the prompt directly as described.

It seemed to work as expected now:

  1. First, it implemented the ARIA a11y changes.
  2. Then ran the built / test / typecheck scripts.

It did not implement this rule, as aria-hidden=true was already defined in the Icon, which was a good catch.

{ selector: '.k-breadcrumb-delimiter-icon', attribute: 'aria-hidden=true', usage: 'Required. The delimiter icon should not be accessed through assistive technology.' },

.gitignore Outdated
.nx/workspace-data

# Generated test output
tests/_output/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We track and commit files in this directory

const args = process.argv.slice(2);
const componentFilters = args.filter(a => !a.startsWith('--'));
const verboseMode = args.includes('--verbose');
const affectedMode = args.includes('--affected');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I thought about the --affected mode.

Lets say we modify the <Icon> component to not have aria-hidden=true.

After that, this test is run, wether locally or at ci.

It will indicate that the Icon component did not meet the A11y requirements.
But many other components will also not meet the requirements as they reuse the Icon.

Is this expected, also can it result in some unwanted recursions in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, on one hand we would like fastests CI on push possible, but on the other hand we would also like to regularly ensure all components are still compliant, or if not - catch a11y issues as early as possible. There seem to be several options:

  • run all tests on CI on push
    • the obvious problem is that this would slow down the build and would be (in most cases) unnecessary, as minor changes in a given component will not affect most other unrelated components
    • the benefit would be that all components will be always tested, a dedicated action or other mechanic to test all affected components would not be needed
  • run affected only on CI
    • fast
    • will not catch issues in other affected components
    • will require another (possibly nightly) run that tests all components
  • Enhance script to treat as "affected" all templates/components where the truly "changed" is used - depending on the component this can have similar positives and negatives as the first option, but also seems to be a good trade-off and compromise for typical PRs that do not introduce changes to a component, used in almost all others.

We can discuss this further while migrating the remaining components, aiming to have a solution that seems to be working best in the long run and integrated it before/when the a11y migration is finished.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the Claude Opus 4.6 analysis (more or less matches expectations, gives an additional fourth option, which does not seem bad either, but recommends option C - the last one from my previous response):

Analysis: --affected mode and cascading failures

The problem: If <Icon> has aria-hidden="true" removed, only icon is "affected" by git diff. But Button, ComboBox, Slider, etc. all embed <Icon> — they'd silently break a11y without being tested.


Option A: Always test all components with ariaSpec on CI

How Drop --affected from CI entirely; always run the full suite
+ Maximum safety — any regression in a shared primitive is caught immediately
+ Simplest conceptually; no dependency analysis needed
Slower CI as component count grows (currently ~22 components, linear growth to ~80+)
Most runs will re-test 70+ unchanged components for nothing

Best if: test suite stays fast enough (<3-5 min for all), or CI time budget is generous.


Option B: Keep --affected on push + scheduled nightly full run

How --affected on PR push (fast); a separate nightly/periodic workflow runs all components
+ Fast CI on push — developers get quick feedback on their own changes
+ Nightly catches cascading failures within 24h
Cascading regressions can go unnoticed for up to a day; the broken-by commit isn't obvious
Requires a separate scheduled workflow (minor maintenance overhead)

Best if: CI speed is critical and the team can tolerate a ~24h window for indirect regressions.


Option C: Dependency-aware affected detection ⭐ recommended

How Enhance getChangedComponents() to resolve component imports — when icon changes, also mark button, slider, combobox, etc. as affected. This can be done statically by scanning import statements in each component's templates/spec for references to other component folders.
+ Fast CI for typical PRs (leaf components don't cascade)
+ Catches cascading failures immediately for shared primitives
+ No separate nightly workflow needed
Requires building/maintaining a dependency graph (import-scanning logic)
Primitive changes (icon, button) could still trigger near-full runs

Implementation sketch: After identifying changed folders from git diff, walk each component's templates/*.tsx for import ... from '../icon' (etc.) to build a reverse-dependency map. Any component that imports from a changed component gets added to the affected set.

Best if: you want the best trade-off between CI speed and coverage. The dependency graph is straightforward since all imports are explicit and local within packages/html/src/.


Option D: Hybrid — affected + deps on push, full on merge to develop

How Use Option C's dep-aware detection on PR pushes; run full suite as a merge-queue or post-merge check on develop
+ Maximum coverage before code lands on develop
+ PR CI stays fast for most changes
Most complex setup (two modes + dep graph)
Post-merge failures require follow-up commits

Best if: you want maximal safety without slowing every PR push.


Recommendation

Option C (dependency-aware affected detection) is the best balance. The HTML package has explicit imports between components (import { Icon } from '../icon'), so building a reverse-dep map is straightforward and low-maintenance. It solves the exact problem of shared primitive changes breaking downstream components silently, while keeping CI fast for leaf-component PRs.

If it turns out to be over-engineered for the current 22 components, Option A (just run everything) is the simplest fallback — the suite is likely fast enough at current scale.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option C seems like the best approach in terms of test coverage.

The dependency graph implementation should not be that large to maintain.
The only concern is that if a commonly used component is updated, it will trigger many other a11y tests. Which is the right way, but might slow our ci-s.

This whole topic is an open question.

@telerik/kendo-front-end What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be decided after most of the aria specs are implemented or during the implementation workflow.

@TeyaVes
Copy link
Contributor

TeyaVes commented Feb 20, 2026

I think we should just clean up the commits - fix up some of them and remove the visual previews commits

@dtopalov dtopalov force-pushed the feat-ai-driven-a11y-spec-integration branch from 2442e44 to 9dda362 Compare February 20, 2026 10:43
@dtopalov dtopalov requested a review from zhpenkov February 20, 2026 12:51
if (affected?.length) { console.log(`📋 Affected: ${affected.join(', ')}\n`); }

// --prompt: show affected components and ask before running (pre-push)
if (promptMode && affectedMode) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When running

node scripts/test-a11y-unified.mjs --prompt --build

it does not prompt

process.exit(0);
}
console.log(`📋 Affected components: ${affected.join(', ')}\n`);
const answer = await promptUser('Run accessibility tests before push? (y/n/s[kip]): ');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need skip ? "y/n" might be enought

@zhpenkov zhpenkov requested a review from Copilot February 20, 2026 13:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 253 out of 911 changed files in this pull request and generated 8 comments.

"test:html": "npm run test --prefix packages/html",
"test:render-test-pages": "node ./scripts/render-test-pages.mjs",
"test:a11y": "node ./scripts/test-a11y-unified.mjs",
"test:a11y:affected": "node ./scripts/test-a11y-unified.mjs -- --affected",
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test:a11y:affected script passes an extra standalone -- to the Node script (process.argv will include "--"), which is inconsistent with the documented usage and the pre-push hook (--affected without the extra separator). Update the script to pass --affected directly (or ensure the runner explicitly strips the standalone -- argument).

Suggested change
"test:a11y:affected": "node ./scripts/test-a11y-unified.mjs -- --affected",
"test:a11y:affected": "node ./scripts/test-a11y-unified.mjs --affected",

Copilot uses AI. Check for mistakes.
React.HTMLAttributes<HTMLDivElement>
) => {
const {
id,
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id is being repurposed to wire label/description relationships, which likely prevents the FormField wrapper from receiving the id attribute via {...other} (since id is removed from other). If callers previously relied on id on the wrapper for anchors/tests/styles, this is a breaking change. Consider either re-applying id={id} onto the wrapper element, or introducing a dedicated prop (e.g., editorId) for Label htmlFor / hint/error ids while leaving wrapper id behavior intact.

Copilot uses AI. Check for mistakes.
)} dir={dir}>
{label &&
<Label className="k-form-label" optional={optional} info={info ? "(field info)" : undefined}>
<Label className="k-form-label" htmlFor={id || undefined} optional={optional} info={info ? "(field info)" : undefined}>
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id is being repurposed to wire label/description relationships, which likely prevents the FormField wrapper from receiving the id attribute via {...other} (since id is removed from other). If callers previously relied on id on the wrapper for anchors/tests/styles, this is a breaking change. Consider either re-applying id={id} onto the wrapper element, or introducing a dedicated prop (e.g., editorId) for Label htmlFor / hint/error ids while leaving wrapper id behavior intact.

Copilot uses AI. Check for mistakes.
Comment on lines +65 to +66
{hint && <FormHint id={id ? `${id}-hint` : undefined}>{hint}</FormHint>}
{error && <FormError id={id ? `${id}-error` : undefined}>{error}</FormError>}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

id is being repurposed to wire label/description relationships, which likely prevents the FormField wrapper from receiving the id attribute via {...other} (since id is removed from other). If callers previously relied on id on the wrapper for anchors/tests/styles, this is a breaking change. Consider either re-applying id={id} onto the wrapper element, or introducing a dedicated prop (e.g., editorId) for Label htmlFor / hint/error ids while leaving wrapper id behavior intact.

Copilot uses AI. Check for mistakes.
...other
} = props;

const titleId = id ? `${id}-title` : undefined;
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aria-labelledby is set whenever id is provided, but it’s possible to render an ActionSheet without a header/title element carrying that ${id}-title id (e.g., custom template or no header). That would create a broken reference. Make aria-labelledby conditional on the title element actually being rendered (or ensure the title element with id={titleId} always exists when id is set).

Suggested change
const titleId = id ? `${id}-title` : undefined;
const titleId = id && !template && header ? `${id}-title` : undefined;

Copilot uses AI. Check for mistakes.
rules: [
{ selector: '.k-actionsheet', attribute: 'role=dialog', usage: 'Announces the dialog role of the component.' },
{ selector: '.k-actionsheet', attribute: 'aria-labelledby (when has title)', usage: 'Associates the title of the action sheet.' },
{ selector: '.k-actionsheet', attribute: 'aria-hidden=true/false', usage: 'Announces the hidden state of the ActionSheet container.' },
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ariaSpec currently includes an unconditional aria-hidden=true/false requirement, but the component implementation in this PR doesn’t set aria-hidden on .k-actionsheet. Either implement aria-hidden for the relevant hidden/visible state, or mark it as conditional (e.g., (when hidden)) so the rule aligns with actual output.

Suggested change
{ selector: '.k-actionsheet', attribute: 'aria-hidden=true/false', usage: 'Announces the hidden state of the ActionSheet container.' },

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,3 @@
import Textbox from "../textbox.spec";

export const TextboxDisabled = (props) => <Textbox {...props} disabled />;
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This template renders a disabled input without guaranteeing an accessible name (no visible <label> and no default aria-label). Since other interactive templates in this PR add a default aria-label to satisfy automated a11y checks, consider doing the same here (and in the other newly added disabled/invalid input templates) or wrapping the control with a Label in the template.

Suggested change
export const TextboxDisabled = (props) => <Textbox {...props} disabled />;
export const TextboxDisabled = ({ "aria-label": ariaLabel, ...props }) => (
<Textbox aria-label={ariaLabel ?? "Disabled textbox"} {...props} disabled />
);

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +55
if (files.length === 0) {
await browser.close();
server.close();
process.exit(1);
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When no matching pages are found, the script exits with code 1 but provides no error output. Add a clear console.error message indicating the glob pattern/component filter used and a hint on expected usage (e.g., how to pass a component name), so failures are diagnosable in CI and local runs.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants