Conversation
Packages Report
|
There was a problem hiding this comment.
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
ariaSpecmetadata objects on several specs. - Extends test tooling (render filtering + unified a11y scripts wiring) and CI/Husky hooks to validate accessibility.
- Introduces many
aria/*_aria.mdspec 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 |
aria/wizard_aria.md
Outdated
| | | `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") | |
There was a problem hiding this comment.
Corrected spelling of 'tor' to 'for'.
| | | `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") | |
| a11y-specs: | ||
| name: A11y Specs | ||
| needs: [ compile-themes, render-test-pages ] | ||
| uses: ./.github/workflows/_test-a11y-specs.yml | ||
|
|
| @@ -0,0 +1,150 @@ | |||
| --- | |||
| mode: "agent" | |||
There was a problem hiding this comment.
This has been deprecated in favour of agent
|
Also there is significant duplication of instructions between the three files |
|
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. 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. |
|
The PR is in the right direction! 💯
|
|
I followed the workflow. Opened Copilot and used the prompt directly as described. It seemed to work as expected now:
It did not implement this rule, as { 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/ |
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
This might be decided after most of the aria specs are implemented or during the implementation workflow.
|
I think we should just clean up the commits - fix up some of them and remove the visual previews commits |
2442e44 to
9dda362
Compare
| if (affected?.length) { console.log(`📋 Affected: ${affected.join(', ')}\n`); } | ||
|
|
||
| // --prompt: show affected components and ask before running (pre-push) | ||
| if (promptMode && affectedMode) { |
There was a problem hiding this comment.
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]): '); |
There was a problem hiding this comment.
Do we need skip ? "y/n" might be enought
| "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", |
There was a problem hiding this comment.
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).
| "test:a11y:affected": "node ./scripts/test-a11y-unified.mjs -- --affected", | |
| "test:a11y:affected": "node ./scripts/test-a11y-unified.mjs --affected", |
| React.HTMLAttributes<HTMLDivElement> | ||
| ) => { | ||
| const { | ||
| id, |
There was a problem hiding this comment.
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.
| )} 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}> |
There was a problem hiding this comment.
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.
| {hint && <FormHint id={id ? `${id}-hint` : undefined}>{hint}</FormHint>} | ||
| {error && <FormError id={id ? `${id}-error` : undefined}>{error}</FormError>} |
There was a problem hiding this comment.
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.
| ...other | ||
| } = props; | ||
|
|
||
| const titleId = id ? `${id}-title` : undefined; |
There was a problem hiding this comment.
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).
| const titleId = id ? `${id}-title` : undefined; | |
| const titleId = id && !template && header ? `${id}-title` : undefined; |
| 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.' }, |
There was a problem hiding this comment.
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.
| { selector: '.k-actionsheet', attribute: 'aria-hidden=true/false', usage: 'Announces the hidden state of the ActionSheet container.' }, |
| @@ -0,0 +1,3 @@ | |||
| import Textbox from "../textbox.spec"; | |||
|
|
|||
| export const TextboxDisabled = (props) => <Textbox {...props} disabled />; | |||
There was a problem hiding this comment.
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.
| export const TextboxDisabled = (props) => <Textbox {...props} disabled />; | |
| export const TextboxDisabled = ({ "aria-label": ariaLabel, ...props }) => ( | |
| <Textbox aria-label={ariaLabel ?? "Disabled textbox"} {...props} disabled /> | |
| ); |
| if (files.length === 0) { | ||
| await browser.close(); | ||
| server.close(); | ||
| process.exit(1); | ||
| } |
There was a problem hiding this comment.
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.




PR Summary: ARIA Accessibility Implementation
Developer Workflow
For each component, CoPilot (with the accessibility agent/prompt, supervised by developer) follows this process:
aria/[component]_aria.md(if it exists) and similar completed components for reference patternsariaSpec.rules+ Apply ARIA — Update.spec.tsxandtemplates/*.tsx:ariaSpecstatic object with arulesarray on the spec component (single source of truth for ARIA testing)(when ...)conditional hints in attribute strings for state-dependent rules (e.g.aria-invalid=true (when invalid))npm run build --prefix packages/html && npm run test:a11y [component]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:
ariaSpec.rulesdirectly from compiled HTML package exports (no regex parsing)specToHtml+ JSDOM for ARIA rule validationInfrastructure Updates (Feb 2026)
npm run test:a11y [component]— single script, ARIA + WCAG in one passariaSpec.ruleson componentsariaSpecobject with arulesarrayhtmlPackageexport metadata (folderName,ariaSpec) — no filename-to-export-name overrides needed(when ...)hints in attribute strings for state-dependent attributes — pass when state is not active<button>,<input>, etc.) correctly matched to their WAI-ARIA implicit rolesnpm run test:a11y:affected— only changed components tested on PRsTest Commands
Key Patterns Established
tabindex(not focusable), addaria-disabled="true"tabindex="0"(still focusable), addaria-readonly="true"aria-labelfor accessible nametabindex="-1"+aria-label, noaria-hiddenon focusablerole="combobox"+aria-controls→role="listbox"withrole="option"itemsrole="dialog"+aria-modal="true"+aria-labelledbyreferencing title(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 applicationstarget-size(WCAG 2.5.8) — Touch target size controlled by product implementationsProgress: 22 Components Complete
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:
(when ...)conditional patternrender-test-pages.mjsfrom the validation loop — the unified runner now handles rendering internally---The main changes from the original description:
(when ...)conditional patternrender-test-pages.mjsfrom the validation loop — the unified runner now handles rendering internally