ARIA tabs · keyboard focus · reduced motion
Accessibility¶
Interaction patterns — focus, keyboard support, motion, labelling — are the component's job, and every component here handles its own. Colour, contrast and type size are the theme's job: components read --theme-* tokens and never hardcode a colour, so an accessible result depends on the values a theme gives those tokens.
What each component handles¶
Four things every component here gets right without a theme having to ask for them.
-
ARIA & semantics
Interactive widgets carry the roles and states screen readers expect — tabs are a real
tablist, empty media placeholders announce as an image with a label rather than nothing at all. -
Keyboard access
Every control a mouse can reach, a keyboard can too — including the horizontal scroll on a wide table, which is easy to strand behind a mouse-only drag.
-
Reduced motion
Anywhere a component animates on its own, it checks
prefers-reduced-motionfirst and shows the resting state instead of skipping straight to motion. -
Images & alt text
Image props resolve
alttext from the media item itself, falling back sensibly when only one image variant has it set. An emptyaltis left empty on purpose for decorative images, not forgotten.
Per-component notes¶
Where the pattern is more specific than "handled," here's the mechanism.
-
Every tab is a real tab, not just a styled button
Screen readers get a genuine
tablist/tab/tabpanelstructure —aria-controls,aria-selected,aria-labelledby, and a rovingtabindexso only the active tab sits in the normal tab order. Arrow keys and Home/End move between tabs, straight from Bootstrap's own Tab component. -
A missing image never reads as nothing
Hero Banner, Ping Pong, Statistic, CTA Icon Item, Media, and Icon Media all announce their empty-state placeholder as
role="img"with a label describing what's missing, instead of a blank, unlabelleddiv. Decorative glyphs — eyebrow icons, CTA arrows — stayaria-hiddenso they don't clutter the announcement. -
Reduced motion means an instant number, not a skipped animation
Statistic checks
prefers-reduced-motionbefore it counts up — the final number renders immediately instead of animating toward it, so the information arrives exactly as fast as everything else on the page. -
A wide table never traps a keyboard user
Table's horizontally-scrolling wrapper is a focusable
role="region"with anaria-label, so scrolling a wide table sideways takes a keyboard, not a mouse. -
Play a video without losing your place
Activating Video facade's poster moves focus straight into the player once it loads, so keyboard focus never falls back to the top of the page when the poster disappears.
-
Nothing auto-plays, so nothing needs a stop button
Neither carousel engine Hero Banner uses ever advances on its own — slides change only on an explicit click on a control or indicator. WCAG 2.2.2's persistent pause control exists for autoplaying content; since nothing here autoplays, there's nothing to pause.
What this module can't enforce for you¶
-
Content quality
Resolving
alttext from the media item, or leaving it empty for a decorative image, still assumes whoever set up that media item wrote something meaningful — this module can require the value exists, not that it's meaningful. The same goes for rich HTML in props like Text'sbody, Statistic'sbody, or Hero Banner Slide'sbody: extra headings, links, and images authored inside them aren't validated either. -
Colour contrast
No component here names a colour — see the two-layer contract. Contrast is entirely the values a theme puts behind
--theme-*, not something this module can guarantee on a theme's behalf.
When you set a custom theme's palette, check it against WCAG 2.1's contrast ratios: 4.5:1 for normal text, 3:1 for large text (24px+, or 19px+ bold) and for the graphical parts of UI components like button borders and focus indicators. --theme-color-primary-ink in the token reference exists specifically for this — it's a computed contrast colour with no Bootstrap equivalent, meant to be the text colour that reads clearly against --theme-color-primary at whatever value your theme gives it.
Before you ship a theme¶
- Tab through it. Every interactive element a mouse reaches should be reachable and operable from the keyboard alone, in an order that matches what's on screen.
- Turn on "reduce motion" in your OS accessibility settings and reload. Animations should stop; content shouldn't.
- Check contrast on your actual token values, not the Bootstrap defaults — a custom theme that redeclares
--theme-color-primaryis also redeclaring what has to stay readable against it.