Accessibility Issue: Data Visualizations Without Accessible Alternatives
WCAG Level: A
Severity: Medium
Category: Non-text Content (WCAG 1.1.1)
Issue Description
The application includes SVG-based charts, gauges, and data visualizations (score gauges, zone charts, heatmaps) that convey important information visually but lack accessible alternatives for screen reader users.
User Impact
- Affected Users: Blind users, low vision users
- Severity: Medium - important data insights are inaccessible
Violations Found
File: src/pages/Insights.tsx
Lines: ~45-66 (ScoreGauge component)
<!-- Current Code -->
function ScoreGauge({ score, size = 120, label, color }: { ... }) {
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.5rem' }}>
<svg width={size} height={size} style={{ transform: 'rotate(-90deg)' }}>
<circle cx={size / 2} cy={size / 2} r={radius} fill="none" stroke="rgba(255,255,255,0.08)" strokeWidth={8} />
<circle ... />
<text ... >{score}</text>
</svg>
<span>{label}</span>
</div>
);
}
Issue: SVG has no accessible name or description. Screen readers may announce nothing or just numbers.
File: src/pages/Insights.tsx
Lines: ~68-92 (ZoneChart component)
<!-- Current Code -->
function ZoneChart({ zones, percentages, totalTimeSec }: { ... }) {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem' }}>
{zones.map((z, i) => (
<div key={z.zone} ...>
{/* Visual bar chart */}
</div>
))}
</div>
);
}
Issue: Chart data not accessible to screen readers in a meaningful way.
File: src/pages/Analytics.tsx
Lines: ~186-230 (ConsistencyHeatmap component)
<!-- Current Code -->
function ConsistencyHeatmap({ data }: { data: ConsistencyDay[] }) {
// Visual heatmap grid
return (
<div style={{ display: 'flex', gap: '0.15rem' }}>
{weeks.map((week, wi) => (
<div key={wi} ...>
{week.map((day, di) => (
<div key={di} title={day.date ? `${day.date}: ${day.miles} mi` : ''} ... />
))}
</div>
))}
</div>
);
}
Issue: Heatmap uses title attribute (not announced reliably) and lacks text alternative.
Recommended Fix
<!-- Fixed Code - ScoreGauge -->
function ScoreGauge({ score, size = 120, label, color }: { ... }) {
return (
<div
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.5rem' }}
role="img"
aria-label={`${label}: ${score} out of 100`}
>
<svg width={size} height={size} style={{ transform: 'rotate(-90deg)' }} aria-hidden="true">
{/* SVG content */}
</svg>
<span aria-hidden="true">{label}</span>
</div>
);
}
<!-- Fixed Code - ZoneChart -->
function ZoneChart({ zones, percentages, totalTimeSec }: { ... }) {
// Generate accessible summary
const summary = zones.map((z, i) =>
`Zone ${z.zone} ${z.name}: ${percentages[i]}%`
).join(', ');
return (
<div role="img" aria-label={`Heart rate zone distribution: ${summary}`}>
{/* Visually hidden summary */}
<span className="visually-hidden">{summary}</span>
{/* Visual chart - hidden from AT */}
<div aria-hidden="true">
{zones.map((z, i) => ( /* visual bars */ ))}
</div>
</div>
);
}
<!-- Fixed Code - ConsistencyHeatmap -->
function ConsistencyHeatmap({ data }: { data: ConsistencyDay[] }) {
const totalMiles = data.reduce((sum, d) => sum + d.miles, 0).toFixed(1);
const runDays = data.filter(d => d.miles > 0).length;
return (
<div>
{/* Accessible summary */}
<div className="visually-hidden" role="img" aria-label={`Training consistency: ${runDays} run days, ${totalMiles} total miles over ${data.length} days`}>
{/* Summary for screen readers */}
</div>
{/* Visual heatmap hidden from AT */}
<div aria-hidden="true">
{/* Heatmap grid */}
</div>
</div>
);
}
/* Add to CSS */
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
Changes Made:
- Add
role="img" and aria-label to container with meaningful description
- Hide decorative SVG from screen readers with
aria-hidden="true"
- Provide text summary of chart data for screen readers
- Use
.visually-hidden class for accessible summaries
Additional Instances
- Recharts components in Analytics.tsx (line 128 - ChartCard)
- Training plan progress bars
- Weekly mileage visualizations
- HR efficiency charts
Testing Instructions
- Use screen reader to navigate to data visualizations
- Verify meaningful summary is announced (not just "image" or nothing)
- Data values should be conveyed textually
- Test with NVDA/VoiceOver
Resources
Acceptance Criteria
Accessibility Issue: Data Visualizations Without Accessible Alternatives
WCAG Level: A
Severity: Medium
Category: Non-text Content (WCAG 1.1.1)
Issue Description
The application includes SVG-based charts, gauges, and data visualizations (score gauges, zone charts, heatmaps) that convey important information visually but lack accessible alternatives for screen reader users.
User Impact
Violations Found
File:
src/pages/Insights.tsxLines: ~45-66 (ScoreGauge component)
Issue: SVG has no accessible name or description. Screen readers may announce nothing or just numbers.
File:
src/pages/Insights.tsxLines: ~68-92 (ZoneChart component)
Issue: Chart data not accessible to screen readers in a meaningful way.
File:
src/pages/Analytics.tsxLines: ~186-230 (ConsistencyHeatmap component)
Issue: Heatmap uses title attribute (not announced reliably) and lacks text alternative.
Recommended Fix
Changes Made:
role="img"andaria-labelto container with meaningful descriptionaria-hidden="true".visually-hiddenclass for accessible summariesAdditional Instances
Testing Instructions
Resources
Acceptance Criteria