a11y-conditional-fields
v1.0.1
Published
Framework-agnostic progressive enhancement for accessible conditional form sections.
Maintainers
Readme
A11y Conditional Fields
Framework-agnostic progressive enhancement for accessible conditional form sections.
Installation
This package is configured for npm publishing. Before sharing the npm install commands publicly, confirm the package has been published under the configured name.
npm install a11y-conditional-fields
pnpm add a11y-conditional-fields
yarn add a11y-conditional-fieldsUsage
import { createConditionalFields } from "a11y-conditional-fields";
const form = document.querySelector("[data-a11y-conditional-fields]");
if (form instanceof HTMLFormElement) {
const conditionalFields = createConditionalFields(form, {
announceChanges: true,
disableWhenHidden: true
});
conditionalFields.refresh();
}The package is ESM-only and does not auto-initialize on import.
Distribution Files
The default package entry points to the readable ESM build at dist/index.js.
npm run build also emits a minified runtime bundle at dist/index.min.js
for static-site, CDN, and other pre-minified ESM use cases.
Most app bundlers should use the default import:
import { createConditionalFields } from "a11y-conditional-fields";Use the minified subpath when you specifically need the pre-minified runtime:
import { createConditionalFields } from "a11y-conditional-fields/min";The ./min subpath exposes the same public API and reuses the same
dist/index.d.ts declarations. Docs metadata remains available from
a11y-conditional-fields/docs.
HTML Structure
Use a form root, unique ids on conditional sections, and trigger attributes that point to those ids. Multiple target ids can be separated with spaces.
<form data-a11y-conditional-fields>
<label>
<input type="checkbox" data-condition-toggle="shipping-address delivery-notes">
Use a different shipping address
</label>
<section id="shipping-address" data-condition-target hidden aria-labelledby="shipping-title">
<h2 id="shipping-title">Shipping address</h2>
<label for="street">Street</label>
<input id="street" name="street" data-required-when-visible>
</section>
<section id="delivery-notes" data-condition-target hidden aria-labelledby="delivery-title">
<h2 id="delivery-title">Delivery notes</h2>
<label for="notes">Notes for the courier</label>
<textarea id="notes" name="notes"></textarea>
</section>
<p data-condition-status></p>
</form>Supported triggers:
input[type="checkbox"][data-condition-toggle]button[data-condition-toggle]input[type="radio"][data-condition-show]select[data-condition-select]with option-leveldata-condition-show
Useful data attributes:
data-condition-targetmarks a hideable section. Give every target a uniqueid.data-required-when-visiblemakes a field required only while its section is visible.data-disable-when-hiddenopts a field into disabled-when-hidden behavior.data-condition-preserveprevents automatic disable or clear behavior for that field. Preserved named controls can remain submit-eligible while hidden.data-clear-when-hiddenclears a field when its section hides.data-focus-firston a target ordata-focus-when-shownon a field opts into focus movement.data-condition-statusprovides a live region for show/hide announcements.data-errormarks validation text that should be hidden and disconnected when a target hides.
API
import {
A11yConditionalFields,
createConditionalFields,
initConditionalFieldsAll
} from "a11y-conditional-fields";createConditionalFields(form, options)initializes one form and returns aConditionalFieldsInstance.initConditionalFieldsAll(options, root)initializes everyform[data-a11y-conditional-fields]in a document or subtree.new A11yConditionalFields(form, options)is available for class-based integration.refresh()rescans dynamic targets and triggers.show(targetId),hide(targetId),toggle(targetId, visible), andisVisible(targetId)manage known targets by id.destroy()removes listeners, restores managed attributes and field state, and allows the form to be initialized again.
Options:
| Option | Default | Description |
|---|---:|---|
| clearWhenHidden | false | Clears managed fields when their section hides, unless the field has data-condition-preserve. |
| disableWhenHidden | true | Disables managed controls while hidden so they are skipped by keyboard and form submission. |
| announceChanges | true | Uses a polite status region for show/hide messages. |
| focusFirstOnShow | false | Moves focus to the first focusable field when a target opens. |
| validateOnHide | false | Calls checkValidity() after fields are hidden. |
| collapseOnEscape | false | Lets Escape hide a visible controlled section that contains focus. |
The same boolean options can be set as data attributes on the form, such as
data-clear-when-hidden="true" or data-collapse-on-escape="true". JavaScript
options take precedence.
Submission Behavior
By default, hidden managed fields are disabled before submission. Native form
submission and new FormData(form) skip disabled controls, so hidden section
values are not sent unless you opt out of that behavior.
Hiding a section does not clear values by default. The values stay in the DOM and
are available again if the section is shown later. Use clearWhenHidden or
data-clear-when-hidden when stale hidden values should be erased.
If you set disableWhenHidden: false or add data-condition-preserve to a
field, that named control can stay enabled while hidden. Treat that as an
application data decision: filter hidden values before submit, or intentionally
accept them in your server-side handling.
Keyboard Behavior
| Key or action | Behavior |
|---|---|
| Tab / Shift+Tab | Follows the native form order. Hidden conditional fields are not focusable. |
| Enter / Space on a checkbox or button trigger | Uses native control behavior to update the target section. |
| Arrow keys in radio groups or selects | Uses native browser behavior for the form control. |
| Escape inside an open target | When collapseOnEscape is enabled, hides that target and restores focus to its controlling trigger. |
Accessibility Notes
The plugin starts from semantic form markup. It keeps hidden sections out of
keyboard and assistive-technology flows with hidden, inert, aria-hidden,
and disabled form controls. It adds aria-controls and aria-expanded to
checkbox, radio, and button triggers, and it uses a polite role="status" live
region when announcements are enabled.
Use data-required-when-visible for fields that should only be required while
their section is visible. Existing validation errors marked with data-error
are hidden and disconnected from aria-describedby or aria-errormessage when
their section hides. Keep labels or legends near the controls that reveal
conditional fields.
Focus stays on the current control by default. Add data-focus-first to a
target or data-focus-when-shown to a specific field only when moving focus
reduces effort. When collapseOnEscape is enabled, pressing Escape inside a
visible conditional target hides that target and restores focus to its
controlling trigger.
Still test the final form with the browsers and assistive technologies your audience uses. The plugin manages section state; it does not prove that the surrounding form copy, validation flow, or data model is accessible.
Lifecycle Events
Lifecycle events bubble from the form and include the plugin instance in
event.detail.instance.
a11y-conditional-fields:inita11y-conditional-fields:refresha11y-conditional-fields:showa11y-conditional-fields:hidea11y-conditional-fields:changea11y-conditional-fields:destroy
Show, hide, and change events also include targetId, target, trigger, and
visible.
Limitations
- The plugin does not create form markup for you.
- Targets without an
idare ignored because triggers need stable references. - It does not provide an error summary or custom validation UI.
- It does not add custom arrow-key behavior to native form controls.
- Live-region output is intentionally short and still needs manual screen reader verification.
- There is no CSS export; bring your own form and focus styles.
Examples
examples/basicshows a semantic form using checkbox, radio, select, default focus retention, Escape focus restoration, and live-region patterns.examples/options-labcompares the main options, includingfocusFirstOnShow,collapseOnEscape,clearWhenHidden,validateOnHide,announceChanges, anddisableWhenHidden.examples/form-validator-integrationcombines conditional fields witha11y-form-validatorso hidden sections do not block validation while visible follow-up fields still receive summaries and inline errors.
Build the package before opening an example because examples import from
../../dist/index.js.
npm run build
python3 -m http.server 4173Then open one of these URLs:
http://127.0.0.1:4173/examples/basic/http://127.0.0.1:4173/examples/options-lab/http://127.0.0.1:4173/examples/form-validator-integration/
GitHub Pages Demo
The root index.html is a static demo and compact documentation page. Build the
package and assemble the Pages artifact with:
npm run build:pagesThe generated _site/ folder contains index.html, the example pages,
dist/, the validator demo assets, and .nojekyll. Configure GitHub Pages to
use GitHub Actions. After deployment, the project page is expected at:
https://vmitsaras.github.io/a11y-conditional-fields/Docs Metadata
import { docs } from "a11y-conditional-fields/docs";License
MIT
