-
Notifications
You must be signed in to change notification settings - Fork 68
Feat ai driven a11y spec integration #5759
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dtopalov
wants to merge
4
commits into
develop
Choose a base branch
from
feat-ai-driven-a11y-spec-integration
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
ce3fb92
chore(ai): enhance ai instructions with a11y workflow
dtopalov 8927764
chore(infra): add a11y testing and CI
dtopalov 9dda362
feat(html): add accessibility attributes to html rendering
dtopalov 47580ab
chore: address PR review comments
dtopalov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| --- | ||
| agent: "agent" | ||
| description: "Apply ARIA accessibility attributes to an HTML package component" | ||
| --- | ||
|
|
||
| # Accessibility | ||
|
|
||
| Given a component name or `.spec.tsx` file, apply WAI-ARIA attributes to make it WCAG 2.2 Level AA compliant. | ||
|
|
||
| ## Steps | ||
|
|
||
| ### 1. Gather context | ||
|
|
||
| - Read `aria/[component]_aria.md` if it exists (reference documentation) | ||
| - Read `packages/html/src/[component]/[component].spec.tsx` and `templates/*.tsx` | ||
| - Look at similarly completed components (those with an `ariaSpec.rules` array) for reference patterns | ||
|
|
||
| ### 2. Build the `ariaSpec` rules | ||
|
|
||
| The `ariaSpec` static object on the spec component is the **single source of truth** for ARIA testing. It must include a `rules` array — each entry maps a CSS selector to an expected attribute. | ||
|
|
||
| If `aria/[component]_aria.md` exists, migrate its rule table into `ariaSpec.rules`. If no markdown spec exists, create rules based on: | ||
| - WAI-ARIA 1.2 Authoring Practices and WCAG 2.2 | ||
| - The component's rendered HTML structure and interactive behavior | ||
| - Specs from similar components (e.g. `combobox` ↔ `autocomplete`) | ||
|
|
||
| Flag and fix issues before applying (wrong selectors, contradictory roles like `role="alert"` + `aria-live="polite"`, missing states). | ||
|
|
||
| **Format:** | ||
|
|
||
| ```tsx | ||
| Component.ariaSpec = { | ||
| selector: '.k-component', | ||
| implicitRole: 'button', | ||
| rules: [ | ||
| { selector: '.k-component', attribute: 'role=button', usage: 'Required' }, | ||
| { selector: '.k-component', attribute: 'aria-label or aria-labelledby or title', usage: 'Required when icon-only' }, | ||
| { selector: '.k-component', attribute: 'aria-pressed', usage: 'When togglable' }, | ||
| { selector: '.k-component', attribute: 'disabled', usage: 'When disabled' }, | ||
| { selector: '.k-component .k-icon', attribute: 'aria-hidden=true', usage: 'Required' }, | ||
| ] | ||
| }; | ||
| ``` | ||
|
|
||
| Each rule: `{ selector, attribute, usage }` — same columns as the markdown tables. The test runner reads `ariaSpec.rules` directly; legacy fields (`requiredAttributes`, `childSelectors`) are still supported as fallback but should be migrated to `rules`. | ||
|
|
||
| **Composite components:** Complex components that embed other components (e.g. Grid contains Pager, Toolbar, ColumnMenu) must include the child component rules in their own `ariaSpec.rules`. The test validates the **full rendered HTML** from each template, so all nested selectors must be accounted for. Reference the child component's `ariaSpec.rules` and adapt selectors to the parent's DOM structure. Building blocks that don't have their own spec (internal abstractions) must still produce accessible HTML — ensure correct attributes flow via props from parent or are set directly in templates. | ||
|
|
||
| ### 3. Apply ARIA to TSX files | ||
|
|
||
| Edit `.spec.tsx` and `templates/*.tsx`. Rules: | ||
|
|
||
| - **Attributes after `className`** — always place ARIA props after the className prop | ||
| - **Semantic HTML first** — prefer `<button>` over `<div role="button">` | ||
| - **Conditional values** — `aria-pressed={selected ? 'true' : 'false'}`, use `undefined` to omit | ||
| - **Dynamic IDs** — ``aria-controls={`${id}-listbox`}``; IDs must reference real DOM elements | ||
| - **Icon-only buttons** — require `aria-label` | ||
| - **`disabled` propagation** — pass to all interactive children | ||
| - **No structural changes** — only add attributes; never add or remove HTML elements | ||
| - **`tests/` folder** — do not modify unless specifically needed for missing coverage | ||
|
|
||
| #### Patterns | ||
|
|
||
| **Attribute placement** — always after `className`: | ||
| ```tsx | ||
| // ❌ BAD | ||
| <button role="button" className="k-button"> | ||
|
|
||
| // ✅ GOOD | ||
| <button className="k-button" role="button" aria-label="Close"> | ||
| ``` | ||
|
|
||
| **Conditional / state-based attributes:** | ||
| ```tsx | ||
| <button | ||
| className="k-button" | ||
| aria-expanded={opened ? 'true' : 'false'} | ||
| aria-disabled={disabled ? 'true' : undefined} | ||
| {...(loading && { 'aria-busy': 'true' })} | ||
| /> | ||
| ``` | ||
|
|
||
| **Dynamic ID references:** | ||
| ```tsx | ||
| const { id = 'combobox' } = props; | ||
|
|
||
| <input | ||
| className="k-input-inner" | ||
| role="combobox" | ||
| aria-controls={opened ? `${id}-listbox` : undefined} | ||
| aria-activedescendant={opened ? `${id}-item-${focusedIndex}` : undefined} | ||
| /> | ||
| ``` | ||
|
|
||
| **Icon-only buttons:** | ||
| ```tsx | ||
| // ❌ BAD — no accessible name | ||
| <Button icon="close" /> | ||
|
|
||
| // ✅ GOOD | ||
| <Button icon="close" aria-label="Close dialog" /> | ||
| ``` | ||
|
|
||
| **Attribute-only changes — never add/remove elements:** | ||
| ```tsx | ||
| // ❌ BAD — adding wrapper element | ||
| <div role="region" aria-label="Actions"> | ||
| <Button>Save</Button> | ||
| </div> | ||
|
|
||
| // ✅ GOOD — attributes on existing element | ||
| <Button aria-label="Save document">Save</Button> | ||
| ``` | ||
|
|
||
| **Coverage gaps — add templates for untested states:** | ||
| ```tsx | ||
| // templates/combobox-disabled.tsx | ||
| export const ComboboxDisabled = (props) => ( | ||
| <Combobox disabled {...props} /> | ||
| ); | ||
| // then export from index.ts | ||
| ``` | ||
|
|
||
| Add **TSDoc** on props that affect ARIA: | ||
|
|
||
| ```tsx | ||
| export type KendoComponentProps = { | ||
| /** @aria aria-pressed="true" when selected */ | ||
| selected?: boolean; | ||
| /** @aria aria-label required for icon-only usage */ | ||
| icon?: string; | ||
| }; | ||
| ``` | ||
|
|
||
| ### 4. Validate iteratively | ||
|
|
||
| Run after every edit round: | ||
|
|
||
| ```bash | ||
| npm run build --prefix packages/html && \ | ||
| npm run test:a11y [component] | ||
| ``` | ||
|
|
||
| Fix violations and re-run until clean. Also run `npm run typecheck --prefix packages/html` to catch type errors. | ||
|
|
||
| ### Known acceptable violations | ||
|
|
||
| These are out of scope — note but don't try to fix: | ||
| - `label` — form labels provided by consuming apps | ||
| - `target-size` (2.5.8) — controlled by product implementations | ||
| - jQuery legacy specs — excluded from compliance testing | ||
|
|
||
| ### Must-fix violations | ||
|
|
||
| Always resolve these: | ||
| - `button-name` — buttons without accessible text | ||
| - `aria-valid-attr-value` — invalid ARIA attribute values | ||
| - `aria-required-attr` — missing required ARIA attributes | ||
| - Any other axe-core WCAG 2.2 Level AA violations |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| name: Test A11y Specs | ||
|
|
||
| on: | ||
| workflow_call: | ||
|
|
||
| concurrency: | ||
| group: test-a11y-specs-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
|
|
||
| run: | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
|
|
||
| - name: Checkout branch | ||
| uses: actions/checkout@v6 | ||
| with: | ||
| fetch-depth: 0 # Needed for affected detection | ||
|
|
||
| - name: Install virtual display on Linux | ||
| run: sudo apt-get install xvfb | ||
|
|
||
| - name: Setup node | ||
| id: setup-node | ||
| uses: actions/setup-node@v6 | ||
| with: | ||
| node-version: 24.x | ||
|
|
||
| - name: Use cache for root node_modules | ||
| id: cache-root-node_modules | ||
| uses: actions/cache@v5 | ||
| with: | ||
| path: node_modules | ||
| key: root-node_modules-${{ steps.setup-node.outputs.node-version }}-${{ hashFiles('package-lock.json') }} | ||
|
|
||
| - name: Install | ||
| if: steps.cache-root-node_modules.outputs.cache-hit != 'true' | ||
| run: | | ||
| npm ci --no-audit --no-fund | ||
|
|
||
| - name: Download artifacts | ||
| uses: actions/download-artifact@v7 | ||
| with: | ||
| path: .tmp | ||
|
|
||
| - name: Unpack artifacts | ||
| run: find .tmp -name "*.tar" -type f -exec tar -xf {} \; | ||
|
|
||
| - name: Build HTML package | ||
| run: npm run build --prefix packages/html | ||
|
|
||
| - name: Run unified A11y tests (affected components) | ||
| id: a11y-tests | ||
| continue-on-error: true | ||
| run: | | ||
| xvfb-run --auto-servernum --server-args="-screen 0, 1366x768x24" npm run test:a11y -- --affected --verbose 2>&1 | tee a11y-test-output.txt | ||
|
|
||
| - name: Report test results | ||
| run: | | ||
| echo "## A11y Spec Test Results" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
|
|
||
| if [ "${{ steps.a11y-tests.outcome }}" == "success" ]; then | ||
| echo ":white_check_mark: Unified A11y tests passed (ARIA + WCAG)" >> $GITHUB_STEP_SUMMARY | ||
| else | ||
| echo ":x: A11y tests failed" >> $GITHUB_STEP_SUMMARY | ||
| fi | ||
|
|
||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "_Tested affected components only for faster CI._" >> $GITHUB_STEP_SUMMARY | ||
|
|
||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "### Test Output" >> $GITHUB_STEP_SUMMARY | ||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo '```' >> $GITHUB_STEP_SUMMARY | ||
| cat a11y-test-output.txt >> $GITHUB_STEP_SUMMARY | ||
| echo '```' >> $GITHUB_STEP_SUMMARY | ||
|
|
||
| echo "" >> $GITHUB_STEP_SUMMARY | ||
| echo "_Note: This check is non-blocking and does not prevent PR merging._" >> $GITHUB_STEP_SUMMARY | ||
|
|
||
| - name: Check for failures | ||
| run: | | ||
| if [ "${{ steps.a11y-tests.outcome }}" == "failure" ]; then | ||
| echo "A11y spec tests failed. See summary above for details." | ||
| exit 1 | ||
| fi |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,4 @@ | ||
| npm run sass && npm run docs:check | ||
|
|
||
| # Run a11y tests on affected components (prompt to skip) | ||
| node scripts/test-a11y-unified.mjs --affected --prompt --build |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| ## WAI-ARIA | ||
|
|
||
| This section lists the selectors, attributes, and behavior patterns supported by the component. | ||
|
|
||
| ### ActionSheet Dialog Wrapper | ||
|
|
||
| | Selector | Attribute | Usage | | ||
| | -------- | --------- | ----- | | ||
| | `.k-actionsheet` | `role=dialog` | Announces the dialog role of the component. | | ||
| | | `aria-labelledby=.k-actionsheet-title id` | Associates the title of the action sheet. | | ||
| | | `aria-hidden=true/false` | Announces the hidden state of the ActionSheet container. | | ||
| | | `aria-modal=true` | Announces that the action sheet is modal. | | ||
| | `.k-actionsheet .k-actionsheet-title` | `id` | Used to associate the title with the action sheet wrapper element. | | ||
|
|
||
| ### Embedded Content | ||
|
|
||
| ActionSheet is a container component with arbitrary content. When components use ActionSheet in adaptive mode (e.g., dropdowns rendering lists), those components are responsible for ensuring their internal content has proper ARIA attributes. | ||
|
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.