Sociologix
← Latest in AI

Digital experiences · · 5 min read

axe-core + jsdom: check AI-built form labels

Run three focused accessibility rules against controlled HTML, compare broken and repaired forms, and understand what still needs browser review.

By Sociologix Editorial

Official jsdom logo, a yellow hexagon containing a dark branching symbol on white.
Official jsdom repository logo, retrieved October 29, 2026. jsdom supplies the DOM environment in this tutorial; axe-core runs the selected accessibility checks.Image source ↗

Make missing names a repeatable test

An AI-built quote form can look convincing while its email field, submit button or services link has no useful accessible name. Before reviewing the visual design, a small automated test can catch those specific omissions in the markup.

This AI-assisted editorial guide runs axe-core inside jsdom against two controlled, fictional HTML fixtures. The first intentionally contains three errors; the second repairs them. It is a local markup test, with no customer information, model API, form submission or website crawl. It does not establish that a finished website is accessible.

1. Install the exact tools in a scratch folder

We verified axe-core 4.14.0 and jsdom 30.1.3 on October 29, 2026. Their licenses are MPL-2.0 and MIT respectively. The current jsdom package requires Node ^22.22.2, ^24.15.0 or >=26.0.0; our executed example used Node 24.19.0. Check your version before installing. [1] [2]

Use these PowerShell commands in a new folder. The .mjs extension in the next step allows JavaScript module imports without changing package.json. Save the following code as form-accessibility.mjs before the final command. Installation requires internet access; running the fixture does not need a hosted service.

mkdir form-accessibility-demo
cd form-accessibility-demo
node --version
npm init -y
npm install --save-exact --ignore-scripts axe-core@4.14.0 jsdom@30.1.3
# Save form-accessibility.mjs below first.
node form-accessibility.mjs

2. Test the broken form and its repair

The runOnly option selects exactly three rule IDs: label, button-name and link-name. Each result retains the rule ID, affected selectors and its official help link. The test also records incomplete results separately from passes. A selected-rule pass says nothing about rules we did not run. [3]

jsdom creates a DOM for the fixture. Its outside-only option lets the host evaluate the installed axe source in that window without enabling embedded script tags. The code uses only fixed markup under our control; jsdom is not a security sandbox for arbitrary AI-generated code. Default subresource loading remains off. [4]

Array.from copies the result lists into host-side arrays. This matters for Node’s strict assertions: arrays from the jsdom window have a different prototype, even when their string values match. The code closes each window after collecting the results.

import assert from 'node:assert/strict';
import axe from 'axe-core';
import { JSDOM } from 'jsdom';

const rules = ['label', 'button-name', 'link-name'];
const broken = `<main><form>
  <input id="email" type="email">
  <button type="submit"></button>
</form><a href="/services"></a></main>`;
const repaired = `<main><form>
  <label for="email">Work email</label>
  <input id="email" type="email">
  <button type="submit">Request a quote</button>
</form><a href="/services">Explore services</a></main>`;

async function inspect(markup) {
  // Controlled fixture only; jsdom is not an untrusted-code sandbox.
  const dom = new JSDOM(`<!doctype html><html lang="en">
    <head><title>Fictional quote form</title></head>
    <body>${markup}</body></html>`, { runScripts: 'outside-only' });
  try {
    dom.window.eval(axe.source); // Only installed axe code is evaluated.
    const result = await dom.window.axe.run(dom.window.document, {
      runOnly: { type: 'rule', values: rules }
    });
    return {
      violations: Array.from(result.violations, v => ({
        id: v.id, targets: v.nodes.map(n => n.target), helpUrl: v.helpUrl
      })),
      incomplete: Array.from(result.incomplete, v => v.id),
      passedRules: Array.from(result.passes, v => v.id).sort()
    };
  } finally {
    dom.window.close();
  }
}

const before = await inspect(broken);
const after = await inspect(repaired);
assert.deepEqual(before.violations.map(v => v.id).sort(), [...rules].sort());
assert.equal(before.violations.reduce((n, v) => n + v.targets.length, 0), 3);
assert.equal(after.violations.length, 0);
assert.equal(after.incomplete.length, 0);
assert.deepEqual(after.passedRules, [...rules].sort());
console.log(JSON.stringify({node: process.version, axe: axe.version,
  before, after, checksPassed: 5}, null, 2));

3. Read the five verified assertions

The local run found button-name on button, label on #email and link-name on a. Each rule reported one affected node. After repair, the selected-rule violations and incomplete lists were both empty, and all three rule IDs appeared in passedRules.

All five assertions passed: the broken fixture produced the expected three rule IDs; it reported three affected nodes in total; the repaired fixture had zero selected-rule violations; it had zero incomplete results; and its passed-rule IDs matched the selected set. These are observations about this fixture and tool combination, not a sitewide scan.

The repair adds a visible Work email label associated with the input through matching for and id values, plus meaningful button and link text. Deque recommends explicit label associations in common form cases. Text that merely sits beside a field does not necessarily label it programmatically. [5]

To check the regression behavior, remove the repaired label or empty the repaired button. The corresponding assertion should fail. Keep both intentionally broken and repaired fixtures: a test that never demonstrates a known failure is harder to trust. When a report includes a selector, inspect that element and follow its helpUrl rather than hiding the finding with a broad exclusion.

4. Continue in a real browser

axe-core documents limited jsdom support, including that color-contrast does not work there. jsdom itself does not perform visual layout or rendering. This fixture also omits the real application’s CSS, hydration, dialogs, validation messages and state changes. A clean result here cannot answer questions about those features. [6] [4]

For a proposed project workflow, keep this narrow test beside component checks, then run broader automated checks on the actual rendered site. Open menus and dialogs before testing their contents. The axe API guidance explains that hidden regions need to be activated and analyzed in their visible state. [3]

Follow with keyboard and assistive-technology review of the actual user journey: reach every control, identify its purpose, trigger validation, correct errors, submit the form and understand the confirmation. Check visible focus, reading order, contrast, zoom and mobile behavior in the real interface. This article executed none of those browser or assistive-technology checks on the fictional form.

An AI worker can propose a repair from the rule ID and controlled code context, but review the diff and rerun the same tests before accepting it. Keep incomplete findings as review work; they are not passes. Test request delivery separately, since an accessible name does not prove a lead reaches the business.

5. Keep reports useful and scoped

For a real project, record the component or page state, tool versions, selected rule IDs, findings and reviewer follow-up. Avoid attaching private form values to a public issue. This example prints selectors and help links from fictional markup only.

Use the narrow result as one quality check in an AI-enabled website workflow. It is not an accessibility certification, a legal assessment or evidence that every visitor can complete the form.

AXE-CORE is a trademark of Deque Systems, Inc. in the US and other countries. This is an independent tutorial using the open-source engine. [7]

Sources & further reading

  1. Official axe-core 4.14.0 package metadata: version and MPL-2.0 license
  2. Official jsdom 30.1.3 package metadata: Node requirements and MIT license
  3. axe-core 4.14.0 API: rule selection, result categories and visible states
  4. jsdom official README: script execution, subresources and rendering limits
  5. Deque University: the axe-core 4.14 label rule
  6. axe-core official README: supported environments and limited jsdom support
  7. Deque trademark attribution and factual-reference policy

Build AI-enabled websites with checks that matter

Sociologix can help combine digital experiences, AI capabilities and business workflows with clear testing and review steps. Bring one customer journey, such as requesting a quote, and the systems it needs to reach.

Talk to Sociologix