Accessible server-error normalization, field mapping, rendering, focus recovery, localization, and optional integration with existing form plugins. Application code owns fetch() and passes error payloads to the bridge.
The package is not currently published. After publication, install it with one package manager:
npm install a11y-server-error-bridge
pnpm add a11y-server-error-bridge
yarn add a11y-server-error-bridgeFor local development, run npm install and npm run build:dist before opening an example.
Start with native, labelled form controls. The bridge enhances the form; it does not replace its semantic structure.
<form id="account-form">
<label for="email">Email address</label>
<input id="email" name="email" type="email">
<button type="submit">Save account</button>
</form>import { createServerErrorBridge } from 'a11y-server-error-bridge';
import 'a11y-server-error-bridge/styles.css';
const form = document.querySelector<HTMLFormElement>('#account-form');
if (!form) throw new Error('Account form not found');
const bridge = createServerErrorBridge(form);
bridge.applyErrors({ fields: { email: 'Already used' }, form: ['Please review the form'] });Supports fields envelopes, flat field objects, arrays of { field, message }, malformed fallback, and the optional adapters/problem-details subpath. Server messages are rendered with textContent, not HTML. Unknown fields default to promotion into form errors; choose retain or ignore with unknownFieldStrategy.
The main entry exports A11yServerErrorBridge, createServerErrorBridge, initServerErrorBridges, DEFAULT_OPTIONS, SELECTORS, CLASSES, ATTRIBUTES, EVENTS, and the public TypeScript contracts.
Important options:
| Option | Default | Purpose |
|---|---|---|
focusOnApply |
"auto" |
Focus an addon summary, the form-error region, or the first resolved control. |
unknownFieldStrategy |
"form" |
Promote, retain, or ignore errors without an eligible control. |
messageMode |
"first" |
Render the first field message or all field messages. |
clearOnInput / clearOnChange |
true |
Clear the bridge-owned error for an edited control. |
clearFormErrorsOnEdit |
false |
Also remove current form-level output when a resolved field is edited. |
useAriaErrorMessage |
true |
Add aria-errormessage alongside aria-describedby. |
fieldAliases |
{} |
Map backend keys to a control key, element, or resolver. |
addons |
[] |
Install opt-in integration contracts. |
Instances expose applyErrors(), normalizeErrors(), clearField(), clearErrors(), focusOnError(), locale and adapter registration methods, state getters, and idempotent destroy().
Structured package metadata is available without initializing the plugin:
import { docs } from 'a11y-server-error-bridge/docs';The bridge preserves existing aria-describedby, aria-invalid, and aria-errormessage values, then restores them on field clear, full clear, and destroy. Generated error IDs are private and unique to each bridge instance. Supplied [data-a11y-server-error-for] containers keep their author-provided IDs and content; the bridge adds and removes only its own message output.
clearField(field) accepts either the backend field key, the resolved control name or ID, or the resolved control element. Aliases do not require a second cleanup call: editing or clearing the mapped control removes its visible error, invalid state, and bridge-owned ARIA references together.
const bridge = createServerErrorBridge(form, {
fieldAliases: { email_address: 'email' },
});
bridge.applyErrors({ fields: { email_address: 'Already used' } });
bridge.clearField('email');The bridge supports focus recovery and avoids duplicate live regions by default. Final applications still require testing with their actual markup, backend responses, keyboard flows, browsers, translations, and screen readers.
See TESTING.md for the automated coverage matrix, supported browser assumptions, WCAG mapping, manual screen-reader checks, and known limitations.
These error identification and reference guarantees support WCAG 2.2 criteria 1.3.1 (Info and Relationships), 3.3.1 (Error Identification), and 4.1.2 (Name, Role, Value). They do not by themselves establish conformance for the surrounding application.
The bridge adds no custom keyboard commands. Native buttons and controls retain their normal Tab, Shift+Tab, Enter, and Space behavior. Focus moves only when applyErrors() or focusOnError() requests recovery. Smooth recovery scrolling is reduced to auto when prefers-reduced-motion: reduce is active.
Events bubble from the form, are non-cancelable, and use the a11y-server-error-bridge: prefix.
| Event key | Trigger and important detail |
|---|---|
init |
Synchronous bridge initialization completed. |
beforeApply |
An application cycle started; the existing bridge state is cleared next. |
beforeNormalize / normalize |
Normalization started/completed; completion includes field and form counts. |
fieldResolved / fieldUnresolved |
A backend key was matched or could not be matched. |
integrationConflict |
More than one addon claimed the same field-errors or form-errors rendering surface. |
focus |
Focus recovery succeeded and reports addon, form-error, or first-invalid. |
apply |
Rendering and focus recovery completed. |
fieldClear / clear |
One field or the current bridge-owned state was cleared. |
localeChange |
Current errors were rerendered with another locale. |
error |
Normalization or addon installation/application/reveal failed safely. |
destroy |
Cleanup completed. Repeated destroy() calls are no-ops. |
Calling applyErrors() emits beforeApply, then clear, normalization and field-resolution events, optional conflict/focus events, and finally apply.
Concrete addons are available only from explicit subpaths:
a11y-server-error-bridge/addons/form-validatora11y-server-error-bridge/addons/error-summarya11y-server-error-bridge/addons/async-buttona11y-server-error-bridge/addons/conditional-fields
The root entry does not import optional peers. Supplying an existing peer instance gives deterministic synchronous behavior and leaves ownership with the application. createIfMissing uses an asynchronous dynamic import; an immediate first applyErrors() may therefore use the core fallback before the peer is ready. Addon installation rejections are reported through EVENTS.error.
When one field is cleared, the form-validator and error-summary addons resynchronize their remaining field errors instead of clearing or retaining stale external output.
Import a11y-server-error-bridge/styles.css. Public custom properties include:
--a11y-server-error-bridge-error-color--a11y-server-error-bridge-border-color--a11y-server-error-bridge-background--a11y-server-error-bridge-gap--a11y-server-error-bridge-focus-outline
The package also exposes component classes through CLASSES. Variables beginning with --_ are private implementation details.
The package performs no network requests, analytics, storage, URL synchronization, clipboard access, or form-value persistence. It keeps normalized messages in memory for the instance lifetime and renders server strings with textContent. Applications remain responsible for deciding what payload data they pass to the bridge and what they log, transmit, or retain.
- Checkout error recovery — nested aliases, multiple messages, unknown-field promotion, and a successful retry.
- Basic server-error recovery — the smallest field and form error setup.
- API payload adapters — envelopes, flat objects, error arrays, and Problem Details.
- Full form workflow — pending, rejected, correction, and success states.
- Form validator addon contract — hand normalized errors to an existing validator instance.
- Error summary addon contract — synchronize an external summary with inline errors and focus.
- Async button addon contract — connect rejection text to an existing async button while the application owns pending, retry, and success.
- Conditional fields addon contract — exclude inactive fields and recover focus only within the active conditional path.
The examples use built local files and simulated payloads. The package is not currently published to npm.
- The bridge does not submit forms, own requests, translate application messages, or define the application’s live-region policy.
- The default renderer does not cross shadow-root boundaries or manage application portals.
- Optional peer creation paths require those peer packages and application-level integration testing.
- Automated DOM and Chrome checks do not establish screen-reader or cross-browser conformance.