Engineering
Fixing Document Semantics without guessing
Stop inventing ARIA roles from a screenshot. Read the rendered structural map, restore landmarks and headings that assistive technology can use, and verify on the same public URL.
· by Maya Okonkwo

Guessing structure from a Figma export is how you ship a second flat page. The public DOM already shipped. Assistive technology asks for a map. Someone pastes role="region" on every wrapper because the screenshot looked "sectional." Sighted QA still sees a tidy hero. A user navigating by landmarks gets noise, or nothing.
SlaySlop's Document semantics check is blunt about the signal: it "Reviews structural signals that help assistive technology." Structural is the word that matters. A pretty layout is not a map. This post is about fixing that miss without inventing poetry for the accessibility tree.
Outcome you want
After the fix, the cited public URL exposes usable structural signals after a real browser render: a clear main region, navigation that presents as navigation, a heading outline that matches the content story, and native elements that match their jobs. The ticket closes against a URL and a template, not against a vibe that "a11y looked fine."
Boundaries before you edit markup
Document semantics is one accessibility signal. SlaySlop also catalogs Accessible names ("Finds controls and content that lack usable accessible labels"), Accessibility audit ("Runs automated accessibility checks in a rendered browser page"), and Viewport configuration ("Checks mobile viewport setup and zoom-safe behavior"). Closing a structure finding does not retire those.
SlaySlop itself is read-only. It does not edit your site. Only scan URLs you own, manage, or have permission to test. A structure pass on the public DOM is not a WCAG conformance certificate and not a pentest. Treat findings as public-surface hygiene with evidence, then fix in your own repo.
Static HTML is a weak witness. Marketing shells and app chrome often mount after hydration. The Performance and accessibility product framing is that the site opens in a real browser so runtime behavior can be measured. Fix and verify against that same rendered surface.
Loading diagram.
Happy path: read the map the browser already exposes
Do not start in a design file. Start on the page the finding points to. In Chromium DevTools, open the Accessibility tree. Look for main, navigation landmarks, header/footer, and the heading outline. If the tree is a flat list of generic nodes while the pixels look hierarchical, that is your bug. If a recent deploy already restored structure, re-scan before you invent duplicate role attributes.
<!-- Prefer native structure the browser already understands -->
<header>
<nav aria-label="Primary"><!-- ... --></nav>
</header>
<main>
<h1>Pricing</h1>
<h2>Monthly plans</h2>
</main>
<footer><!-- ... --></footer>
<!-- Guessing: visual hierarchy with no structural map -->
<div class="page">
<div class="top"><div class="links"><!-- ... --></div></div>
<div class="big">Pricing</div>
<div class="sub">Monthly plans</div>
</div># Permissioned host — confirm the document responds before a structure pass
curl -sI "https://staging.example.com/pricing" | sed -n '1,12p'Headers only prove the document arrived. Document semantics cares about nodes after paint and script. Open the same URL in a real browser and inspect the tree.
Fix the mismatched class only
Missing main or landmark-less chrome. Wrap primary content in main. Put primary navigation in nav. Prefer one clear main over five competing ones.
Visual titles that are still divs. Replace style-class "headings" with real h1–h6 that match the content outline. Do not skip levels to chase a design scale.
Nav that is only a row of links in a div. Sighted users see a bar. Assistive tech sees generic links. Native nav (named if you have several) is the fix direction, not role="navigation" on every wrapper.
Accessible names fired on the same page. Keep separate tickets. Labeling a hamburger does not create a heading outline. What an attacker sees on the public surface is also what assistive tech sees: the live DOM, not your intent deck.
ARIA spam as a substitute. Ten unnamed role="region" wrappers can be as noisy as zero landmarks. Prefer native elements first. Structural signals should help, not drown, assistive technology.
fix_ticket:
check: document-semantics
definition: Reviews structural signals that help assistive technology.
page: /pricing
template: pricing-hero / layout-shell
action: restore main + heading outline; avoid ARIA-first patches
siblings: accessible-names | accessibility-audit | viewport-configuration
rescan: same URL after deployVerify the same surface
Rescan the same public URL family. SlaySlop's broader product language includes follow-up scans to check whether an issue still appears. Local green on a different hash, or a screenshot of the Figma frame, is not that loop. Click the same route after deploy. Confirm landmarks and headings still hold when late-mounted chrome finishes.
Pitfalls
Closing from a staging shell that still uses the old layout while production ships the flat hero. Declaring victory because Accessible names went green. Treating one local Lighthouse open as Document semantics done. Scanning only an empty SPA state that never mounts the marketing templates in the SOW. Papering over missing headings with aria-label on decorative wrappers. Shipping a second main because a campaign page nested another layout.
Related next step
If Document semantics fires on a primary template, fix that before debating footer microcopy. Pair with Accessible names when the same page also ships anonymous icon buttons. Soft links: Document semantics, Accessible names, Performance and accessibility. Soft close: a permissioned SlaySlop rescan ties the structural fix to the URL better than a folder of unlabeled screenshots.