Comprehensive workbook
Semantic HTML for Reliable Browser Agents
A comprehensive pattern guide to landmarks, names, controls, state, errors, and task verification for humans and browser agents.
By AgentReady Editorial Team. Technical review: AgentReady Engineering.
Start with a task and the document outline
Choose one real public task, such as finding an installation guide, comparing plans, or preparing a form without sending it. Write the expected starting point, destination, safe stopping point, and visible proof. Then inspect the document without styling. The title and primary heading should identify the page; subsequent headings should describe sections in a logical hierarchy; landmarks should identify header, navigation, main content, complementary material, and footer where those regions exist. This outline gives both people and tools a stable way to move through the page without reasoning from pixel coordinates. [AR3][AR11]
Avoid using headings solely to achieve a font size. Skipped levels and several indistinguishable primary headings make the hierarchy harder to interpret. Repeated card titles can be lower-level headings inside a named section. A page can have nested sections, but adding section elements everywhere does not automatically improve meaning. Use the simplest structure that describes the content, then verify the accessibility tree and heading outline in browser developer tools. Repair shared layouts first because one heading or landmark defect may affect every route. [AR3][AR11]
- Task: Write one user job, the safe stopping point, and the evidence of success before changing markup.
- Outline: List title, H1, H2 and H3 headings, landmarks, and primary navigation labels in reading order.
- Verify: Compare the DOM and accessibility tree; fix missing, duplicate, or misleading structure at the component source.
Use links for navigation and buttons for operations
A link identifies a destination and supports browser behavior such as copying, opening in a new tab, and exposing a URL. A button performs an operation in the current context. Replacing either with a clickable div removes built-in keyboard and semantic behavior and forces every consumer to reconstruct it. Use an anchor with a real href for navigation and a button with an explicit type for actions. If a control changes between the two jobs, reconsider the interaction rather than layering event handlers on an element with the wrong role. [AR3][AR4][AR11]
Write labels that communicate outcome. Account settings is stronger than click here; Remove project is stronger than an unlabeled trash icon. Repeated labels need context, but avoid stuffing invisible names with unrelated keywords. An icon can accompany text or use an accessible name when the visual convention is truly understood. Test names in the accessibility tree, not only by reading aria-label in source, because visible labels, referenced labels, and nested content can combine in unexpected ways. [AR3][AR4]
- Inventory: Search the critical journey for click handlers on noninteractive elements, links without href, and buttons without explicit type.
- Name: Make the accessible name describe the destination or operation and distinguish repeated controls.
- Exercise: Activate each control with keyboard and confirm native browser behaviors still work.
Prefer native elements before ARIA patterns
Native inputs, selects, details elements, dialogs where supported, and buttons carry established semantics and interaction expectations. ARIA can express roles, properties, and states when native HTML cannot model the widget, but it does not add keyboard behavior, focus management, validation, or business logic. A custom combobox, menu, tabs interface, or grid therefore requires a complete interaction pattern rather than a role attribute alone. Consult the documented pattern, decide whether the complexity is justified, and test with multiple input methods. [AR4][AR11]
Do not use ARIA to contradict native semantics or hide focusable content. Keep owned and controlled relationships accurate as components mount and unmount. Dynamic IDs must remain stable for the lifetime of the relationship. Expose expanded, selected, checked, pressed, invalid, and busy states only when they are true, and update them in the same state transition that changes the interface. Automation becomes flaky when visible state and programmatic state disagree. [AR3][AR4]
- Simplify: Ask whether a native element can satisfy the job before adopting a composite widget.
- Implement the pattern: Add keyboard support, focus behavior, roles, names, properties, and states as one tested contract.
- Keep truth synchronized: Update visible and programmatic state together and remove stale relationships when content disappears.
Design stable names, descriptions, and state
Accessible names answer what is this; descriptions provide supporting instructions. Keep the name concise and persistent. Place format or consequence guidance in visible text associated with the control. Placeholder text is not a substitute for a label because it disappears and may be interpreted as an example rather than identity. When a button changes from Save to Saving to Saved, expose the busy state and completion message deliberately so a consumer does not click again or assume the request failed. [AR3][AR4]
State should be observable before and after action. A disclosure reports whether it is expanded and which region it controls. A tab reports selection and links to its panel. A validation summary points to fields with errors. A modal has a name, moves focus inside, prevents focus from wandering behind it, closes predictably, and returns focus to a sensible control. These details reduce guesswork for users, tests, and agents, while also making failure evidence far easier to interpret. [AR4]
- Inspect names: List computed accessible names and descriptions for every control in the critical task.
- Inspect state: Capture expanded, selected, checked, invalid, disabled, and busy values before and after interaction.
- Test repeated action: Verify loading and completion feedback prevent duplicate or uncertain submission.
Build forms that explain input and recovery
Associate every input with a persistent label and use the correct type and autocomplete purpose where applicable. Explain required format and why sensitive information is requested before the user submits. Group related choices with fieldset and legend. Mark required fields programmatically and visually. Keep the entered value after validation whenever it is safe. An error should identify the field, describe the violated constraint, and suggest a repair instead of presenting an undifferentiated invalid message. [AR3][AR11]
After validation, move focus or announce the summary in a way that helps recovery without disorienting the user. Link summary items to fields and ensure each field exposes its own error. For asynchronous checks, communicate busy and final state without a rapid stream of announcements. On submission, distinguish preview from commit and expose consequence in the button label and confirmation. The accessible-forms guide in this resource library expands the pattern; here, the core rule is that input, state, error, and effect must all be understandable without visual inference. [AR3][AR4]
- Complete empty: Submit the safe test form empty and verify required fields and recovery order.
- Complete wrong: Enter each invalid format and confirm the message names the problem and preserves other valid input.
- Stop before consequence: Ensure the preview or confirmation makes the external effect explicit before it occurs.
Handle dynamic content, focus, and announcements
Single-page navigation, lazy panels, toasts, and inline updates can alter the task without a full page load. Decide what should receive focus, what should be announced, and what should remain quiet. When navigation replaces primary content, update the document title and move focus to a meaningful heading or preserve focus according to the product pattern. When an operation succeeds, provide durable visible status in addition to any transient toast. Avoid moving focus for background updates that the user did not request. [AR3][AR4]
Live regions should communicate concise state changes, not mirror entire containers or repeatedly announce timers. Insert the region before the update when the platform pattern requires it. Keep error and success messages in the DOM long enough to inspect and understand. For loading, communicate busy state on the relevant region or control and provide a timeout or recovery path when failure is possible. Browser agents benefit because the page exposes a clear state transition rather than requiring timing guesses. [AR3][AR4]
- Trigger each update: Open overlays, change tabs, load results, submit safely, and provoke a handled failure.
- Track focus: Record the focused element before action, during overlays, after completion, and after dismissal.
- Review announcements: Use accessibility tools or a screen reader to ensure updates are concise, timely, and not duplicated.
Keep automation hints subordinate to user semantics
A data-testid or product-specific data-agent attribute can provide a stable engineering hook when labels vary by locale, repeated controls need a durable identifier, or a test must avoid coupling to presentation. It should not replace an accessible name, native role, or safe state model. Choose a documented naming convention, avoid values derived from personal data, and keep identifiers stable across cosmetic refactors but review them when the product meaning changes. Remove abandoned hooks so they do not imply a contract the component no longer meets. [AR3][AR7][AR9]
Prefer resilient selectors in this order: user-visible role and name when that reflects the task, a stable semantic relationship, then a dedicated test hint when engineering needs isolation. Never identify destructive operations only by a generic test ID; the interface and server still need authorization and confirmation. Treat public hints as public metadata. They may reveal component purpose, so do not encode account identifiers, internal secrets, or privileged workflow details. [AR7][AR8]
- Define the gap: Explain why role, name, and relationship are insufficient before adding a dedicated hint.
- Name by stable purpose: Use a terse product concept rather than layout position, generated copy, or sensitive value.
- Test semantics too: Keep accessibility and consequence assertions even when the automation selector uses a dedicated attribute.
Protect consequential actions from indirect instructions
Semantic clarity can make actions easier to locate; it must not make them easier to abuse. Treat page content, uploaded files, and external text as untrusted input that can contain instructions aimed at an agent. Keep policy and authorization outside that content. Mark high-consequence controls with clear visible language, require fresh confirmation, validate identity and target on the server, and return a receipt. An agent should be able to identify delete, publish, pay, invite, and permission-change operations before invoking them. [AR7][AR8]
Test refusal and safe stopping as first-class outcomes. A fixture can include a page instruction that asks automation to ignore scope, reveal a credential, or submit without review. The correct result is to stop, preserve state, and request authorization. Avoid logging the malicious payload or sensitive form values in analytics. Secret scanning and repository controls help reduce accidental exposure, but runtime validation and least privilege remain necessary. [AR7][AR8]
- Classify consequence: Label operations as read-only, reversible, externally visible, financial, permission-changing, or destructive.
- Place the gate: Show target and effect, request intentional approval, enforce permission server-side, and prevent duplicate execution.
- Test refusal: Present an out-of-scope or injected instruction and confirm the workflow stops without leaking or acting.
Turn patterns into deterministic release checks
Add tests at the lowest useful layer. Component tests can assert element choice, name, associated label, and state transitions. Integration tests can verify validation and consequence boundaries. Browser tests can complete the user task with role-and-name selectors, capture accessibility snapshots, and confirm focus behavior. Static checks can find missing headings, broken internal links, invalid JSON-LD, or duplicate IDs, but interpret heuristic findings in the context of the actual page. [AR3][AR9]
Run a production build and preview crawl because server rendering, middleware, redirects, and headers can differ from the component test. Fetch the page without JavaScript, confirm the meaningful body and canonical, then run keyboard and agent task fixtures. Store the trace for failures. A passing test should describe the user outcome and prohibited regression, not merely assert an implementation detail. When a custom control is hard to test, use that friction as evidence that a simpler native design may be better. [AR2][AR7][AR9]
- Unit: Assert element, name, description, state, and keyboard transition for shared controls.
- Journey: Complete find, navigate, extract, safe fill, recovery, and refusal tasks in a production-like browser.
- Preview: Verify server-rendered content, canonical, sitemap membership, MIME types, and the real CTA before merge.
A remediation worksheet for one critical page
Create one row per failure with page, task step, observed behavior, expected behavior, affected users or tools, consequence, frequency, evidence, likely component, and verification command. Group rows by shared root cause. Fix an unlabeled design-system button once rather than patching ten pages. Repair server-rendered content or route generation before adding an experimental discovery file. Address unsafe consequence before improving convenience. This order keeps semantic work connected to user and security outcomes. [AR7][AR9]
After each fix, rerun the exact baseline and record the changed result. Keep unresolved limits visible. Browser and assistive-technology behavior varies, and automated checks cannot replace testing with people. Likewise, an agent completing one fixture does not establish universal compatibility. Publish only what the evidence supports: the task, environment, date, result, and known gaps. AgentReady can identify an initial set of semantic and browser signals, but the engineering team remains responsible for verifying the page's real behavior. [AR3][AR7]
- Evidence: Attach the URL, DOM or accessibility snapshot, keyboard steps, browser trace, and exact failure message.
- Repair: Name the shared component or content source, expected semantic contract, and safety constraints.
- Proof: Run the deterministic test, production build, preview fetch, and the original task; keep the before-and-after result.
Conclusion
Semantic HTML earns reliability by making purpose and state explicit. Start with native structure and controls, add ARIA only as part of a complete interaction pattern, and use dedicated automation hints only where they solve a documented engineering need. Keep forms recoverable, dynamic state observable, and consequential operations gated. Then turn the critical user tasks into tests that survive visual redesign. The result helps keyboard and assistive-technology users first, while also giving browser agents and automated tests a far stronger contract than a screen coordinate or a guessed selector.
Sources
Primary and official sources checked July 13, 2026. Recheck current versions before relying on time-sensitive requirements.
- AR1: Google robots.txt specification — Google Search Central
- AR2: Sitemaps XML format — Sitemaps.org
- AR3: Web Content Accessibility Guidelines 2.2 — W3C
- AR4: ARIA Authoring Practices Guide — W3C Web Accessibility Initiative
- AR5: Schema.org vocabulary — Schema.org
- AR6: OpenAPI Specification — OpenAPI Initiative
- AR7: Secure Software Development Framework, SP 800-218 — NIST
- AR8: OWASP Top 10 for LLM Applications — OWASP
- AR9: GitHub Actions documentation — GitHub Docs
- AR11: HTML Living Standard — WHATWG