fix: accessibility, theming, and focus-visibility improvements
Addresses critical and high-severity findings from UI audit: - C1: Define missing CSS tokens (--hover, --bg-primary/secondary/tertiary) fixing broken hover states and Slides Builder backgrounds - C2: Chart colors now read CSS custom properties at render-time via getChartTheme(), adapting tooltip, ticks, and grid to dark mode - C3: Multi-select ARIA fixed — label elements now carry role="option" and aria-selected for valid listbox semantics - H1/M1: Remove unused --gold and duplicate --primary tokens; replace all var(--primary) with var(--accent) throughout App.css - H3/H4: Focus-visible outlines added to all custom interactive elements (chips, controls, year buttons, hero button, multi-select trigger) - H5: access-badge--full hardcoded colors replaced with design tokens - H7: aria-pressed added to all chart toggle buttons - L1: Hardcoded #fff/white replaced with var(--text-inverse) - M4: index.html now preloads DM Serif Display, Outfit, and IBM Plex Sans Arabic — all fonts actually used in the app Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -305,7 +305,7 @@ function AltMultiSelect({ value, options, onChange, allLabel, countLabel, clearL
|
||||
<div className="altms-dropdown" role="listbox" aria-multiselectable="true">
|
||||
<div className="altms-list">
|
||||
{options.map(opt => (
|
||||
<label key={opt} className={`altms-option${value.includes(opt)?' altms-option--checked':''}`}>
|
||||
<label key={opt} role="option" aria-selected={value.includes(opt)} className={`altms-option${value.includes(opt)?' altms-option--checked':''}`}>
|
||||
<input type="checkbox" className="altms-check" checked={value.includes(opt)} onChange={() => toggle(opt)} aria-label={opt} />
|
||||
<span className="altms-check-box">{value.includes(opt) && <svg width="10" height="8" viewBox="0 0 10 8" fill="none"><path d="M1 4L3.5 6.5L9 1" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/></svg>}</span>
|
||||
<span className="altms-opt-label">{opt}</span>
|
||||
@@ -617,6 +617,15 @@ export default function DashboardDemo({ data, seasons: _seasons, includeVAT, set
|
||||
.alt-page-title { font-size:1.75rem; }
|
||||
.altms-label { max-width:100px; }
|
||||
}
|
||||
|
||||
/* ── focus-visible ── */
|
||||
.alt-chip:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
.alt-ctrl:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
.alt-yr-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
.dalt-hero-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
.altms-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
.altms-clear:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
.alt-cancel:focus-visible, .alt-apply:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
`}</style>
|
||||
|
||||
<h1 className="alt-page-title">{L.pageTitle}</h1>
|
||||
@@ -656,9 +665,9 @@ export default function DashboardDemo({ data, seasons: _seasons, includeVAT, set
|
||||
<div className="alt-chart-header">
|
||||
<h3 className="alt-chart-title">{L.trendTitle}</h3>
|
||||
<div className="alt-chart-controls">
|
||||
{metricOpts.map(o => <button key={o.value} type="button" className={`alt-ctrl${metric===o.value?' alt-ctrl-on':''}`} onClick={() => setMetric(o.value)}>{o.label}</button>)}
|
||||
{metricOpts.map(o => <button key={o.value} type="button" aria-pressed={metric===o.value} className={`alt-ctrl${metric===o.value?' alt-ctrl-on':''}`} onClick={() => setMetric(o.value)}>{o.label}</button>)}
|
||||
<div className="alt-ctrl-sep" />
|
||||
{granOpts.map(o => <button key={o.value} type="button" className={`alt-ctrl${gran===o.value?' alt-ctrl-on':''}`} onClick={() => setGran(o.value)}>{o.label}</button>)}
|
||||
{granOpts.map(o => <button key={o.value} type="button" aria-pressed={gran===o.value} className={`alt-ctrl${gran===o.value?' alt-ctrl-on':''}`} onClick={() => setGran(o.value)}>{o.label}</button>)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="alt-chart-wrap alt-chart-wrap--tall"><Line data={trendData} options={chartOpts} /></div>
|
||||
@@ -668,13 +677,13 @@ export default function DashboardDemo({ data, seasons: _seasons, includeVAT, set
|
||||
<div className="alt-chart-header">
|
||||
<h3 className="alt-chart-title">{L.museumTitle}</h3>
|
||||
<div className="alt-chart-controls">
|
||||
{metricOpts.map(o => <button key={o.value} type="button" className={`alt-ctrl${metric===o.value?' alt-ctrl-on':''}`} onClick={() => setMetric(o.value)}>{o.label}</button>)}
|
||||
{metricOpts.map(o => <button key={o.value} type="button" aria-pressed={metric===o.value} className={`alt-ctrl${metric===o.value?' alt-ctrl-on':''}`} onClick={() => setMetric(o.value)}>{o.label}</button>)}
|
||||
<div className="alt-ctrl-sep" />
|
||||
<button type="button" className={`alt-ctrl${museumChartType==='bar'?' alt-ctrl-on':''}`} onClick={() => setMuseumChartType('bar')}>{L.barLabel}</button>
|
||||
<button type="button" className={`alt-ctrl${museumChartType==='pie'?' alt-ctrl-on':''}`} onClick={() => setMuseumChartType('pie')}>{L.pieLabel}</button>
|
||||
<button type="button" aria-pressed={museumChartType==='bar'} className={`alt-ctrl${museumChartType==='bar'?' alt-ctrl-on':''}`} onClick={() => setMuseumChartType('bar')}>{L.barLabel}</button>
|
||||
<button type="button" aria-pressed={museumChartType==='pie'} className={`alt-ctrl${museumChartType==='pie'?' alt-ctrl-on':''}`} onClick={() => setMuseumChartType('pie')}>{L.pieLabel}</button>
|
||||
<div className="alt-ctrl-sep" />
|
||||
<button type="button" className={`alt-ctrl${museumDisplayMode==='absolute'?' alt-ctrl-on':''}`} onClick={() => setMuseumDisplayMode('absolute')}>{L.absLabel}</button>
|
||||
<button type="button" className={`alt-ctrl${museumDisplayMode==='percent'?' alt-ctrl-on':''}`} onClick={() => setMuseumDisplayMode('percent')}>{L.pctLabel}</button>
|
||||
<button type="button" aria-pressed={museumDisplayMode==='absolute'} className={`alt-ctrl${museumDisplayMode==='absolute'?' alt-ctrl-on':''}`} onClick={() => setMuseumDisplayMode('absolute')}>{L.absLabel}</button>
|
||||
<button type="button" aria-pressed={museumDisplayMode==='percent'} className={`alt-ctrl${museumDisplayMode==='percent'?' alt-ctrl-on':''}`} onClick={() => setMuseumDisplayMode('percent')}>{L.pctLabel}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="alt-chart-wrap alt-chart-wrap--tall">
|
||||
@@ -686,13 +695,13 @@ export default function DashboardDemo({ data, seasons: _seasons, includeVAT, set
|
||||
<div className="alt-chart-header">
|
||||
<h3 className="alt-chart-title">{L.channelTitle}</h3>
|
||||
<div className="alt-chart-controls">
|
||||
{metricOpts.map(o => <button key={o.value} type="button" className={`alt-ctrl${metric===o.value?' alt-ctrl-on':''}`} onClick={() => setMetric(o.value)}>{o.label}</button>)}
|
||||
{metricOpts.map(o => <button key={o.value} type="button" aria-pressed={metric===o.value} className={`alt-ctrl${metric===o.value?' alt-ctrl-on':''}`} onClick={() => setMetric(o.value)}>{o.label}</button>)}
|
||||
<div className="alt-ctrl-sep" />
|
||||
<button type="button" className={`alt-ctrl${channelChartType==='bar'?' alt-ctrl-on':''}`} onClick={() => setChannelChartType('bar')}>{L.barLabel}</button>
|
||||
<button type="button" className={`alt-ctrl${channelChartType==='pie'?' alt-ctrl-on':''}`} onClick={() => setChannelChartType('pie')}>{L.pieLabel}</button>
|
||||
<button type="button" aria-pressed={channelChartType==='bar'} className={`alt-ctrl${channelChartType==='bar'?' alt-ctrl-on':''}`} onClick={() => setChannelChartType('bar')}>{L.barLabel}</button>
|
||||
<button type="button" aria-pressed={channelChartType==='pie'} className={`alt-ctrl${channelChartType==='pie'?' alt-ctrl-on':''}`} onClick={() => setChannelChartType('pie')}>{L.pieLabel}</button>
|
||||
<div className="alt-ctrl-sep" />
|
||||
<button type="button" className={`alt-ctrl${channelDisplayMode==='absolute'?' alt-ctrl-on':''}`} onClick={() => setChannelDisplayMode('absolute')}>{L.absLabel}</button>
|
||||
<button type="button" className={`alt-ctrl${channelDisplayMode==='percent'?' alt-ctrl-on':''}`} onClick={() => setChannelDisplayMode('percent')}>{L.pctLabel}</button>
|
||||
<button type="button" aria-pressed={channelDisplayMode==='absolute'} className={`alt-ctrl${channelDisplayMode==='absolute'?' alt-ctrl-on':''}`} onClick={() => setChannelDisplayMode('absolute')}>{L.absLabel}</button>
|
||||
<button type="button" aria-pressed={channelDisplayMode==='percent'} className={`alt-ctrl${channelDisplayMode==='percent'?' alt-ctrl-on':''}`} onClick={() => setChannelDisplayMode('percent')}>{L.pctLabel}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="alt-chart-wrap">
|
||||
@@ -704,13 +713,13 @@ export default function DashboardDemo({ data, seasons: _seasons, includeVAT, set
|
||||
<div className="alt-chart-header">
|
||||
<h3 className="alt-chart-title">{L.districtTitle}</h3>
|
||||
<div className="alt-chart-controls">
|
||||
{metricOpts.map(o => <button key={o.value} type="button" className={`alt-ctrl${metric===o.value?' alt-ctrl-on':''}`} onClick={() => setMetric(o.value)}>{o.label}</button>)}
|
||||
{metricOpts.map(o => <button key={o.value} type="button" aria-pressed={metric===o.value} className={`alt-ctrl${metric===o.value?' alt-ctrl-on':''}`} onClick={() => setMetric(o.value)}>{o.label}</button>)}
|
||||
<div className="alt-ctrl-sep" />
|
||||
<button type="button" className={`alt-ctrl${districtChartType==='bar'?' alt-ctrl-on':''}`} onClick={() => setDistrictChartType('bar')}>{L.barLabel}</button>
|
||||
<button type="button" className={`alt-ctrl${districtChartType==='pie'?' alt-ctrl-on':''}`} onClick={() => setDistrictChartType('pie')}>{L.pieLabel}</button>
|
||||
<button type="button" aria-pressed={districtChartType==='bar'} className={`alt-ctrl${districtChartType==='bar'?' alt-ctrl-on':''}`} onClick={() => setDistrictChartType('bar')}>{L.barLabel}</button>
|
||||
<button type="button" aria-pressed={districtChartType==='pie'} className={`alt-ctrl${districtChartType==='pie'?' alt-ctrl-on':''}`} onClick={() => setDistrictChartType('pie')}>{L.pieLabel}</button>
|
||||
<div className="alt-ctrl-sep" />
|
||||
<button type="button" className={`alt-ctrl${districtDisplayMode==='absolute'?' alt-ctrl-on':''}`} onClick={() => setDistrictDisplayMode('absolute')}>{L.absLabel}</button>
|
||||
<button type="button" className={`alt-ctrl${districtDisplayMode==='percent'?' alt-ctrl-on':''}`} onClick={() => setDistrictDisplayMode('percent')}>{L.pctLabel}</button>
|
||||
<button type="button" aria-pressed={districtDisplayMode==='absolute'} className={`alt-ctrl${districtDisplayMode==='absolute'?' alt-ctrl-on':''}`} onClick={() => setDistrictDisplayMode('absolute')}>{L.absLabel}</button>
|
||||
<button type="button" aria-pressed={districtDisplayMode==='percent'} className={`alt-ctrl${districtDisplayMode==='percent'?' alt-ctrl-on':''}`} onClick={() => setDistrictDisplayMode('percent')}>{L.pctLabel}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="alt-chart-wrap">
|
||||
|
||||
Reference in New Issue
Block a user