@accesslint/core

中文文档
0.21.0发布于 2026/08/11MITTypeScriptESM + CJS零依赖构建来源已验证近 30 天下载 7.3万

Pure accessibility rule engine — WCAG audit with zero browser dependencies

原始使用文档
文档目录20 节
  1. Contents
  2. Why @accesslint/core
  3. Install
  4. Quick start
  5. Vitest + React Testing Library
  6. Playwright
  7. Cypress
  8. API
  9. runAudit(doc: Document, options?: AuditOptions): AuditResult
  10. version: string
  11. createChunkedAudit(doc: Document, options?: AuditOptions): …
  12. rules
  13. getActiveRules(options?: AuditOptions): Rule[]
  14. getRuleById(id, options?: { locale?: string; additionalRule…
  15. attachReactFiberSource(violations): Promise<void>
  16. Utilities
  17. Rules
  18. Compatibility
  19. Development
  20. License

@accesslint/core

npm version license

Pure accessibility rule engine with zero browser dependencies. Covers WCAG 2.2 Level A and AA with best-practice rules included.

Building with AI coding agents? @accesslint/mcp wraps this engine as a Model Context Protocol server. The Claude Code plugin installs it in one step.

Looking for CI? AccessLint runs accessibility checks on every pull request and posts review comments directly on your GitHub PRs.

Contents

Why @accesslint/core

  • Synchronous API — runAudit() returns results immediately, no async/await needed
  • Works with happy-dom — full support for happy-dom, jsdom, and real browsers with no polyfills or workarounds, including color contrast checks in virtual DOMs
  • Lightweight — 46 KB gzipped (IIFE), zero runtime dependencies
  • Chunked audits — time-budgeted processing via createChunkedAudit to avoid long tasks on the main thread
  • Source mapping (React) — opt-in attachReactFiberSource walks React DevTools fibers and resolves bundled-chunk URLs through their sourcemaps so violations point at real source files (src/Card.tsx:42:7) instead of opaque selectors
  • ESM, CJS, and IIFE — tree-shakable ES modules, CommonJS for Node, and a single-file IIFE for script injection into any page

Install

npm install @accesslint/core

Quick start

Vitest + React Testing Library

Audit a rendered component in your existing test suite:

import { render } from "@testing-library/react";
import { runAudit } from "@accesslint/core";
import { LoginForm } from "./LoginForm";

test("LoginForm has no accessibility violations", () => {
  const { container } = render(<LoginForm />);
  const { violations } = runAudit(container.ownerDocument);
  expect(violations).toEqual([]);
});

Playwright

Inject the library into the page and audit the live DOM:

// a11y.spec.ts
import { test, expect } from "@playwright/test";

const iife = require.resolve("@accesslint/core/iife");

test("page has no accessibility violations", async ({ page }) => {
  await page.goto("https://example.com");

  await page.addScriptTag({ path: iife });

  const violations = await page.evaluate(() => {
    const { runAudit } = (window as any).AccessLint;
    return runAudit(document).violations.map((v: any) => ({
      ruleId: v.ruleId,
      message: v.message,
      selector: v.selector,
      impact: v.impact,
    }));
  });

  expect(violations).toEqual([]);
});

Cypress

Inject the library into the page and audit the live DOM:

// cypress/e2e/a11y.cy.js
Cypress.Commands.add("audit", () => {
  return cy.readFile("node_modules/@accesslint/core/dist/index.iife.js").then((src) => {
    return cy.window().then((win) => {
      win.eval(src);
      const result = win.AccessLint.runAudit(win.document);
      return result.violations;
    });
  });
});

describe("sample.html accessibility audit", () => {
  beforeEach(() => {
    cy.visit("sample.html");
  });

  it("has no accessibility violations", () => {
    cy.audit().should("have.length", 0);
  });
});

API

runAudit(doc: Document, options?: AuditOptions): AuditResult

Run all active rules against a document and return violations. All options are per-call and non-mutating — no global state is touched.

interface AuditOptions {
  additionalRules?: Rule[];
  disabledRules?: string[];
  includeAAA?: boolean;
  componentMode?: boolean;
  locale?: string;
}
interface AuditResult {
  url: string;
  timestamp: number;
  testEngine: TestEngine;
  testEnvironment: TestEnvironment;
  violations: Violation[];
  ruleCount: number;
  skippedRules: { ruleId: string; error: string }[];
}

interface TestEngine {
  name: "accesslint";
  version: string;
}

interface TestEnvironment {
  userAgent: string;
  windowWidth: number;
  windowHeight: number;
  orientationAngle: number;
  orientationType: string;
}

testEngine.version is the @accesslint/core engine version. testEnvironment describes the audited document's realm; unreadable values fall back to "", 0, and "portrait-primary".

interface Violation {
  ruleId: string;
  selector: string;
  html: string;
  impact: "critical" | "serious" | "moderate" | "minor";
  message: string;
  context?: string;
  element?: Element;
  /** Source-code locations for the violating element, when populated by `attachReactFiberSource`. */
  source?: SourceLocation[];
}

interface SourceLocation {
  file: string;
  line: number;
  column?: number;
  /** Component or function name, when known (e.g. "ProductCard"). */
  symbol?: string;
  /** 0 = JSX literal that produced the element; 1+ = enclosing component(s). */
  ownerDepth: number;
}

version: string

The engine version, mirroring axe.version. Also available as AccessLint.version on the standalone (IIFE) global.

import { version } from "@accesslint/core";

createChunkedAudit(doc: Document, options?: AuditOptions): ChunkedAudit

Create a chunked audit that processes rules in time-boxed batches to avoid long tasks.

const audit = createChunkedAudit(document, { includeAAA: true });

function processNext() {
  const hasMore = audit.processChunk(16); // 16ms budget per frame
  if (hasMore) requestAnimationFrame(processNext);
  else console.log(audit.getViolations());
}

processNext();

rules

Array of all bundled Rule objects.

getActiveRules(options?: AuditOptions): Rule[]

Returns bundled rules filtered by the given options: excludes AAA-level rules unless includeAAA is set, excludes page-level rules when componentMode is true, excludes ids in disabledRules, and appends additionalRules. When locale is passed, returns shallow-cloned rules with translated descriptions and guidance.

getRuleById(id, options?: { locale?: string; additionalRules?: Rule[] }): Rule | undefined

Look up a rule by its ID. Pass locale to get the translated rule, or additionalRules to also search custom rules.

attachReactFiberSource(violations): Promise<void>

Opt-in, in-page post-processor that attaches source locations to each violation by walking React fibers. No-op on non-React pages, production builds, or when no fiber is found; never throws. Requires the JSX __source transform and dev sourcemaps (default in CRA, Next dev, Vite + React).

When the fiber carries _debugSource (Vite, CRA) it is trusted as-is; otherwise _debugStack (e.g. Next.js dev) is parsed and resolved through the chunk's .js.map.

Limitation: on the _debugSource path, dev servers that inject a per-module preamble (notably Vite, both React 18 and 19) offset the reported line/column — the file and component stay correct, but the line can point further down than the true source. The _debugStack path is unaffected.

// In a Playwright test, after the audit:
const violations = await page.evaluate(async () => {
  const { runAudit, attachReactFiberSource } = (window as any).AccessLint;
  const result = runAudit(document);
  await attachReactFiberSource(result.violations);
  return result.violations;
});

console.log(violations[0].source);
// [{ file: "src/components/ProductCard.tsx", line: 42, column: 7, symbol: "ProductCard", ownerDepth: 0 }, ...]

Utilities

Helpers for building custom rules:

  • getAccessibleName(el) — compute the accessible name of an element
  • getComputedRole(el) — get the computed ARIA role
  • getImplicitRole(el) — get the implicit (native) ARIA role
  • isAriaHidden(el) — check if an element is hidden via aria-hidden
  • isValidRole(role) — check if a string is a valid ARIA role
  • getAccessibleTextContent(el) — get text content respecting aria-hidden
  • getSelector(el) — generate a CSS selector for an element
  • getHtmlSnippet(el) — get a truncated HTML snippet of an element

Rules

Covers WCAG 2.2 Level A and AA, plus best-practice rules. One additional AAA-level rule (distinguishable/color-contrast-enhanced) is bundled but excluded by default; include it via runAudit(doc, { includeAAA: true }).

Rule IDs match the ruleId field in violations and are used with the disabledRules option and getRuleById().

View all rules
RuleLevelWCAGDescription
navigable/document-titleA2.4.2Documents must have a <title> element.
navigable/bypassA—Page must have a mechanism to bypass repeated blocks.
navigable/page-has-heading-oneA—Page should contain a level-one heading.
labels-and-names/frame-titleA4.1.2Frames must have an accessible name.
labels-and-names/frame-title-uniqueA4.1.2Frame titles should be unique.
distinguishable/meta-viewportAA1.4.4Viewport meta must not disable user scaling.
enough-time/meta-refreshA2.2.1Meta refresh must not redirect or refresh automatically.
enough-time/meta-refresh-no-exceptionA2.2.1Meta refresh must not be used with a delay (no exceptions).
enough-time/blinkA2.2.2<blink> must not be used.
enough-time/marqueeA2.2.2<marquee> must not be used.
text-alternatives/img-altA1.1.1Images must have alternate text.
text-alternatives/svg-img-altA1.1.1SVG images must have an accessible name.
text-alternatives/input-image-altA1.1.1, 4.1.2Image inputs must have alternate text.
text-alternatives/image-redundant-altA—Image alt should not duplicate adjacent text.
text-alternatives/image-alt-wordsA—Alt text should not contain "image", "photo", etc.
text-alternatives/area-altA1.1.1, 4.1.2<area> elements must have alt text.
text-alternatives/object-altA1.1.1<object> elements must have alt text.
text-alternatives/role-img-altA1.1.1role="img" elements must have an accessible name.
keyboard-accessible/server-image-mapA2.1.1Server-side image maps must not be used.
labels-and-names/form-labelA4.1.2Form elements must have labels.
labels-and-names/multiple-labelsA—Form fields should not have multiple labels.
labels-and-names/input-button-nameA4.1.2Input buttons must have discernible text.
adaptable/autocomplete-validAA1.3.5Autocomplete attribute must use valid values.
labels-and-names/label-content-mismatchA—Accessible name must contain visible text.
labels-and-names/label-title-onlyA—Forms should not use title as the only label.
keyboard-accessible/tabindexA—tabindex should not be greater than 0.
keyboard-accessible/focus-orderA—Focusable elements must have an appropriate role.
keyboard-accessible/nested-interactiveA4.1.2Interactive controls must not be nested.
keyboard-accessible/scrollable-regionA2.1.1Scrollable regions must be keyboard accessible.
keyboard-accessible/accesskeysA—Accesskey values must be unique.
keyboard-accessible/focus-visibleAA2.4.7Elements in focus order must have a visible focus indicator.
navigable/heading-orderA—Heading levels should increase by one.
navigable/empty-headingA—Headings must have discernible text.
navigable/p-as-headingA—Paragraphs should not be styled as headings.
landmarks/landmark-mainA—Page should have one main landmark.
landmarks/no-duplicate-bannerA—No duplicate banner landmarks.
landmarks/no-duplicate-contentinfoA—No duplicate contentinfo landmarks.
landmarks/no-duplicate-mainA—No duplicate main landmarks.
landmarks/banner-is-top-levelA—Banner landmark should be top-level.
landmarks/contentinfo-is-top-levelA—Contentinfo landmark should be top-level.
landmarks/main-is-top-levelA—Main landmark should be top-level.
landmarks/complementary-is-top-levelA—Aside landmark should be top-level.
landmarks/landmark-uniqueA—Landmarks of the same type should have unique labels.
landmarks/regionA—All content should be within landmarks.
adaptable/list-childrenA1.3.1<ul> and <ol> must only contain valid children.
adaptable/listitem-parentA1.3.1<li> must be in a <ul>, <ol>, or <menu>.
adaptable/dl-childrenA1.3.1<dt> and <dd> must be in a <dl>.
adaptable/definition-listA1.3.1<dl> must only contain valid children.
distinguishable/letter-spacingAA1.4.12Letter spacing with !important must be at least 0.12em.
distinguishable/line-heightAA1.4.12Line height with !important must be at least 1.5.
distinguishable/word-spacingAA1.4.12Word spacing with !important must be at least 0.16em.
adaptable/orientation-lockAA1.3.4Page orientation must not be restricted.
aria/aria-rolesA4.1.2ARIA role values must be valid.
aria/aria-valid-attrA4.1.2ARIA attributes must be valid (correctly spelled).
aria/aria-valid-attr-valueA4.1.2ARIA attributes must have valid values.
aria/aria-required-attrA4.1.2Required ARIA attributes must be present.
aria/aria-allowed-attrA4.1.2ARIA attributes must be allowed for the role.
aria/aria-allowed-roleA4.1.2ARIA role must be appropriate for the element.
adaptable/aria-required-childrenA1.3.1ARIA roles must have required child roles.
adaptable/aria-required-parentA1.3.1ARIA roles must be in required parent roles.
aria/aria-hidden-bodyA4.1.2aria-hidden must not be on <body>.
aria/aria-hidden-focusA4.1.2aria-hidden regions must not contain focusable elements.
labels-and-names/aria-command-nameA4.1.2ARIA commands must have an accessible name.
labels-and-names/aria-input-field-nameA4.1.2ARIA input fields must have an accessible name.
labels-and-names/aria-toggle-field-nameA4.1.2ARIA toggle fields must have an accessible name.
labels-and-names/aria-meter-nameA4.1.2ARIA meters must have an accessible name.
labels-and-names/aria-progressbar-nameA4.1.2ARIA progressbars must have an accessible name.
labels-and-names/aria-dialog-nameA4.1.2ARIA dialogs must have an accessible name.
labels-and-names/aria-tooltip-nameA4.1.2ARIA tooltips must have an accessible name.
labels-and-names/aria-treeitem-nameA4.1.2ARIA treeitems must have an accessible name.
aria/aria-prohibited-attrA4.1.2ARIA attributes must not be prohibited for the role.
aria/presentation-role-conflictA4.1.2Presentation role must not conflict with focusability.
aria/presentational-children-focusableA4.1.2Presentational children must not contain focusable content.
labels-and-names/button-nameA4.1.2Buttons must have discernible text.
labels-and-names/summary-nameA4.1.2<summary> elements must have an accessible name.
navigable/link-nameA2.4.4, 4.1.2Links must have discernible text.
navigable/skip-linkA2.4.1Skip links must point to a valid target.
distinguishable/link-in-text-blockA1.4.1Links in text blocks must be distinguishable by more than color.
readable/html-has-langA3.1.1<html> must have a lang attribute.
readable/html-lang-validA3.1.1lang on <html> must be valid.
readable/valid-langAA3.1.2lang attribute must have a valid value on all elements.
readable/html-xml-lang-mismatchA3.1.1lang and xml:lang must match.
adaptable/td-headers-attrA1.3.1Table headers references must be valid.
adaptable/th-has-data-cellsA1.3.1Table headers should be associated with data cells.
adaptable/td-has-headerA1.3.1Data cells in large tables should have associated headers.
adaptable/scope-attr-validA1.3.1scope attribute must have a valid value.
adaptable/empty-table-headerA—Table headers should have visible text.
labels-and-names/duplicate-id-ariaA4.1.2IDs used in ARIA must be unique.
time-based-media/video-captionsA1.2.2Videos must have captions.
time-based-media/audio-transcriptA1.2.1Audio elements should have a text alternative.
distinguishable/color-contrastAA1.4.3Text must have sufficient color contrast.
input-assistance/accessible-authenticationAA3.3.8Password inputs must not block password managers.
distinguishable/color-contrast-enhancedAAA1.4.6Text must have enhanced color contrast (AAA).

Compatibility

Tested in the following environments:

EnvironmentSupport
Node.js 18+Yes
happy-domYes
jsdomYes
Chrome / EdgeYes
FirefoxYes
SafariYes

Development

npm install
npm test        # 1191 tests
npm run bench   # performance benchmarks
npm run build   # produces dist/index.js, dist/index.cjs, dist/index.d.ts, dist/index.iife.js

Found a bug or have a suggestion? Open an issue.

License

MIT

版本与发布记录

@accesslint/core 共发布 62 个版本,首次发布于 2026/02/05;过去 12 个月发布了 62 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.21.0latest2026/08/112 个月前
0.20.12026/08/112 个月前
0.20.02026/08/112 个月前
0.19.02026/08/112 个月前
0.18.02026/08/072 个月前
0.17.02026/07/273 个月前

关于 @accesslint/core 的常见问题

@accesslint/core 是什么?

Pure accessibility rule engine — WCAG audit with zero browser dependencies

如何安装 @accesslint/core?

在项目目录运行 npm install @accesslint/core,或使用 pnpm add / yarn add / bun add。

@accesslint/core 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@accesslint/core 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@accesslint/core 可以商用吗?

@accesslint/core 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@accesslint/core 的下载量有多少?

根据 npm 官方下载统计,@accesslint/core 在 2026/10/05 – 2026/11/03 这 30 天内被下载 72,696 次,日均约 2,423 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@accesslint/core 还在维护吗?

最新版本 0.21.0 发布于 2026/08/11(2 个月前);至今共发布 62 个版本,其中过去 12 个月发布了 62 个;首次发布于 2026/02/05。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。