Modern web development has habit of using <div>, <span> and other non-native elements and then slapping a coat of ARIA roles on them to ensure they are "accessible'. The result is a tangled web of ARIA that is used to cover a foundation of "DIV soup".
I built Semantica11y to help fix that.
Semantica11y is a small, focused rules engine that analyzes your application and highlights places where semantic HTML should be used instead of ARIA-heavy or non-semantic patterns. It’s not Axe-core, its not a WCAG violation checker, it’s simply a tool that helps you write cleaner, sustainable, and more accessible markup from the start.
Why I Created Semantica11y
After years of reviewing sites for accessibility and getting asked questions on how to fix accessibility issues, I kept seeing the same patterns.
Using ARIA instead of semantic or native elements:
<div role="button"> instead of <button>
Overriding native label for buttons completely:
<button aria-label="Insurance Information">Continue </button>
Completely missing key landmarks for webpages:
<body>
<header></header>
<main></main>
<footer></footer>
</body>
ARIA attributes used to recreate native behavior that HTML already provides out of the box. These aren’t violations but instead are missed opportunities.
I wanted a tool that:
Encourages developers to use semantic HTML first
Helps testers identify semantic gaps in UI components
Integrates cleanly into Playwright, CI pipelines, and local dev workflows
Educates developers on WHY semantic/native HTML matters.
Using Semantica11y
Semantica11y is published on npm, so installation is straightforward:
npm install semantica11y
It is designed to run against rendered HTML, which most integration testing libraries such as Playwright allow you to be able to test against.
Here’s a simple Playwright example:
const { test, expect } = require('@playwright/test');
const semantica11y = import('semantica11y');
test('scans a live page with the published semantica11y package', async ({ page }, testInfo) => {
const { Analyzer, exportTextReport, formatConsoleReport } = await semantica11y;
const analyzer = new Analyzer();
await page.goto('https://www.normalil.gov/', { waitUntil: 'domcontentloaded' });
const results = await analyzer.analyzeHTML(
await page.content(),
page.url()
);
console.log(formatConsoleReport(results, { colors: false }));
const reportPath = testInfo.outputPath('semantica11y-report.txt');
await exportTextReport(results, reportPath, { colors: false });
console.log(`Semantica11y text report written to: ${reportPath}`);
expect(results.summary.errors).toBe(0);
expect(results.summary.warnings).toBe(0);
});
});
The Results
When your test cases with Semantica11y you can either console out the results or get a text format of the results put to a specific folder.
An example of the output looks like the following below:
The results are broken up into errors, warnings and suggestions.
Where to Learn More and Contribute
If you to explore the project more in depth, contribute, or follow for more updates, go here: https://github.com/Steady5063/Semantica11y

Top comments (0)