Date: July 2025
Standard: WCAG 2.1 Level AA
Scope: web/src/ frontend React/TypeScript codebase
Related Issue: #4072 — CNCF Incubation Readiness Tracker
This audit evaluates the KubeStellar Console frontend against WCAG 2.1 AA criteria. The application already demonstrates several strong accessibility practices — semantic landmarks, focus traps in modals, live regions for dynamic content, and a skip-to-content link. The findings below identify areas that still need remediation.
| Area | Status |
|---|---|
Document language (lang) |
✅ Pass |
| Viewport meta tag | ✅ Pass |
| Skip-to-content link | ✅ Pass |
Landmark regions (<main>, <nav>, <aside>) |
✅ Pass |
| Modal focus traps | ✅ Pass |
| Live regions for toasts / loading | ✅ Pass |
| Keyboard navigation (menus, dropdowns) | ✅ Pass |
| Icon-only button labels | ❌ Needs work |
| Clickable non-interactive elements | ❌ Needs work |
Table semantics (scope, <caption>) |
❌ Needs work |
| Color contrast (hardcoded values) | |
| Heading hierarchy |
Issues that block keyboard-only or screen-reader users from completing tasks.
WCAG: 4.1.2 Name, Role, Value Status: ✅ Fixed in this PR
| File | Lines |
|---|---|
web/src/lib/unified/card/visualizations/ListVisualization.tsx |
200–215 |
web/src/lib/unified/card/visualizations/TableVisualization.tsx |
212–227 |
Pagination buttons contain only <ChevronLeft /> / <ChevronRight /> icons with no
accessible name. Screen readers announce them as unlabelled buttons.
Remediation: Added aria-label="Previous page" / aria-label="Next page".
WCAG: 2.1.1 Keyboard Status: ✅ Fixed in this PR
| File | Line |
|---|---|
web/src/components/compliance/RiskRegisterDashboard.tsx |
243 |
<tr> elements with onClick handlers lack role, tabIndex, and onKeyDown,
making them unreachable via keyboard.
Remediation: Added tabIndex={0}, role="row" (existing semantic), and
onKeyDown handler for Enter/Space activation.
WCAG: 2.1.1 Keyboard Status: ✅ Fixed in this PR
| File | Line |
|---|---|
web/src/components/ui/StatsOverview.tsx |
232–240 |
When isClickable is true, the <div> receives onClick but no role="button",
tabIndex={0}, or onKeyDown handler.
Remediation: Added conditional keyboard attributes when the stat block is clickable.
WCAG: 2.1.1 Keyboard Status: ✅ Fixed in this PR
| File | Line |
|---|---|
web/src/lib/unified/card/visualizations/ListVisualization.tsx |
293–299 |
Row <div> elements with conditional onClick lack keyboard support.
Remediation: Added conditional role="button", tabIndex={0}, and onKeyDown.
Issues that significantly degrade the experience for assistive technology users.
WCAG: 1.3.1 Info and Relationships
All <th> elements across the codebase (50+ tables) are missing scope="col" or
scope="row". Screen readers cannot determine header–cell relationships.
Files affected (sample):
web/src/components/charts/DataTable.tsxweb/src/components/cards/ClusterComparison.tsxweb/src/components/compliance/RiskRegisterDashboard.tsxweb/src/components/compliance/RiskMatrixDashboard.tsxweb/src/components/compliance/SIEMDashboard.tsx
Recommended fix: Add scope="col" to column headers, scope="row" to row headers.
WCAG: 1.3.1 Info and Relationships
Zero <caption> elements found. Tables lack programmatic descriptions, forcing
screen-reader users to infer purpose from surrounding context.
Recommended fix: Add <caption className="sr-only"> with a concise table
description, or use aria-label / aria-labelledby on the <table> element.
M-3 Inline SVG icons missing aria-hidden
WCAG: 4.1.2 Name, Role, Value
Decorative SVG elements in web/src/components/events/Events.tsx (lines 73–84) and
elsewhere lack aria-hidden="true", causing screen readers to announce meaningless
path data.
Recommended fix: Add aria-hidden="true" to all decorative SVGs, or
role="img" with aria-label to meaningful ones.
WCAG: 2.4.4 Link Purpose
Links containing only icons or colored dots (no visible text) are missing aria-label:
| File | Line |
|---|---|
web/src/components/cards/pipelines/NightlyReleasePulse.tsx |
121, 138, 252, 380 |
web/src/components/cards/ACMMRecommendations.tsx |
198 |
web/src/components/cards/llmd/NightlyE2EStatus.tsx |
290, 706 |
Recommended fix: Add aria-label describing the link destination/action.
WCAG: 1.3.1 Info and Relationships, 4.1.2 Name, Role, Value
| File | Line | Element |
|---|---|---|
web/src/lib/unified/card/visualizations/ListVisualization.tsx |
132 | Sort <select> |
web/src/lib/cards/CardComponents.tsx |
246 | Search <input> |
Placeholder text alone is not a substitute for a <label> or aria-label.
Recommended fix: Add aria-label to each input, or associate a visible/sr-only
<label>.
WCAG: 2.1.1 Keyboard
| File | Line |
|---|---|
web/src/components/cards/drasi/DrasiResultsTable.tsx |
161 |
Same pattern as C-2 — <tr onClick> without tabIndex or onKeyDown.
Recommended fix: Add tabIndex={0} and onKeyDown handler for Enter/Space.
Issues that reduce usability but do not fully block task completion.
WCAG: 1.3.1 Info and Relationships
Several pages jump heading levels (e.g., <h3> without a preceding <h2>, <h4>
without <h3>):
| File | Lines | Issue |
|---|---|---|
web/src/pages/FromHeadlamp.tsx |
308, 353 | <h3> / <h4> without parent levels |
web/src/components/compliance/AirGapDashboard.tsx |
227, 257, 288 | Orphaned <h3> |
web/src/components/alerts/AlertRuleEditor.tsx |
344, 568, 762 | <h4> without <h3> |
Recommended fix: Restructure headings to maintain sequential order, or use
aria-level when visual design requires a different size.
WCAG: 1.4.3 Contrast (Minimum)
web/src/lib/widgets/codeGenerator.ts uses inline hex colors (#9ca3af, #64748b,
#4b5563) that may fail the 4.5:1 contrast ratio against dark backgrounds.
Recommended fix: Replace with design tokens or Tailwind classes that respect the theme system.
WCAG: 1.4.3 Contrast (Minimum)
212+ files apply disabled:opacity-50 to elements using text-muted-foreground. The
compounded opacity reduction can push contrast well below 4.5:1.
Recommended fix: Use explicit disabled-state colors (e.g., disabled:text-gray-500)
instead of opacity reduction.
WCAG: 1.4.1 Use of Color
Status indicators in web/src/lib/widgets/codeGenerator.ts (line 231) use red/green
coloring as the sole differentiator between failing/passing states.
Recommended fix: Add text labels, icons, or patterns alongside color to convey status.
WCAG: 1.1.1 Non-text Content
web/src/components/dashboard/cardFactoryTemplatesT2.ts (line 343) uses alt="Card image"
instead of a descriptive alternative.
Recommended fix: Use context-specific alt text describing the image content.
WCAG: 4.1.2 Name, Role, Value
web/src/components/ui/AlertBadge.tsx (line 271) sets aria-modal={isMobile}.
Dialogs should always declare aria-modal="true" when they have a backdrop.
Recommended fix: Set aria-modal="true" unconditionally when the component
renders as a dialog with an overlay.
These patterns should be preserved and extended to new components:
- Skip-to-content link —
Layout.tsx(line 387–392) - Semantic landmarks —
<main id="main-content">,<nav>,<aside>in Layout and Sidebar - Modal focus traps —
useModalFocusTraphook inlib/modals/useModalNavigation.ts - Keyboard navigation — Arrow keys in menus, dropdowns, and modal tabs
- Live regions —
role="status"andaria-liveon Toast, RefreshIndicator, CardWrapper - Focus indicators —
focus-visible:ringused consistently (256 instances) as replacement for outline - Accessible status component —
components/ui/AccessibleStatus.tsxwithrole="status" - Conditional keyboard attributes —
CardComponents.tsx(line 510) properly addsrole,tabIndex,onKeyDownwhen elements are clickable
| Priority | Items | Effort |
|---|---|---|
| P0 — Ship blocker | C-1 through C-4 (fixed in this PR) | Done |
| P1 — Next sprint | M-1, M-2 (table scope/caption), M-3 (SVG aria-hidden), M-5 (form labels) | ~2 days |
| P2 — Backlog | M-4, M-6, m-1 through m-6 | ~3 days |
| P3 — Ongoing | Automated axe-core / Playwright a11y checks in CI | ~1 day setup |
Add an automated accessibility check to the CI pipeline:
npx playwright test --grep "accessibility"
# or
npx @axe-core/cli http://localhost:5174 --tags wcag2a,wcag2aaThis prevents new WCAG regressions from landing in main.