Accessibility patterns
Minimal HTML snippets for WCAG-aligned structure. Combine with
form validation and Velin components such as
velin-dialog and velin-tabs. These patterns are not a substitute for
testing with assistive technologies.
docs/a11y-patterns/ and samples/a11y-patterns.html. This page mirrors the essentials for the public docs site.
Skip link and focus
VelinStyle ships .velin-skip-link (and skip-link utilities in docs) plus
:focus-visible styles. Point to your main landmark with href="#main-content" (or #main).
<a href="#main-content" class="velin-doc-skip">Skip to main content</a>
…
<main id="main-content">…</main>
Live regions
Use role="status" with aria-live="polite" for non-urgent updates;
role="alert" (assertive) only when users must be interrupted immediately.
<div id="save-status" role="status" aria-live="polite" class="text-muted"></div>
Dialog
For focus trap and Escape handling, prefer <velin-dialog> or follow the WAI-ARIA dialog pattern.
Markup should set aria-modal="true" and reference a label and description.
<div class="velin-modal-overlay" data-velin-open role="presentation">
<div class="velin-modal" role="dialog" aria-modal="true"
aria-labelledby="dlg-title" aria-describedby="dlg-desc">
<h2 id="dlg-title" class="velin-modal__title">Title</h2>
<div id="dlg-desc" class="velin-modal__body">…</div>
</div>
</div>
Tabs
Prefer <velin-tabs> for keyboard support. Static structure example:
<div role="tablist" aria-label="Settings">
<button type="button" role="tab" aria-selected="true" aria-controls="panel-1" id="tab-1">General</button>
<button type="button" role="tab" aria-selected="false" aria-controls="panel-2" id="tab-2">Security</button>
</div>
<div role="tabpanel" id="panel-1" aria-labelledby="tab-1">…</div>
<div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>…</div>
Landmarks
<body>
<header>…</header>
<nav aria-label="Primary">…</nav>
<main id="main-content">…</main>
<aside aria-label="Related">…</aside>
<footer>…</footer>
</body>
Forms and errors
Associate errors with aria-describedby; set aria-invalid="true" when invalid.
Optional layer: src/components/form-validation.css adds :user-invalid styling,
.velin-field-error, and .velin-field-hint — see
Validation.
<label class="velin-label" for="email">Email</label>
<input id="email" class="velin-input" aria-invalid="true" aria-describedby="email-err">
<p class="velin-field-error" id="email-err">Invalid address.</p>
Menu button
Use <velin-dropdown> with role="menuitem" and type-ahead (0.7.0).
<velin-dropdown>
<button slot="trigger">Actions</button>
<button role="menuitem">Edit</button>
</velin-dropdown>
Disclosure (collapse)
<velin-collapse>
<button slot="trigger" type="button">Details</button>
<p>Content…</p>
</velin-collapse>
Data table
<div class="velin-table-wrapper">
<table class="velin-table">
<caption>Sales</caption>
<th scope="col">Region</th>
</table>
</div>
Accessible authentication (3.3.8)
Use .velin-auth-form with labeled inputs, autocomplete for password managers, and aria-describedby for errors. See framework sample samples/wcag22-auth.html and CSS src/a11y/authentication.css.
<form class="velin-auth-form velin-stack velin-gap-3" method="post" action="/login">
<div class="velin-form-group">
<label class="velin-label" for="login-email">Email</label>
<input class="velin-input" id="login-email" type="email" name="email" autocomplete="username" required>
</div>
<div class="velin-form-group">
<label class="velin-label" for="login-password">Password</label>
<input class="velin-input" id="login-password" type="password" name="password" autocomplete="current-password" required>
</div>
<div id="login-error" class="velin-form-error" role="alert">Invalid credentials.</div>
<button type="submit" class="velin-btn velin-btn--primary">Sign in</button>
</form>
Dragging alternatives (2.5.7)
When drag-and-drop is the only pointer path, provide a keyboard alternative: move items with buttons, or use .velin-drag-list with aria-grabbed and Up/Down controls. See samples/wcag22-dragging.html and src/a11y/dragging-alternatives.css.
<ul class="velin-drag-list" aria-label="Sortable list">
<li><button type="button" class="velin-btn velin-btn--ghost velin-btn--sm" aria-label="Move up">↑</button>
<span>Item two</span>
<button type="button" class="velin-btn velin-btn--ghost velin-btn--sm" aria-label="Move down">↓</button>
</li>
</ul>
Consistent help (3.2.6)
Repeat help links in the same relative order across pages — use .velin-help-links or the velin-consistent-help module. See src/a11y/consistent-help.css.
Loading / busy
<section aria-busy="true" aria-live="polite">
<div class="velin-skeleton velin-skeleton--heading"></div>
</section>