Skip to content
22 changes: 11 additions & 11 deletions .beads/issues.jsonl

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions .changeset/fix-a11y-cluster.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@stackwright/core": patch
---

Fix accessibility issues: remove WCAG 2.1.2 keyboard trap from code_block `<pre>` element, add visually-hidden `<h1>` to TopAppBar when title is empty and logo is present, add ARIA arrow-key keyboard navigation (ArrowLeft/ArrowRight/Home/End) to tabbed_content component, fix carousel card text/background contrast ratio using auto-computed contrast-safe text color.
4 changes: 2 additions & 2 deletions examples/stackwright-docs/stackwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ customTheme:
text: "#FFFFFF"
textSecondary: "#B0BEC5"
darkColors:
primary: "#FCC03E" # Safety Yellow
primary: "#92400E" # Amber-800 — WCAG AA on white (was #FCC03E Safety Yellow)
secondary: "#0288D1"
accent: "#F59E0B" # Amber 500
accent: "#B45309" # Amber-700 — WCAG AA on white (was #F59E0B)
background: "#FDFDFD"
surface: "#F5F5F5"
text: "#1A1A2E"
Expand Down
23 changes: 23 additions & 0 deletions packages/core/src/components/base/TabbedContentGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,29 @@ export function TabbedContentGrid(content: TabbedContent) {
>
<div
role="tablist"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'ArrowRight') {
e.preventDefault();
const next = (value + 1) % content.tabs.length;
setValue(next);
(document.getElementById(`tab-${next}`) as HTMLElement)?.focus();
} else if (e.key === 'ArrowLeft') {
e.preventDefault();
const prev = (value - 1 + content.tabs.length) % content.tabs.length;
setValue(prev);
(document.getElementById(`tab-${prev}`) as HTMLElement)?.focus();
} else if (e.key === 'Home') {
e.preventDefault();
setValue(0);
(document.getElementById('tab-0') as HTMLElement)?.focus();
} else if (e.key === 'End') {
e.preventDefault();
const last = content.tabs.length - 1;
setValue(last);
(document.getElementById(`tab-${last}`) as HTMLElement)?.focus();
}
}}
style={{
display: 'flex',
justifyContent: 'center',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import { CarouselItem, MediaItem } from '@stackwright/types';
import { useSafeTheme } from '../../../hooks/useSafeTheme';
import { getBetterTextColor } from '../../../utils/colorUtils';
import { Media } from '../../media/Media';

interface OverflowImageCardProps {
Expand All @@ -13,6 +14,7 @@ export const OverflowImageCard = ({ item, minWidth, style }: OverflowImageCardPr
const theme = useSafeTheme();

const backgroundColor = item.background || theme.colors.accent;
const cardTextColor = getBetterTextColor('#1a1a1a', '#FFFFFF', backgroundColor);

return (
<div
Expand Down Expand Up @@ -42,11 +44,11 @@ export const OverflowImageCard = ({ item, minWidth, style }: OverflowImageCardPr
<div style={{ width: '100%', height: '100%', backgroundColor: backgroundColor }}>
<Media {...(item.media as MediaItem)} label={item.title} style="overflow" />

<h3 style={{ margin: theme.spacing.xs, color: theme.colors.text, textAlign: 'center' }}>
<h3 style={{ margin: theme.spacing.xs, color: cardTextColor, textAlign: 'center' }}>
{item.title}
</h3>

<p style={{ margin: theme.spacing.md, color: theme.colors.text }}>{item.text}</p>
<p style={{ margin: theme.spacing.md, color: cardTextColor }}>{item.text}</p>
</div>
</div>
</div>
Expand Down
48 changes: 33 additions & 15 deletions packages/core/src/components/structural/TopAppBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,21 +163,39 @@ export default function TopAppBar({
label={`${title} logo`}
/>
</div>
<h1
style={{
fontSize: 'clamp(1rem, 4vw, 2.125rem)',
fontWeight: 400,
margin: 0,
marginRight: theme.spacing.xl,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
minWidth: 0,
flexShrink: 1,
}}
>
{title}
</h1>
{title ? (
<h1
style={{
fontSize: 'clamp(1rem, 4vw, 2.125rem)',
fontWeight: 400,
margin: 0,
marginRight: theme.spacing.xl,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
minWidth: 0,
flexShrink: 1,
}}
>
{title}
</h1>
) : (
<h1
style={{
position: 'absolute',
width: '1px',
height: '1px',
overflow: 'hidden',
clip: 'rect(0,0,0,0)',
whiteSpace: 'nowrap',
margin: 0,
padding: 0,
border: 0,
}}
>
{(logo as { alt?: string }).alt || 'Home'}
</h1>
)}
</>
) : (
<h1
Expand Down
Loading