Accessibility
VelinStyle is designed with accessibility in mind — WCAG 2.2 AA is structural, not optional.
Components ship with ARIA, keyboard support, focus management, and utilities for screen readers, reduced motion,
and forced colors. Enable enhanced contrast with data-velin-contrast="aaa" on <html>.
Visually Hidden (sr-only)
The .sr-only class hides an element visually while keeping it accessible to
screen readers. Use .sr-only-focusable to reveal the element when it receives
keyboard focus.
<!-- Hidden label for icon-only buttons -->
<button class="btn btn-icon" aria-label="Close">
<velin-icon name="x" size="18"></velin-icon>
<span class="sr-only">Close</span>
</button>
<!-- Skip link becomes visible on focus -->
<a href="#main-content" class="sr-only sr-only-focusable">
Skip to main content
</a>
Tab into this area to reveal the skip link:
Skip to main contentSkip Links
Skip links let keyboard users jump directly to the main content, bypassing repeated navigation.
VelinStyle includes a .velin-doc-skip class (used on this very docs site) that
is hidden until focused.
<body>
<a href="#main-content" class="skip-link">Skip to main content</a>
<nav>...</nav>
<main id="main-content">
<!-- page content -->
</main>
</body>
Focus Management
All interactive components display a visible focus ring. VelinStyle uses the
:focus-visible pseudo-class so focus indicators only appear during keyboard
navigation, not mouse clicks.
/* VelinStyle default focus ring */
:focus-visible {
outline: 2px solid var(--velin-focus-ring-color, #6366f1);
outline-offset: 2px;
}
/* Custom focus ring via utility */
.focus-ring-primary:focus-visible {
--velin-focus-ring-color: var(--velin-primary);
}
.focus-ring-danger:focus-visible {
--velin-focus-ring-color: var(--velin-danger);
}
Reduced Motion
VelinStyle respects the prefers-reduced-motion media query. When enabled,
all animations and transitions are reduced to near-instant durations, ensuring users who
are sensitive to motion have a comfortable experience.
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
You can also conditionally apply animations only when motion is allowed using the
utility class .motion-safe:
<div class="animate-fade-in motion-safe">
This fades in only when motion is allowed.
</div>
Forced Colors (High Contrast)
VelinStyle supports Windows High Contrast Mode and forced-colors: active.
Component borders, focus rings, and interactive states are rendered using system colors so
they remain visible in high-contrast themes.
@media (forced-colors: active) {
.btn {
border: 1px solid ButtonText;
}
.btn:focus-visible {
outline: 2px solid Highlight;
}
.form-control {
border-color: ButtonText;
}
}
Focus not obscured (WCAG 2.4.11)
Fixed headers can hide focused elements when tabbing. VelinStyle sets
scroll-padding-block-start from --velin-nav-height and provides
.velin-scroll-pt-nav for custom layouts.
<html class="velin-scroll-pt-nav">…</html>
Target size (WCAG 2.5.8)
Primary controls meet 44×44px via target-size.css (.velin-target-touch).
Enhanced contrast (AAA, optional)
Set data-velin-contrast="aaa" on <html> for 7:1 token pairs. Verified with npm run test:contrast.
WCAG 2.2 checklist
| Criterion | VelinStyle |
|---|---|
| 2.4.11 Focus Not Obscured | focus-not-obscured.css |
| 2.5.8 Target Size | target-size.css |
| 2.1.1 Keyboard | Focus trap, roving tabindex, WC updates in 0.7.0 |
| 1.4.6 Contrast (Enhanced) | data-velin-contrast="aaa" |
Testing in CI
npm run test:a11y (axe WCAG 2.2, 33+ pages) and npx velinstyle scan — see scanner rules.
Best Practices
- Always provide
aria-labelor visible text for icon-only buttons. - Use semantic HTML (
<button>,<nav>,<main>). - Include
alton images;alt=""when decorative. - Never rely on color alone for state.
- Test keyboard-only and with a screen reader.
- See A11y patterns.