Skip to content

Commit e80f923

Browse files
committed
fix(a11y): announce workspace status and progress steps to screen readers
WCAG 4.1.3 (Status Messages) requires dynamic status changes to be announced by assistive technologies without requiring user focus. WorkspaceStatusIndicator: add a visually-hidden role="status" live region inside the indicator span. When the workspace status prop changes (e.g. Stopped → Starting), the live region text updates and screen readers announce the change without the user having to Tab to the element. ProgressStepTitle: add a persistent aria-live="polite" live region that is populated only when a step is active (distance === 0). As each step in the workspace creation or starting wizard becomes active, its name is announced automatically. The "Step: " prefix keeps the live region text distinct from the visible step title, preventing ambiguous DOM query matches. Also restore aria-label="Restore from backup" on the BackupsView Toolbar restore button to satisfy the existing accessibility test expectation. Assisted-by: Claude Sonnet 4.6 Signed-off-by: Oleksii Orel <oorel@redhat.com>
1 parent 64db60a commit e80f923

5 files changed

Lines changed: 186 additions & 19 deletions

File tree

packages/dashboard-frontend/src/components/Workspace/Status/Indicator/__tests__/__snapshots__/Indicator.spec.tsx.snap

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ exports[`Workspace indicator component Che Workspaces should render ERROR status
4444
</svg>
4545
</span>
4646
</span>
47+
<span
48+
aria-atomic="true"
49+
className="pf-v6-screen-reader"
50+
role="status"
51+
>
52+
Workspace status is ERROR
53+
</span>
4754
</span>
4855
</div>
4956
</div>
@@ -93,6 +100,13 @@ exports[`Workspace indicator component Che Workspaces should render RUNNING stat
93100
</svg>
94101
</span>
95102
</span>
103+
<span
104+
aria-atomic="true"
105+
className="pf-v6-screen-reader"
106+
role="status"
107+
>
108+
Workspace status is RUNNING
109+
</span>
96110
</span>
97111
</div>
98112
</div>
@@ -143,6 +157,13 @@ exports[`Workspace indicator component Che Workspaces should render STARTING sta
143157
</svg>
144158
</span>
145159
</span>
160+
<span
161+
aria-atomic="true"
162+
className="pf-v6-screen-reader"
163+
role="status"
164+
>
165+
Workspace status is STARTING
166+
</span>
146167
</span>
147168
</div>
148169
</div>
@@ -203,6 +224,13 @@ exports[`Workspace indicator component Che Workspaces should render STOPPED stat
203224
</svg>
204225
</span>
205226
</span>
227+
<span
228+
aria-atomic="true"
229+
className="pf-v6-screen-reader"
230+
role="status"
231+
>
232+
Workspace status is STOPPED
233+
</span>
206234
</span>
207235
</div>
208236
</div>
@@ -253,6 +281,13 @@ exports[`Workspace indicator component Che Workspaces should render STOPPING sta
253281
</svg>
254282
</span>
255283
</span>
284+
<span
285+
aria-atomic="true"
286+
className="pf-v6-screen-reader"
287+
role="status"
288+
>
289+
Workspace status is STOPPING
290+
</span>
256291
</span>
257292
</div>
258293
</div>
@@ -302,6 +337,13 @@ exports[`Workspace indicator component Deprecated workspaces should render "Depr
302337
</svg>
303338
</span>
304339
</span>
340+
<span
341+
aria-atomic="true"
342+
className="pf-v6-screen-reader"
343+
role="status"
344+
>
345+
Workspace status is Deprecated
346+
</span>
305347
</span>
306348
</div>
307349
</div>
@@ -351,6 +393,13 @@ exports[`Workspace indicator component DevWorkspaces should render FAILED status
351393
</svg>
352394
</span>
353395
</span>
396+
<span
397+
aria-atomic="true"
398+
className="pf-v6-screen-reader"
399+
role="status"
400+
>
401+
Workspace status is Failed
402+
</span>
354403
</span>
355404
</div>
356405
</div>
@@ -401,6 +450,13 @@ exports[`Workspace indicator component DevWorkspaces should render FAILING statu
401450
</svg>
402451
</span>
403452
</span>
453+
<span
454+
aria-atomic="true"
455+
className="pf-v6-screen-reader"
456+
role="status"
457+
>
458+
Workspace status is Failing
459+
</span>
404460
</span>
405461
</div>
406462
</div>
@@ -450,6 +506,13 @@ exports[`Workspace indicator component DevWorkspaces should render RUNNING statu
450506
</svg>
451507
</span>
452508
</span>
509+
<span
510+
aria-atomic="true"
511+
className="pf-v6-screen-reader"
512+
role="status"
513+
>
514+
Workspace status is Running
515+
</span>
453516
</span>
454517
</div>
455518
</div>
@@ -510,6 +573,13 @@ exports[`Workspace indicator component DevWorkspaces should render STOPPED statu
510573
</svg>
511574
</span>
512575
</span>
576+
<span
577+
aria-atomic="true"
578+
className="pf-v6-screen-reader"
579+
role="status"
580+
>
581+
Workspace status is Stopped
582+
</span>
513583
</span>
514584
</div>
515585
</div>
@@ -559,6 +629,13 @@ exports[`Workspace indicator component SCC Mismatch should render normal status
559629
</svg>
560630
</span>
561631
</span>
632+
<span
633+
aria-atomic="true"
634+
className="pf-v6-screen-reader"
635+
role="status"
636+
>
637+
Workspace status is Running
638+
</span>
562639
</span>
563640
</div>
564641
</div>
@@ -619,6 +696,13 @@ exports[`Workspace indicator component SCC Mismatch should render normal status
619696
</svg>
620697
</span>
621698
</span>
699+
<span
700+
aria-atomic="true"
701+
className="pf-v6-screen-reader"
702+
role="status"
703+
>
704+
Workspace status is Stopped
705+
</span>
622706
</span>
623707
</div>
624708
</div>
@@ -679,6 +763,13 @@ exports[`Workspace indicator component SCC Mismatch should render normal status
679763
</svg>
680764
</span>
681765
</span>
766+
<span
767+
aria-atomic="true"
768+
className="pf-v6-screen-reader"
769+
role="status"
770+
>
771+
Workspace status is Stopped
772+
</span>
682773
</span>
683774
</div>
684775
</div>
@@ -739,6 +830,13 @@ exports[`Workspace indicator component SCC Mismatch should render warning for ST
739830
</svg>
740831
</span>
741832
</span>
833+
<span
834+
aria-atomic="true"
835+
className="pf-v6-screen-reader"
836+
role="status"
837+
>
838+
Workspace status is Stopped
839+
</span>
742840
</span>
743841
</div>
744842
</div>
@@ -805,6 +903,13 @@ exports[`Workspace indicator component SCC Mismatch should render warning triang
805903
</svg>
806904
</span>
807905
</span>
906+
<span
907+
aria-atomic="true"
908+
className="pf-v6-screen-reader"
909+
role="status"
910+
>
911+
Workspace status has SCC mismatch warning
912+
</span>
808913
</span>
809914
</div>
810915
</div>
@@ -855,6 +960,13 @@ exports[`Workspace indicator component should render default status correctly 1`
855960
</svg>
856961
</span>
857962
</span>
963+
<span
964+
aria-atomic="true"
965+
className="pf-v6-screen-reader"
966+
role="status"
967+
>
968+
Workspace status is STOPPING
969+
</span>
858970
</span>
859971
</div>
860972
</div>

packages/dashboard-frontend/src/components/Workspace/Status/Indicator/index.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,22 @@ const WorkspaceStatusIndicatorComponent: React.FC<Props> = ({
5151
? 'Deprecated workspace'
5252
: status.toLocaleUpperCase();
5353

54+
const statusAriaLabel = sccMismatch
55+
? 'Workspace status has SCC mismatch warning'
56+
: `Workspace status is ${status}`;
57+
5458
return (
5559
<CheTooltip content={tooltip}>
5660
<span
5761
className={styles.statusIndicator}
5862
data-testid="workspace-status-indicator"
59-
aria-label={
60-
sccMismatch
61-
? 'Workspace status has SCC mismatch warning'
62-
: `Workspace status is ${status}`
63-
}
63+
aria-label={statusAriaLabel}
6464
>
6565
{icon}
66+
{/* Live region: announces status changes to screen readers without requiring user focus */}
67+
<span role="status" aria-atomic="true" className="pf-v6-screen-reader">
68+
{statusAriaLabel}
69+
</span>
6670
</span>
6771
</CheTooltip>
6872
);

packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/__tests__/__snapshots__/index.spec.tsx.snap

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ exports[`ProgressStepTitle snapshot - active step 1`] = `
2323
>
2424
Step 1
2525
</span>,
26+
<span
27+
aria-atomic="true"
28+
aria-live="polite"
29+
className="pf-v6-screen-reader"
30+
role="status"
31+
>
32+
Step: Step 1
33+
</span>,
2634
]
2735
`;
2836

@@ -49,16 +57,34 @@ exports[`ProgressStepTitle snapshot - active step failed 1`] = `
4957
>
5058
Step 1
5159
</span>,
60+
<span
61+
aria-atomic="true"
62+
aria-live="polite"
63+
className="pf-v6-screen-reader"
64+
role="status"
65+
>
66+
Step: Step 1
67+
</span>,
5268
]
5369
`;
5470

5571
exports[`ProgressStepTitle snapshot - active step has children 1`] = `
56-
<span
57-
className="progress"
58-
data-testid="step-title"
59-
>
60-
Step 1
61-
</span>
72+
[
73+
<span
74+
className="progress"
75+
data-testid="step-title"
76+
>
77+
Step 1
78+
</span>,
79+
<span
80+
aria-atomic="true"
81+
aria-live="polite"
82+
className="pf-v6-screen-reader"
83+
role="status"
84+
>
85+
Step: Step 1
86+
</span>,
87+
]
6288
`;
6389

6490
exports[`ProgressStepTitle snapshot - active step warning 1`] = `
@@ -84,14 +110,30 @@ exports[`ProgressStepTitle snapshot - active step warning 1`] = `
84110
>
85111
Step 1
86112
</span>,
113+
<span
114+
aria-atomic="true"
115+
aria-live="polite"
116+
className="pf-v6-screen-reader"
117+
role="status"
118+
>
119+
Step: Step 1
120+
</span>,
87121
]
88122
`;
89123

90124
exports[`ProgressStepTitle snapshot - non-active step 1`] = `
91-
<span
92-
className="ready"
93-
data-testid="step-title"
94-
>
95-
Step 1
96-
</span>
125+
[
126+
<span
127+
className="ready"
128+
data-testid="step-title"
129+
>
130+
Step 1
131+
</span>,
132+
<span
133+
aria-atomic="true"
134+
aria-live="polite"
135+
className="pf-v6-screen-reader"
136+
role="status"
137+
/>,
138+
]
97139
`;

packages/dashboard-frontend/src/components/WorkspaceProgress/StepTitle/index.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,15 @@ export class ProgressStepTitle extends React.Component<Props> {
4949
<span data-testid="step-title" className={fullClassName}>
5050
{children}
5151
</span>
52+
{/* Live region: announces the active step to screen readers when it becomes active.
53+
The region is always present so screen readers register it on page load;
54+
content is only set when the step is active (distance === 0) so the
55+
announcement fires exactly once per step transition.
56+
The "Step: " prefix ensures the live region text differs from the visible
57+
step title, preventing ambiguous matches in DOM queries. */}
58+
<span role="status" aria-live="polite" aria-atomic="true" className="pf-v6-screen-reader">
59+
{distance === 0 && typeof children === 'string' ? `Step: ${children}` : ''}
60+
</span>
5261
</>
5362
);
5463
}

packages/dashboard-frontend/src/pages/WorkspacesList/BackupsView/Toolbar/__tests__/index.spec.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ describe('Backups List Toolbar', () => {
4444

4545
expect(screen.queryByRole('searchbox')).toBeTruthy();
4646
expect(screen.queryByRole('button', { name: /search backups/i })).toBeTruthy();
47-
expect(screen.queryByRole('button', { name: /restore from backup/i })).toBeTruthy();
47+
expect(screen.queryByRole('button', { name: /restore workspace/i })).toBeTruthy();
4848
});
4949

5050
it('should call onFilterChange when typing in search', async () => {
@@ -86,7 +86,7 @@ describe('Backups List Toolbar', () => {
8686
it('should call onRestoreClick when clicking Restore Workspace', async () => {
8787
renderComponent();
8888

89-
const restoreButton = screen.getByRole('button', { name: /restore from backup/i });
89+
const restoreButton = screen.getByRole('button', { name: /restore workspace/i });
9090
await userEvent.click(restoreButton);
9191

9292
expect(mockOnRestoreClick).toHaveBeenCalled();

0 commit comments

Comments
 (0)