Accessibility patterns
Minimal HTML snippets for WCAG-aligned structure. These are not a substitute for manual testing with assistive technologies.
Skip link and focus
VelinStyle ships .velin-skip-link and focus-visible styles in the core bundle. Target your main landmark with href="#main".
<a href="#main" class="velin-skip-link">Skip to main content</a> … <main id="main">…</main>
Live regions
Use role="status" + aria-live="polite" for non-urgent updates; role="alert" (assertive) sparingly for errors that must interrupt.
<div id="save-status" role="status" aria-live="polite" class="velin-text-sm velin-text-muted"></div> <!-- optional: .velin-live-region for visually hidden SR-only updates -->
Dialog
For behavior (focus trap, Esc), use <velin-dialog> or follow the WAI-ARIA dialog pattern. Markup should include aria-modal="true" and label/description references.
<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">…</main> <aside aria-label="Related">…</aside> <footer>…</footer> </body>
Forms and errors
Associate errors with aria-describedby; set aria-invalid="true" when invalid. Visual styles: .velin-field-error, .velin-form--validated.
<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>
Disclosure (collapse)
Prefer <velin-collapse> with a <button slot="trigger"> so aria-expanded and aria-controls stay in sync.
<velin-collapse> <button slot="trigger" type="button">Show details</button> <p>Expandable content…</p> </velin-collapse>
Data table
Use <th scope="col|row"> and <caption> for data tables. Wrap wide tables in .velin-table-wrapper.
<div class="velin-table-wrapper">
<table class="velin-table">
<caption>Sales by region</caption>
<thead><tr><th scope="col">Region</th><th scope="col">Total</th></tr></thead>
<tbody>…</tbody>
</table>
</div>
Loading / busy
Mark loading regions with aria-busy="true" until content is ready. Pair with .velin-skeleton to avoid layout shift.
<section aria-busy="true" aria-live="polite"> <div class="velin-skeleton velin-skeleton--heading"></div> <div class="velin-skeleton velin-skeleton--text"></div> </section>