NEURALNG
FOUNDATION

Accessibility

Build inclusive Angular interfaces from native semantics, predictable keyboard models and explicit focus contracts instead of repairing accessibility after release.

WCAG 2.2 informedKeyboard firstFocus deterministic

SHARED RESPONSIBILITY

A strong foundation, not a compliance badge

NeuralNg is designed against WCAG 2.2 and WAI-ARIA Authoring Practices, but a component library cannot certify the finished application. Content, custom colors, page structure and end-to-end workflows remain part of the product team's accessibility responsibility.

NeuralNg provides

  • Native elements or documented ARIA patterns
  • Keyboard interaction and focus lifecycle
  • State, error, busy and live-region wiring
  • RTL, reduced-motion and high-contrast-friendly hooks

Your application owns

  • Meaningful labels, instructions and alternative text
  • Contrast after token or class customization
  • Heading order, landmarks and logical page flow
  • Automated and assistive-technology testing

Prefer native semantics

Button renders a real button; form controls keep native input semantics. Composite widgets add roles only where the platform has no equivalent, such as tablist, tree, listbox and calendar grid patterns.

The icon-only action has an explicit name. The busy action exposes its state and blocks duplicate activation without replacing native semantics.

actions.html HTML
<neural-button label="Save changes" />

<neural-button
  icon="nt nt-settings"
  ariaLabel="Open settings"
  rounded
/>

<neural-button
  label="Saving changes"
  [loading]="true"
  loadingLabel="Saving changes"
/>

Names, descriptions and errors

Visible text should be the accessible name whenever possible. Field assigns deterministic IDs and keeps for, aria-describedby, aria-invalid, aria-required and pending state synchronized as conditional hints and errors enter the composition.

A placeholder is never a label. Icon-only controls need an ariaLabel or a visible label reference, and decorative icons should remain hidden from assistive technology.
email-field.html HTML
<neural-field
  controlId="account-email"
  required
  [invalid]="email.touched() && email.invalid()"
>
  <label neuralFieldLabel>Email</label>
  <input neuralInput type="email" autocomplete="email" [formField]="email" />
  <small neuralFieldHint>Use your work address.</small>

  @if (email.touched() && email.invalid()) {
    <small neuralFieldError>Enter a valid email address.</small>
  }
</neural-field>

Keyboard contracts follow the widget pattern

Complex components do not invent one universal shortcut set. Each follows the interaction model users expect for its role, while logical directions adapt to orientation and RTL.

PatternPrimary keysFocus model
Button Enter, Space Native button activation
Tabs Arrow keys, Home, End Automatic or manual activation
Combobox Up, Down, Enter, Escape Active option via aria-activedescendant
Tree Arrow keys, Home, End, typeahead Roving treeitem focus
Dialog and Drawer Tab, Shift+Tab, Escape Focus containment and restoration
DatePicker Arrow keys, Home, End, Page Up/Down Grid navigation across dates
Toolbar Orientation arrows, Home, End Roving action focus

Focus belongs to the interaction

Dialog and modal Drawer contain keyboard focus, support an explicit initial focus target and restore focus to the opener after closing. Popover, Select and other non-modal overlays preserve their trigger relationship without trapping the rest of the document.

review-dialog.html HTML
<neural-dialog #review ariaLabelledby="review-title">
  <neural-dialog-header>
    <h2 id="review-title">Review release</h2>
  </neural-dialog-header>
  <neural-dialog-body>
    <input neuralInput neuralDialogInitialFocus aria-label="Release name" />
  </neural-dialog-body>
</neural-dialog>

<neural-button label="Open review" (clicked)="review.open()" />

Never remove focus outlines without supplying an equally visible replacement. Neutral uses :focus-visible so pointer interaction does not create unnecessary rings while keyboard focus remains discoverable.

State must not depend on color alone

Disabled

Native disabled state prevents focus and activation where expected.

Readonly

The value remains discoverable and focusable without becoming editable.

Invalid

ARIA state and associated error text supplement visual severity.

Loading

Busy state, visible text and duplicate-action prevention travel together.

Selected

ARIA selection or checked state complements color and icons.

Live updates

Announcement politeness is chosen by urgency, not decoration.

Motion, contrast and headless ownership

Component motion respects prefers-reduced-motion. Neutral is the recommended accessible baseline, but changing primary/surface scales or using unstyled transfers visual contrast, target size and focus-indicator ownership to the consumer.

Headless does not mean accessibility-free. Structural behavior remains, but every custom foreground/background pair and focus treatment must be tested in light, dark, forced-colors and zoomed layouts.

RTL and localization are accessibility inputs

Logical start/end positions, directional keyboard behavior, localized control labels and calendar conventions resolve from configured direction and locale. Supply the same initial values on server and browser so users do not experience a direction or language shift during hydration.

Test behavior, not only markup

Automated rules catch missing names, invalid relationships and many contrast failures, but cannot judge content quality or complete keyboard flows. Pair component tests and axe checks with keyboard-only, zoom, forced-colors and screen-reader passes on critical journeys.

01

Unit

Roles, attributes, keyboard events and focus restoration.

02

Browser

Real tab order, overlays, zoom, direction and axe rules.

03

Manual

Screen reader output, content meaning and end-to-end flow.

accessibility.spec.ts TypeScript
import AxeBuilder from '@axe-core/playwright';
import { expect, test } from '@playwright/test';

test('button example has no detectable violations', async ({ page }) => {
  await page.goto('/docs/components/button');
  await expect(page.getByRole('button', { name: 'Save changes' })).toBeVisible();

  const result = await new AxeBuilder({ page }).analyze();
  expect(result.violations).toEqual([]);
});

SSR and hydration keep identity stable

Deterministic IDs preserve label, description, control and overlay relationships across server render and hydration. Avoid environment-dependent labels, locale or direction during the initial render; update them only after the application owns a consistent client state.