About content-semantics
content-semantics is an agent skill for fixing the Content & Semantics category on IsAgentReady.com. It is intended for developers improving page readability and operability, with repair workflows, verification commands and rescanning guidance tied to scan checkpoints.
Key features
- 01Find issues from scan checkpoints
- 02Guide server-rendered content improvements
- 03Check headings, landmarks and semantic HTML
- 04Improve image alt text and form labels
- 05Provide read-only checks and rescan steps
Use cases
- 01Fix semantic content issues identified by a scan
- 02Add server-rendered content to an empty SPA shell
- 03Make page controls easier for browser agents to operate
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ bartwaardenburg/ isagentready-skills --skill content-semanticsAfter installing, type/content-semanticsin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/checkpoints.mdgotchas.mdsemantic-html-guide.mdssr-strategies.mdDetails
From the project READMEContent & Semantics
Fixes the Content & Semantics category of IsAgentReady.com. Agents read the server HTML, the DOM and the accessibility tree. Good semantic HTML gives reliable signals in each of these views.
- Methodology version:
2026-10-22. Methodology: https://isagentready.com/en/methodology - Scoring (weights and points): https://isagentready.com/api/v2/methodology/2026-10-22/scoring
- Full checkpoint data (stage, evidence type, verify command, rescan command): references/checkpoints.md
When to Use
- The scan shows failures in Content & Semantics (legacy ids 3.x).
- The page is an empty SPA shell without server-rendered text.
- Headings, landmarks, alt texts, labels or button names are missing.
- Forms or widgets are hard for browser agents to operate.
When NOT to Use
- JSON-LD or FAQPage markup: use
structured-data. - robots.txt, sitemap or llms.txt: use
ai-content-discovery. - WebMCP tool annotations: use
agent-protocols. - Visual design changes without a change to the HTML structure.
Instructions
- Get the failing checkpoints from the scan. Each result has a legacy id (
3.2) and a stable id (ia.heading-hierarchy). - Go to the fix workflow with the anchor that is equal to the stable id.
- Adapt the example to the site. Replace
example.comwith the domain of the site. - Run the verify command. All verify commands are read-only. Some checks have no shell check. Then use the rescan command.
- Rescan with
isagentready results <domain> --checkpoint <stable-id>or at isagentready.com.
If $ARGUMENTS is provided, use it as the URL to fix or as the checkpoint to fix.
Checkpoints
Strength is the agentready.org spec level (MUST, SHOULD, MAY). Strength does not change the score.
| Legacy | Stable ID | Name | AR IDs | Strength |
|---|---|---|---|---|
| 3.1 | ia.server-rendered-content | Server-side rendered content | AR-READ-01, AR-READ-03 | MUST |
| 3.2 | ia.heading-hierarchy | Heading hierarchy | AR-READ-03 | MUST |
| 3.3 | ia.semantic-html | Semantic HTML elements | none | MUST |
| 3.4 | ia.landmarks-aria | Landmarks and ARIA | none | SHOULD |
| 3.5 | ia.image-alt | Image alt text | none | MUST |
| 3.6 | ia.lang-attribute | Language attribute | none | MUST |
| 3.7 | ia.link-text | Descriptive link texts | none | MUST |
| 3.9 | ia.form-labels | Form label coverage | none | SHOULD |
| 3.10 | ia.form-autocomplete | Form field autocomplete | none | SHOULD |
| 3.11 | ia.keyboard-navigation | Keyboard navigability | none | SHOULD |
| 3.12 | ia.document-title | Document title | AR-READ-03 | MUST |
| 3.13 | ia.button-names | Button accessible names | none | SHOULD |
| 3.14 | ia.aria-hidden | aria-hidden misuse | none | SHOULD |
| 3.15 | ia.widget-state | Dynamic widget state | none | SHOULD |
Fix Workflows
3.1 Server-side rendered content
Passes when the <body> of the raw HTML has more than 200 characters of visible text without JavaScript. A <noscript> fallback gives partial credit.
- Find an empty shell such as
<div id="root"></div>in the raw HTML. - Turn on SSR or static generation in the framework. See references/ssr-strategies.md for Next.js, Nuxt, Astro, Remix, SvelteKit and Angular.
// Next.js App Router: a server component renders on the server by default export default async function Page() { const product = await getProduct(); return <main><h1>{product.name}</h1><p>{product.summary}</p></main>; }
Verify: curl -sf https://example.com/ | sed -e 's/<[^>]*>//g' | tr -s ' \n' | wc -c (expect more than 200)
3.2 Heading hierarchy
Passes when the page has exactly one <h1>, does not skip levels and has no empty headings.
- Keep one
<h1>for the page title. Change other<h1>elements to<h2>. - Add the missing level when a heading jumps from
<h1>to<h3>:<h1>Planner Pro</h1> <h2>Features</h2> <h3>Timeline view</h3> <h2>Pricing</h2> - Do not put the logo image in the
<h1>.
Verify: no read-only shell check gives a pass or fail. Use the rescan command.
3.3 Semantic HTML elements
Passes when the page has <header>, <nav>, <main>, <article> or <section>, and <footer>.
- Replace the layout
<div>wrappers with semantic elements:<body> <header><nav aria-label="Primary"><a href="/">Home</a></nav></header> <main><article><h1>Page title</h1><section><h2>Part</h2></section></article></main> <footer><p>Example Inc</p></footer> </body>
Verify: curl -sf https://example.com/ | grep -oE '<(header|nav|main|article|section|footer)[ >]' | sort -u
3.4 Landmarks and ARIA
Passes when the page has three or more landmark regions and one or more ARIA attributes such as aria-label, aria-current or aria-live.
- Use the semantic elements from 3.3 first. They give implicit landmarks.
- Label repeated landmarks and mark the current page:
<nav aria-label="Primary"><a href="/" aria-current="page">Home</a></nav> <nav aria-label="Footer"><a href="/privacy">Privacy</a></nav> - Do not add redundant roles such as
<main role="main">.
Verify: curl -sf https://example.com/ | grep -oE '(role|aria-[a-z]+)="' | sort -u
3.5 Image alt text
Passes when each <img> has an alt attribute. A page without images passes. alt="" is correct for decorative images.
- Write a short description for each content image. Use
alt=""for decorative images:<img src="team.jpg" alt="The Example Inc team at the 2026 offsite"> <img src="divider.svg" alt="">
Verify: curl -sf https://example.com/ | grep -oE '<img [^>]*>' | grep -vc 'alt=' (expect 0)
3.6 Language attribute
Passes when <html> has a lang attribute with a valid BCP 47 code such as en, nl or en-US.
- Set the language on the root element:
<html lang="en">. - Use
langon elements in a different language:<blockquote lang="fr">.
Verify: curl -sf https://example.com/ | grep -oE '<html[^>]* lang="[A-Za-z-]+"'
3.7 Descriptive link texts
Passes when the sampled links do not use generic text such as "click here", "read more", "learn more", "here", "more", "details" or "info".
- Replace generic text with text that names the target:
<a href="/pricing">See the pricing plans</a> <a href="/blog/sprints" aria-label="Read the sprint planning guide">Read more</a>
Verify: curl -sf https://example.com/ | grep -ciE '>(click here|read more|learn more|here|more)</a>' (expect 0)
3.9 Form label coverage
Passes when each visible form field has a label: <label for>, a wrapping <label>, aria-label, aria-labelledby, title or placeholder. A page without fields skips this check.
- Connect a label to each field:
<label for="email">Email address</label> <input id="email" name="email" type="email"> - Use a visible
<label>when possible. Aplaceholderalone is a weak label.
Verify: no read-only shell check gives a pass or fail. Use the rescan command.
3.10 Form field autocomplete
Passes when 80% or more of the text, email, tel, url, password and search fields have an autocomplete attribute. 40% or more gives partial credit.
- Add the correct token to each field:
<input name="name" autocomplete="name"> <input name="email" type="email" autocomplete="email"> <input name="phone" type="tel" autocomplete="tel"> <input name="password" type="password" autocomplete="current-password">
Verify: curl -sf https://example.com/ | grep -oE '<input[^>]*type="(text|email|tel|url|password)"[^>]*>' | grep -vc 'autocomplete=' (expect 0)
3.11 Keyboard navigability
Passes when the page has no positive tabindex, no clickable <div> without a role and focus, and no event handler on a <div> or <span> without a role.
- Remove positive
tabindexvalues. Usetabindex="0"or the natural order. - Use native controls for click targets:
<!-- Incorrect --> <div onclick="openMenu()">Menu</div> <!-- Correct --> <button type="button" onclick="openMenu()">Menu</button>
Verify: curl -sf https://example.com/ | grep -cE 'tabindex="[1-9]' (expect 0)
3.12 Document title
Passes when <title> is present, not generic ("Home", "Untitled") and 10 to 70 characters long.
- Give each page a unique title with the page topic and the brand:
<title>Pricing plans for teams | Planner Pro</title>
Verify: curl -sf https://example.com/ | grep -oE '<title>[^<]*</title>'
3.13 Button accessible names
Passes when each <button>, input button and [role="button"] has a name from text, aria-label, aria-labelledby, title or value.
- Add text or an
aria-labelto icon buttons:<button type="button" aria-label="Close dialog"><svg aria-hidden="true">...</svg></button> <input type="submit" value="Send message">
Verify: no read-only shell check gives a pass or fail. Use the rescan command.
3.14 aria-hidden misuse
Passes when aria-hidden="true" is not on <body> and not on a container with links, buttons or inputs. aria-hidden on <body> fails.
- Remove
aria-hiddenfrom<body>and from containers with focusable elements. - To hide inactive content, use
hiddenorinert. These attributes also remove focus:<div id="old-panel" inert>...</div>
Verify: curl -sf https://example.com/ | grep -c '<body[^>]*aria-hidden="true"' (expect 0)
3.15 Dynamic widget state
Applies when the server HTML has widgets or live regions. Passes when each widget exposes its state and updates are announced.
- Expose the state on disclosures, tabs, toggles and the current item:
<button aria-expanded="false" aria-controls="faq-1">Can I cancel?</button> <div id="faq-1" hidden>Yes, at any time.</div> <div role="tablist"> <button role="tab" aria-selected="true" aria-controls="tab-1">Monthly</button> </div> <p role="status" aria-live="polite">3 results</p> - Update
aria-expandedandaria-selectedin the same script that changes the view.
Verify: curl -sf https://example.com/ | grep -oE 'aria-(expanded|selected|checked|pressed|current|live)=' | sort | uniq -c
Key Gotchas
- Each component adds its own
<h1>, so the page has many. <img src="...">withoutaltfails.alt=""is correct for decoration.- A
<div id="root"></div>shell has no text for agents. aria-hidden="true"on a modal backdrop that contains the modal hides the modal.- A JavaScript toggle changes the view but not
aria-expanded.
See references/gotchas.md for correct and incorrect examples.
References
- references/checkpoints.md: generated checkpoint data for each checkpoint
- references/semantic-html-guide.md: semantic elements, ARIA mapping, heading and link text patterns
- references/ssr-strategies.md: SSR for each framework
- references/gotchas.md: common errors with correct and incorrect examples