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>
Loading / busy
<section aria-busy="true" aria-live="polite">
<div class="velin-skeleton velin-skeleton--heading"></div>
</section>