Skip to content

Commit dadaabd

Browse files
saagpatelclaude
andcommitted
perf: fix accessibility audit findings (contrast, form labels)
- Replace <span> labels with proper <label htmlFor> in Select and Slider components, linking each control via useId() for WCAG-compliant association - Add aria-label to all <select> and <input type="range"> elements as a redundant accessible name fallback - Add id + aria-label to the Epochs <input type="number"> in HyperparamPanel - Bump text-slate-500 and text-slate-600 to text-slate-400 across all components where used for readable content on dark (slate-900/950) backgrounds, bringing contrast ratios to ≥4.5:1 for WCAG AA compliance Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c28b2d8 commit dadaabd

11 files changed

Lines changed: 51 additions & 31 deletions

src/components/playground/ActivationViewer.tsx

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ function DenseActivationBar({ activations }: { activations: Float32Array }) {
178178
/>
179179
))}
180180
{activations.length > MAX_NEURONS && (
181-
<span className="text-[8px] text-slate-600 self-center ml-1">
181+
<span className="text-[8px] text-slate-400 self-center ml-1">
182182
+{activations.length - MAX_NEURONS}
183183
</span>
184184
)}
@@ -214,7 +214,7 @@ function OutputProbabilities({
214214
}}
215215
/>
216216
</div>
217-
<span className="text-[9px] text-slate-500 w-8 text-right">
217+
<span className="text-[9px] text-slate-300 w-8 text-right">
218218
{(v * 100).toFixed(0)}%
219219
</span>
220220
</div>
@@ -251,7 +251,7 @@ function ConvActivationMaps({ activation }: { activation: LayerActivation }) {
251251
/>
252252
))}
253253
{C > numMaps && (
254-
<span className="text-[9px] text-slate-600 self-center">
254+
<span className="text-[9px] text-slate-400 self-center">
255255
+{C - numMaps} more
256256
</span>
257257
)}
@@ -356,7 +356,7 @@ export function ActivationViewer() {
356356
</p>
357357
</div>
358358
<div className="flex-1 flex items-center justify-center">
359-
<p className="text-xs text-slate-500 text-center px-4">
359+
<p className="text-xs text-slate-400 text-center px-4">
360360
{status === "training" || status === "paused"
361361
? "Activations available after training completes"
362362
: "Train a model to inspect activations"}
@@ -378,7 +378,7 @@ export function ActivationViewer() {
378378
{/* Sample thumbnails */}
379379
{samples ? (
380380
<div>
381-
<p className="text-[9px] text-slate-500 uppercase tracking-wider mb-1.5">
381+
<p className="text-[9px] text-slate-400 uppercase tracking-wider mb-1.5">
382382
Test Samples — click to inspect
383383
</p>
384384
<div className="grid grid-cols-5 gap-1">
@@ -399,7 +399,7 @@ export function ActivationViewer() {
399399
</div>
400400
) : (
401401
<div className="flex items-center justify-center py-4">
402-
<span className="text-[10px] text-slate-500">
402+
<span className="text-[10px] text-slate-400">
403403
{loading ? "Loading samples…" : "No samples"}
404404
</span>
405405
</div>
@@ -408,19 +408,17 @@ export function ActivationViewer() {
408408
{/* Per-layer activations */}
409409
{activations && activations.length > 0 && (
410410
<div className="space-y-2">
411-
<p className="text-[9px] text-slate-500 uppercase tracking-wider">
411+
<p className="text-[9px] text-slate-400 uppercase tracking-wider">
412412
Layer Activations
413413
</p>
414414
{activations.map((act, i) => {
415415
// layerConfig available for future type-specific rendering
416416

417417
return (
418418
<div key={i} className="bg-slate-900 rounded p-2">
419-
<p className="text-[9px] text-slate-500 mb-1.5 flex items-center gap-1">
420-
<span className="text-slate-400 font-medium">
421-
{act.layerName}
422-
</span>
423-
<span className="text-slate-700">·</span>
419+
<p className="text-[9px] text-slate-400 mb-1.5 flex items-center gap-1">
420+
<span className="font-medium">{act.layerName}</span>
421+
<span className="text-slate-500">·</span>
424422
<span>{act.shape.join("×")}</span>
425423
</p>
426424
{act.shape.length === 3 ? (

src/components/playground/ConfusionMatrix.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ export function ConfusionMatrix() {
205205
/>
206206
))}
207207
</div>
208-
<p className="text-xs text-slate-500 text-center">
208+
<p className="text-xs text-slate-400 text-center">
209209
Train to see confusion matrix
210210
</p>
211211
</div>

src/components/playground/DatasetSelector.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,12 @@ export function DatasetSelector() {
5555
{meta.name}
5656
</span>
5757
<div className="flex gap-1 items-center">
58-
<span className="text-[10px] text-slate-500">
58+
<span className="text-[10px] text-slate-400">
5959
{meta.downloadSizeMB} MB
6060
</span>
6161
</div>
6262
</div>
63-
<p className="text-[10px] text-slate-500 mt-0.5 leading-tight">
63+
<p className="text-[10px] text-slate-400 mt-0.5 leading-tight">
6464
{meta.inputShape.join("×")} · {meta.numClasses} classes ·{" "}
6565
{(meta.trainSize / 1000).toFixed(0)}k train
6666
</p>
@@ -72,7 +72,7 @@ export function DatasetSelector() {
7272
{/* Dataset load progress bar */}
7373
{status === "loading" && loadProgress > 0 && loadProgress < 1 && (
7474
<div className="mt-2">
75-
<div className="flex justify-between text-[10px] text-slate-500 mb-1">
75+
<div className="flex justify-between text-[10px] text-slate-400 mb-1">
7676
<span>Downloading dataset…</span>
7777
<span>{Math.round(loadProgress * 100)}%</span>
7878
</div>

src/components/playground/HyperparamPanel.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,17 @@ export function HyperparamPanel() {
7373
/>
7474

7575
<div className="flex flex-col gap-1">
76-
<span className="text-xs text-slate-400">Epochs</span>
76+
<label htmlFor="hyperparam-epochs" className="text-xs text-slate-400">
77+
Epochs
78+
</label>
7779
<input
80+
id="hyperparam-epochs"
7881
type="number"
7982
min={1}
8083
max={200}
8184
value={config.epochs}
8285
disabled={disabled}
86+
aria-label="Epochs"
8387
onChange={(e) => {
8488
const val = parseInt(e.target.value, 10);
8589
if (!isNaN(val) && val >= 1 && val <= 200) {

src/components/playground/LossCurveChart.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -393,7 +393,7 @@ export function LossCurveChart() {
393393
</p>
394394
{metricsHistory.length === 0 ? (
395395
<div className="flex-1 flex items-center justify-center">
396-
<p className="text-xs text-slate-600">Train to see metrics</p>
396+
<p className="text-xs text-slate-400">Train to see metrics</p>
397397
</div>
398398
) : (
399399
<div ref={containerRef} className="flex-1 min-h-0" />

src/components/playground/NetworkArchitect.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ export function NetworkArchitect() {
220220
type="button"
221221
disabled={isTraining}
222222
onClick={() => removeLayer(i)}
223-
className="text-slate-500 hover:text-red-400 disabled:opacity-30 transition-colors text-xs leading-none"
223+
className="text-slate-400 hover:text-red-400 disabled:opacity-30 transition-colors text-xs leading-none"
224224
title="Remove layer"
225225
>
226226
@@ -362,7 +362,7 @@ export function NetworkArchitect() {
362362

363363
{/* Flatten: no config */}
364364
{layer.type === "flatten" && (
365-
<p className="text-[10px] text-slate-500">No config</p>
365+
<p className="text-[10px] text-slate-400">No config</p>
366366
)}
367367

368368
{errs.length > 0 && (
@@ -414,7 +414,7 @@ export function NetworkArchitect() {
414414

415415
{/* Param count footer */}
416416
<div className="mt-2 pt-2 border-t border-slate-800 flex justify-between items-center">
417-
<span className="text-[10px] text-slate-500">Total parameters</span>
417+
<span className="text-[10px] text-slate-400">Total parameters</span>
418418
<span className="text-xs font-mono text-slate-300">
419419
{totalParams.toLocaleString()}
420420
</span>

src/components/playground/PlaygroundShell.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export function PlaygroundShell() {
155155
{tutorialMenuOpen && (
156156
<div className="absolute right-0 top-full mt-1 w-56 bg-slate-900 border border-slate-700 rounded-lg shadow-xl z-50 overflow-hidden">
157157
<div className="px-3 py-2 border-b border-slate-800">
158-
<p className="text-[10px] font-medium text-slate-500 uppercase tracking-wider">
158+
<p className="text-[10px] font-medium text-slate-400 uppercase tracking-wider">
159159
Guided Tutorials
160160
</p>
161161
</div>
@@ -172,7 +172,7 @@ export function PlaygroundShell() {
172172
<p className="text-xs font-medium text-slate-200">
173173
{t.title}
174174
</p>
175-
<p className="text-[10px] text-slate-500 mt-0.5">
175+
<p className="text-[10px] text-slate-400 mt-0.5">
176176
{t.description}
177177
</p>
178178
</button>
@@ -319,7 +319,7 @@ export function PlaygroundShell() {
319319
"px-3 py-2 text-xs transition-colors",
320320
rightPanelTab === tab.id
321321
? "text-slate-200 border-b-2 border-blue-500"
322-
: "text-slate-500 hover:text-slate-300",
322+
: "text-slate-400 hover:text-slate-200",
323323
].join(" ")}
324324
>
325325
{tab.label}

src/components/playground/TrainingControls.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ export function TrainingControls() {
237237
style={{ width: `${batchProgress * 100}%` }}
238238
/>
239239
</div>
240-
<span className="text-[10px] text-slate-500 font-mono whitespace-nowrap">
240+
<span className="text-[10px] text-slate-400 font-mono whitespace-nowrap">
241241
{currentBatch}/{totalBatches}
242242
</span>
243243
</div>
@@ -254,7 +254,7 @@ export function TrainingControls() {
254254
"px-2 py-1 rounded text-xs border transition-colors",
255255
overfittingMode
256256
? "border-orange-500 bg-orange-500/15 text-orange-300"
257-
: "border-slate-700 text-slate-500 hover:border-slate-500 hover:text-slate-400",
257+
: "border-slate-700 text-slate-400 hover:border-slate-500 hover:text-slate-200",
258258
status === "training" || status === "loading"
259259
? "opacity-40 cursor-not-allowed"
260260
: "",

src/components/playground/TutorialOverlay.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ export function TutorialOverlay() {
5858
{tutorial.title}
5959
</span>
6060
<div className="flex items-center gap-3">
61-
<span className="text-[10px] text-slate-500">
61+
<span className="text-[10px] text-slate-400">
6262
{tutorialStep + 1}/{tutorial.steps.length}
6363
</span>
6464
<button
6565
type="button"
6666
onClick={() => setActiveTutorial(null)}
67-
className="text-slate-500 hover:text-slate-300 transition-colors"
67+
className="text-slate-400 hover:text-slate-200 transition-colors"
6868
>
6969
<svg
7070
width="14"
@@ -91,7 +91,7 @@ export function TutorialOverlay() {
9191
type="button"
9292
onClick={() => setTutorialStep(tutorialStep - 1)}
9393
disabled={tutorialStep === 0}
94-
className="text-xs text-slate-500 hover:text-slate-300 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
94+
className="text-xs text-slate-400 hover:text-slate-200 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"
9595
>
9696
Back
9797
</button>

src/components/ui/Select.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { useId } from "react";
2+
13
interface SelectOption<T extends string> {
24
value: T;
35
label: string;
@@ -19,12 +21,20 @@ export function Select<T extends string>({
1921
onChange,
2022
disabled = false,
2123
}: SelectProps<T>) {
24+
const id = useId();
25+
2226
return (
2327
<div className="flex flex-col gap-1">
24-
{label && <span className="text-xs text-slate-400">{label}</span>}
28+
{label && (
29+
<label htmlFor={id} className="text-xs text-slate-400">
30+
{label}
31+
</label>
32+
)}
2533
<select
34+
id={id}
2635
value={value}
2736
disabled={disabled}
37+
aria-label={label}
2838
onChange={(e) => onChange(e.target.value as T)}
2939
className={[
3040
"w-full px-2 py-1.5 rounded bg-slate-800 border border-slate-700",

0 commit comments

Comments
 (0)