mcpField
C
content-semantics

content-semantics

github.com/bartwaardenburgDevelopment0187 installsUpdated 1w ago
Repository

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

  1. 01Find issues from scan checkpoints
  2. 02Guide server-rendered content improvements
  3. 03Check headings, landmarks and semantic HTML
  4. 04Improve image alt text and form labels
  5. 05Provide read-only checks and rescan steps

Use cases

  1. 01Fix semantic content issues identified by a scan
  2. 02Add server-rendered content to an empty SPA shell
  3. 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-semantics

After installing, type/content-semanticsin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
checkpoints.md
gotchas.md
semantic-html-guide.md
ssr-strategies.md

Details

From the project README

Content & 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.

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

  1. Get the failing checkpoints from the scan. Each result has a legacy id (3.2) and a stable id (ia.heading-hierarchy).
  2. Go to the fix workflow with the anchor that is equal to the stable id.
  3. Adapt the example to the site. Replace example.com with the domain of the site.
  4. Run the verify command. All verify commands are read-only. Some checks have no shell check. Then use the rescan command.
  5. 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.

LegacyStable IDNameAR IDsStrength
3.1ia.server-rendered-contentServer-side rendered contentAR-READ-01, AR-READ-03MUST
3.2ia.heading-hierarchyHeading hierarchyAR-READ-03MUST
3.3ia.semantic-htmlSemantic HTML elementsnoneMUST
3.4ia.landmarks-ariaLandmarks and ARIAnoneSHOULD
3.5ia.image-altImage alt textnoneMUST
3.6ia.lang-attributeLanguage attributenoneMUST
3.7ia.link-textDescriptive link textsnoneMUST
3.9ia.form-labelsForm label coveragenoneSHOULD
3.10ia.form-autocompleteForm field autocompletenoneSHOULD
3.11ia.keyboard-navigationKeyboard navigabilitynoneSHOULD
3.12ia.document-titleDocument titleAR-READ-03MUST
3.13ia.button-namesButton accessible namesnoneSHOULD
3.14ia.aria-hiddenaria-hidden misusenoneSHOULD
3.15ia.widget-stateDynamic widget statenoneSHOULD

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.

  1. Find an empty shell such as <div id="root"></div> in the raw HTML.
  2. 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.

  1. Keep one <h1> for the page title. Change other <h1> elements to <h2>.
  2. 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>
    
  3. 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>.

  1. 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.

  1. Use the semantic elements from 3.3 first. They give implicit landmarks.
  2. 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>
    
  3. 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.

  1. 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.

  1. Set the language on the root element: <html lang="en">.
  2. Use lang on 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".

  1. 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.

  1. Connect a label to each field:
    <label for="email">Email address</label>
    <input id="email" name="email" type="email">
    
  2. Use a visible <label> when possible. A placeholder alone 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.

  1. 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.

  1. Remove positive tabindex values. Use tabindex="0" or the natural order.
  2. 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.

  1. 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.

  1. Add text or an aria-label to 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.

  1. Remove aria-hidden from <body> and from containers with focusable elements.
  2. To hide inactive content, use hidden or inert. 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.

  1. 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>
    
  2. Update aria-expanded and aria-selected in 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

  1. Each component adds its own <h1>, so the page has many.
  2. <img src="..."> without alt fails. alt="" is correct for decoration.
  3. A <div id="root"></div> shell has no text for agents.
  4. aria-hidden="true" on a modal backdrop that contains the modal hides the modal.
  5. A JavaScript toggle changes the view but not aria-expanded.

See references/gotchas.md for correct and incorrect examples.

References

Comments

Markdown supported