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

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.

Important: The full pattern reference and sample page live in the VelinStyle repository under 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>

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>