Skip to content

Commit 261111a

Browse files
georgeglarsonclaude
andcommitted
Add consistent accessibility attributes across all React components
- aria-label on every textarea and unlabeled input - role="alert" on all error containers - aria-live="polite" on output/result regions - scope="col" on all table header cells - htmlFor/id to connect CashRegister labels to inputs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 029b565 commit 261111a

8 files changed

Lines changed: 47 additions & 34 deletions

File tree

RestaurantReviews/src/client/components/exercises/CashRegisterPanel.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,11 @@ export function CashRegisterPanel() {
9797
{/* Add transaction form */}
9898
<div style={styles.addRow}>
9999
<div style={styles.inputGroup}>
100-
<label style={styles.inputLabel}>Owed</label>
100+
<label htmlFor="cash-owed" style={styles.inputLabel}>Owed</label>
101101
<div style={styles.dollarWrap}>
102102
<span style={styles.dollarSign}>$</span>
103103
<input
104+
id="cash-owed"
104105
type="number"
105106
step="0.01"
106107
min="0.01"
@@ -113,10 +114,11 @@ export function CashRegisterPanel() {
113114
</div>
114115
</div>
115116
<div style={styles.inputGroup}>
116-
<label style={styles.inputLabel}>Paid</label>
117+
<label htmlFor="cash-paid" style={styles.inputLabel}>Paid</label>
117118
<div style={styles.dollarWrap}>
118119
<span style={styles.dollarSign}>$</span>
119120
<input
121+
id="cash-paid"
120122
ref={paidRef}
121123
type="number"
122124
step="0.01"
@@ -201,7 +203,7 @@ export function CashRegisterPanel() {
201203
)}
202204
</div>
203205

204-
{error && <div style={styles.error}>{error}</div>}
206+
{error && <div role="alert" style={styles.error}>{error}</div>}
205207
</div>
206208
);
207209
}

RestaurantReviews/src/client/components/exercises/ExercisePanel.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,16 @@ export function ExercisePanel({
5050
placeholder={placeholder}
5151
rows={6}
5252
style={styles.textarea}
53+
aria-label={`${title} input`}
5354
/>
5455

5556
<button onClick={handleRun} disabled={loading} style={styles.button}>
5657
{loading ? loadingLabel : buttonLabel}
5758
</button>
5859

59-
{error && <div style={styles.error}>{error}</div>}
60+
{error && <div role="alert" style={styles.error}>{error}</div>}
6061

61-
{data && <pre style={styles.output}>{data.output}</pre>}
62+
{data && <pre role="status" aria-live="polite" style={styles.output}>{data.output}</pre>}
6263
</div>
6364
);
6465
}

RestaurantReviews/src/client/components/exercises/GildedRosePanel.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,12 @@ export function GildedRosePanel() {
200200
onChange={(e) => setInventory(e.target.value)}
201201
rows={8}
202202
style={styles.textarea}
203+
aria-label="Inventory CSV"
203204
/>
204205
<button onClick={handleStart} disabled={startApi.loading} style={styles.startBtn}>
205206
{startApi.loading ? "Starting..." : "Start Simulation"}
206207
</button>
207-
{startApi.error && <div style={styles.error}>{startApi.error}</div>}
208+
{startApi.error && <div role="alert" style={styles.error}>{startApi.error}</div>}
208209
</>
209210
) : (
210211
<>
@@ -218,16 +219,16 @@ export function GildedRosePanel() {
218219
</button>
219220
</div>
220221

221-
{cmdApi.error && <div style={styles.error}>{cmdApi.error}</div>}
222+
{cmdApi.error && <div role="alert" style={styles.error}>{cmdApi.error}</div>}
222223

223224
<table style={styles.table}>
224225
<thead>
225226
<tr>
226-
<th style={styles.th}>Item</th>
227-
<th style={styles.th}>Category</th>
228-
<th style={{ ...styles.th, textAlign: "right" }}>SellIn</th>
229-
<th style={styles.th}>Quality</th>
230-
<th style={{ ...styles.th, textAlign: "center", width: "50px" }}>{"\u0394"}</th>
227+
<th scope="col" style={styles.th}>Item</th>
228+
<th scope="col" style={styles.th}>Category</th>
229+
<th scope="col" style={{ ...styles.th, textAlign: "right" }}>SellIn</th>
230+
<th scope="col" style={styles.th}>Quality</th>
231+
<th scope="col" style={{ ...styles.th, textAlign: "center", width: "50px" }}>{"\u0394"}</th>
231232
</tr>
232233
</thead>
233234
<tbody>

RestaurantReviews/src/client/components/exercises/MorseCodePanel.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export function MorseCodePanel() {
104104
}
105105
rows={6}
106106
style={styles.textarea}
107+
aria-label={mode === "encode" ? "Text to encode" : "Morse code to decode"}
107108
/>
108109

109110
<div style={styles.buttonRow}>
@@ -117,7 +118,7 @@ export function MorseCodePanel() {
117118
)}
118119
</div>
119120

120-
{error && <div style={styles.error}>{error}</div>}
121+
{error && <div role="alert" style={styles.error}>{error}</div>}
121122

122123
{data && (
123124
<div style={styles.outputWrap}>
@@ -150,7 +151,7 @@ export function MorseCodePanel() {
150151
{morseOutput && audio.playing ? (
151152
<MorseHighlight morse={morseOutput} tokenIndex={audio.tokenIndex} />
152153
) : (
153-
<pre style={styles.output}>{data.output}</pre>
154+
<pre role="status" aria-live="polite" style={styles.output}>{data.output}</pre>
154155
)}
155156
</div>
156157
)}

RestaurantReviews/src/client/components/exercises/OnScreenKeyboardPanel.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,13 +324,14 @@ export function OnScreenKeyboardPanel() {
324324
placeholder="words to spell"
325325
rows={3}
326326
style={styles.textarea}
327+
aria-label="Words to spell"
327328
/>
328329

329330
<button onClick={handleRun} disabled={loading} style={styles.button}>
330331
{loading ? "Spelling..." : "Spell It"}
331332
</button>
332333

333-
{error && <div style={styles.error}>{error}</div>}
334+
{error && <div role="alert" style={styles.error}>{error}</div>}
334335

335336
{data && (
336337
<>

RestaurantReviews/src/client/components/reviews/RestaurantSection.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,25 +103,28 @@ export function RestaurantSection({ refreshKey, onMutate }: Props) {
103103
onChange={(e) => setName(e.target.value)}
104104
placeholder="Name"
105105
style={styles.input}
106+
aria-label="Restaurant name"
106107
/>
107108
<input
108109
value={city}
109110
onChange={(e) => setCity(e.target.value)}
110111
placeholder="City"
111112
style={styles.input}
113+
aria-label="City"
112114
/>
113115
<input
114116
value={cuisine}
115117
onChange={(e) => setCuisine(e.target.value)}
116118
placeholder="Cuisine (optional)"
117119
style={styles.input}
120+
aria-label="Cuisine"
118121
/>
119122
<button onClick={handleCreate} disabled={createApi.loading} style={styles.btn}>
120123
{createApi.loading ? "Creating..." : "Create"}
121124
</button>
122125
</div>
123-
{createApi.error && <div style={styles.error}>{createApi.error}</div>}
124-
{updateApi.error && <div style={styles.error}>{updateApi.error}</div>}
126+
{createApi.error && <div role="alert" style={styles.error}>{createApi.error}</div>}
127+
{updateApi.error && <div role="alert" style={styles.error}>{updateApi.error}</div>}
125128

126129
{listApi.loading && !listApi.data && (
127130
<div style={{ color: "#64748b", fontSize: "13px", padding: "16px 0" }}>
@@ -135,6 +138,7 @@ export function RestaurantSection({ refreshKey, onMutate }: Props) {
135138
onChange={(e) => setFilter(e.target.value)}
136139
placeholder="Filter by city..."
137140
style={styles.filterInput}
141+
aria-label="Filter by city"
138142
/>
139143
{filter && (
140144
<span style={styles.filterLabel}>
@@ -146,10 +150,10 @@ export function RestaurantSection({ refreshKey, onMutate }: Props) {
146150
<table style={styles.table}>
147151
<thead>
148152
<tr>
149-
<th style={styles.th}>Name</th>
150-
<th style={styles.th}>City</th>
151-
<th style={styles.th}>Cuisine</th>
152-
<th style={styles.th}>Actions</th>
153+
<th scope="col" style={styles.th}>Name</th>
154+
<th scope="col" style={styles.th}>City</th>
155+
<th scope="col" style={styles.th}>Cuisine</th>
156+
<th scope="col" style={styles.th}>Actions</th>
153157
</tr>
154158
</thead>
155159
<tbody>

RestaurantReviews/src/client/components/reviews/ReviewSection.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,14 @@ export function ReviewSection({ refreshKey, onMutate }: Props) {
151151
onChange={(e) => setBody(e.target.value)}
152152
placeholder="Comment (optional)"
153153
style={{ ...styles.input, minHeight: "36px", flex: 1 }}
154+
aria-label="Comment"
154155
/>
155156
<button onClick={handleCreate} disabled={createApi.loading} style={styles.btn}>
156157
{createApi.loading ? "Creating..." : "Create"}
157158
</button>
158159
</div>
159-
{createApi.error && <div style={styles.error}>{createApi.error}</div>}
160-
{updateApi.error && <div style={styles.error}>{updateApi.error}</div>}
160+
{createApi.error && <div role="alert" style={styles.error}>{createApi.error}</div>}
161+
{updateApi.error && <div role="alert" style={styles.error}>{updateApi.error}</div>}
161162

162163
{listApi.loading && !listApi.data && (
163164
<div style={{ color: "#64748b", fontSize: "13px", padding: "16px 0" }}>
@@ -168,11 +169,11 @@ export function ReviewSection({ refreshKey, onMutate }: Props) {
168169
<table style={styles.table}>
169170
<thead>
170171
<tr>
171-
<th style={styles.th}>User</th>
172-
<th style={styles.th}>Restaurant</th>
173-
<th style={styles.th}>Rating</th>
174-
<th style={styles.th}>Comment</th>
175-
<th style={styles.th}>Actions</th>
172+
<th scope="col" style={styles.th}>User</th>
173+
<th scope="col" style={styles.th}>Restaurant</th>
174+
<th scope="col" style={styles.th}>Rating</th>
175+
<th scope="col" style={styles.th}>Comment</th>
176+
<th scope="col" style={styles.th}>Actions</th>
176177
</tr>
177178
</thead>
178179
<tbody>

RestaurantReviews/src/client/components/reviews/UserSection.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,19 +105,21 @@ export function UserSection({ refreshKey, onMutate }: Props) {
105105
onChange={(e) => setName(e.target.value)}
106106
placeholder="Name"
107107
style={styles.input}
108+
aria-label="User name"
108109
/>
109110
<input
110111
value={email}
111112
onChange={(e) => setEmail(e.target.value)}
112113
placeholder="Email"
113114
style={styles.input}
115+
aria-label="Email"
114116
/>
115117
<button onClick={handleCreate} disabled={createApi.loading} style={styles.btn}>
116118
{createApi.loading ? "Creating..." : "Create User"}
117119
</button>
118120
</div>
119-
{createApi.error && <div style={styles.error}>{createApi.error}</div>}
120-
{updateApi.error && <div style={styles.error}>{updateApi.error}</div>}
121+
{createApi.error && <div role="alert" style={styles.error}>{createApi.error}</div>}
122+
{updateApi.error && <div role="alert" style={styles.error}>{updateApi.error}</div>}
121123

122124
{listApi.loading && !listApi.data && (
123125
<div style={{ color: "#64748b", fontSize: "13px", padding: "16px 0" }}>
@@ -128,10 +130,10 @@ export function UserSection({ refreshKey, onMutate }: Props) {
128130
<table style={styles.table}>
129131
<thead>
130132
<tr>
131-
<th style={styles.th}>Name</th>
132-
<th style={styles.th}>Email</th>
133-
<th style={styles.th}>Status</th>
134-
<th style={styles.th}>Actions</th>
133+
<th scope="col" style={styles.th}>Name</th>
134+
<th scope="col" style={styles.th}>Email</th>
135+
<th scope="col" style={styles.th}>Status</th>
136+
<th scope="col" style={styles.th}>Actions</th>
135137
</tr>
136138
</thead>
137139
<tbody>

0 commit comments

Comments
 (0)