Skip to main content
VelinStyle v1.0.0
  1. Docs
  2. Getting Started
  3. Accessibility

Accessibility

VelinStyle targets WCAG 2.2 Level AAA token defaults and component contracts — focus, target size, authentication patterns, and reduced motion. Components ship with ARIA, keyboard support, and utilities for screen readers and forced colors. Using the framework does not certify your application. AAA contrast is the default; use data-velin-contrast="aa" for a lighter 4.5:1 palette on <html>.

WCAG 2.2 resourcesAAA conformance matrix · Patterns · npm run test:a11y:coverage · npx velinstyle scan --only a11y · Samples: samples/wcag22-auth.html, samples/wcag22-dragging.html

Component contracts (36)

Each shipped web component has an entry in core/a11y/component-contracts.json (status, keyboard, live regions, required attributes). <velin-persist> supports WCAG 2.2 3.3.7 Redundant Entry by auto-saving form fields. See WCAG 2.2 AAA matrix (six criteria remain partial — author responsibility).

Component contracts (36)

Each shipped web component has an entry in core/a11y/component-contracts.json (status, keyboard, live regions, required attributes). <velin-persist> supports WCAG 2.2 3.3.7 Redundant Entry by auto-saving form fields. See WCAG 2.2 AAA matrix (six criteria remain partial — author responsibility).

Runtime bootstrap (initA11y)

Optional JavaScript wires a live region announcer and scroll padding for fixed headers (focus-not-obscured). Import from @birdapi/velinstyle/a11y or call after your component bundle loads.

import { initA11y } from '@birdapi/velinstyle/a11y';

initA11y({ announcer: true, scrollPadding: true });

Icon-only controls: use velin-icon-label or aria-label on the host — see Icons.

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 content

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);
}
Link button

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).

Color contrast (AAA defaults)

VelinStyle ships 7:1 OKLCH token defaults in :root, dark mode, and all 13 themes — no attribute required. Run npm run test:contrast in the framework repo to validate pairs.

For a lighter AA palette (4.5:1 body text), set data-velin-contrast="aa" on <html> (loads src/a11y/high-contrast-aaa.css overrides). See Color and generated token docs.

WCAG 2.2 checklist

CriterionVelinStyle
3.3.8 Accessible Authenticationauthentication.css
3.2.6 Consistent Helpconsistent-help.css
2.5.7 Dragging Movementsdragging-alternatives.css
2.4.12 Focus Appearancefocus-appearance.css
2.4.11 Focus Not Obscuredfocus-not-obscured.css
2.5.8 Target Sizetarget-size.css
1.4.3 Contrast (Minimum)AAA defaults exceed 7:1; data-velin-contrast="aa" for 4.5:1
2.1.1 KeyboardFocus-visible, focus trap, roving tabindex (35/35 WC contracts pass)
1.4.6 Contrast (Enhanced)Built-in AAA token defaults

Testing in CI

npm run test:a11y, npm run test:a11y:coverage, npm run test:contrast, and npx velinstyle scan --only a11y. See the full scanner rules reference (security, PII, a11y, CSS, performance) or run scans via the CLI scan command and rule tables.

Best Practices