A card in an issue tracker opens the right page when you click it. The UI looks finished. Then someone presses Tab and cannot reach the card. The search field has a visible label, but a screen reader cannot associate that text with the input.
These are not styling problems. The application used elements that do not carry the behavior its interface suggests.
Semantic HTML is a choice about what the browser should understand and do. Elements such as a, button, form, and main communicate different contracts. CSS can make them look identical, but it cannot make their built-in behavior identical.
A small issue-tracker example makes the difference concrete.
Refactor the behavior, not just the tags
Consider this deliberately incomplete markup. It is a sketch of a common implementation pattern, not runnable application code: the JavaScript functions have not been defined.
<div class="search-bar">
<span class="label">Search issues</span>
<input placeholder="Search issues">
<div class="search-button" onclick="searchIssues()">Search</div>
</div>
<div class="issue" onclick="openIssue(482)">
Checkout retry fails
</div>
A pointer user may be able to operate both div elements after the handlers are implemented. But a plain div is not normally in the keyboard tab order, does not expose a button or link role, and does not implement their activation keys. A placeholder is not a substitute for a programmatically associated label.
Here is a better HTML starting point. The example assumes the application serves /issues with an optional q query parameter and individual issue pages at /issues/:id.
<main id="content">
<h1>Open issues</h1>
<form role="search" action="/issues" method="get">
<label for="issue-query">Search issues</label>
<input id="issue-query" name="q" type="search">
<button type="submit">Search</button>
</form>
<ul>
<li>
<a href="/issues/482">Checkout retry fails</a>
</li>
</ul>
</main>
Several behaviors now come from the platform rather than custom event code:
- The linked issue is available through
Taband normally activates withEnter. Its destination is visible in the markup, can be opened in a new tab, and is a crawlable URL. - The search control has a visible label associated through matching
forandid. Clicking that label can focus the input. - The submit button has the native button role and keyboard activation. The
formbuilds a GET request using the input'sname; the application still needs to handle that request and render the results. - The search form is identified as a search landmark. The
mainelement identifies the primary page content, andulcommunicates that the issues form a list.
This is a change in interaction semantics, not merely a prettier DOM. The native behaviors above are documented in MDN's accessibility guide, W3C's form-label guidance, and Google's link-crawling guidance.
The example does not implement a search service or demonstrate a browser test. It shows the markup contract a working issue tracker can build on.
Choose an element by the user's task
A useful review question is not "Can this be a semantic tag?" It is "What is this piece of UI supposed to do?"
| User's task or content | Start with | Check before shipping |
|---|---|---|
| Move to another resource or route | <a href="..."> | Is there a real destination, including when JavaScript is unavailable? |
| Trigger an action on the current page | <button type="button"> | Does the action have an accessible name and a useful focus outcome? |
| Submit information | <form>, <label>, and native controls | Are labels connected, and is the submit behavior intentional? |
| Show a collection | <ul> or <ol> with <li> | Is it actually a list, and does order matter? |
| Publish self-contained content | <article> | Would this item make sense on its own, outside its surrounding page? |
| Group related content under a theme | <section> with a heading where appropriate | Is it a meaningful section rather than a CSS wrapper? |
| Wrap elements only for layout or scripting | <div> | Is there really no more specific meaning to express? |
The last row matters. A div is not an error. The HTML standard explicitly recommends it when a container exists only for styling or scripting. Replacing every wrapper with section makes the document less meaningful, not more. The distinction between article and section also depends on whether the content stands alone or belongs to a larger whole. See the HTML Living Standard's sectioning guidance.
There is another practical trap: inside a form, a button without an explicit type can act as a submit button. Use type="button" for an action such as opening a filter panel, and type="submit" when submitting is the intent. This is defined in the HTML button reference.
And avoid turning a card into both a link and an action button by nesting interactive elements. If the issue title opens its page while a separate Watch button changes state, make them separate controls. That gives each action its own name, destination or handler, and keyboard behavior. For dynamic lists, the event-delegation guide shows how to handle button clicks without turning the whole row into a fake button.
Landmarks describe regions, not every container
Page structure involves a second distinction: a sectioning element is not always a landmark.
A typical document might have a page-level header, primary nav, one main, and page-level footer. These create recognizable regions that assistive technologies can expose for navigation. If the page has two navigation areas, name them so the purpose of each is clear:
<header>
<nav aria-label="Primary">
<a href="/issues">Issues</a>
<a href="/projects">Projects</a>
</nav>
</header>
<main>
<h1>Issue details</h1>
<nav aria-label="On this page">
<a href="#activity">Activity</a>
<a href="#history">History</a>
</nav>
<section id="activity" aria-labelledby="activity-heading">
<h2 id="activity-heading">Activity</h2>
<p>No recent updates.</p>
</section>
<section id="history">
<h2>History</h2>
<p>No changes recorded.</p>
</section>
</main>
<footer>
<p>Support portal</p>
</footer>
The two nav elements are both navigation landmarks but have distinct names. The Activity section is a named region because its aria-labelledby references the heading. The History section still organizes the document, but its heading does not automatically give it a region landmark name.
Also, a header nested inside an article does not become another page-level banner, nor does an article's footer become the page's content-info landmark. Context changes these semantics. The W3C landmark practices and page-region tutorial document the mappings and naming rules.
You do not need to add role="main" to a main element or role="navigation" to a nav element. Those roles are already implicit. Add an accessible label when it distinguishes otherwise similar regions, not just to decorate the accessibility tree.
Semantics are useful for search, but they are not a ranking shortcut
Semantic markup makes important content and links easier to interpret. Google advises developers to use semantic HTML where possible, and its link guidance explicitly favors real <a href> navigation over click handlers on unrelated elements.
That is a sound reason to care about markup. It is not evidence that changing a card from div to article gives the page a measurable ranking boost. Google's SEO Starter Guide even distinguishes logical heading order, which helps screen reader navigation, from how Google Search processes headings. There is no special heading count to optimize.
Likewise, semantic HTML alone does not make a page eligible for every rich-result treatment. Structured data has separate formats, policies, and eligibility requirements, and Google does not guarantee rich results even when eligible markup is present.
Treat search discoverability as one useful consequence of good HTML choices, not the test for whether those choices are correct.
How to review a page without assuming the markup worked
The code samples above are illustrative. For an implemented page, a review should examine both the DOM and its observable behavior.
- Use the keyboard. Start before the search field and press
Tabthrough the page. Can you reach the link and submit button? DoesEnterfollow the link? Can you activate the button with the expected keyboard keys? Is the focus indicator visible? - Inspect the accessibility tree. In browser DevTools, check whether the link, button, search region, main region, and named navigation areas expose the expected roles and names. This is more informative than counting semantic elements in the source.
- Exercise the form. Submit a query and inspect the resulting request or URL. The example promises
GET /issues?q=..., not a working search backend. Confirm the application actually implements that route, handles input, and reports errors. - Check the document structure. Scan headings in order, verify there is a clear primary content region, and make sure repeated landmarks can be distinguished. A list should contain real items, not arbitrary visual wrappers.
- Test with assistive technology when the feature matters. Correct HTML is a strong foundation, but it does not prove that focus order, status announcements, validation errors, or an entire workflow is accessible.
This is why a page can have excellent semantic markup and still fail users. JavaScript may move focus unpredictably, CSS may suppress outlines, or a form may never explain a validation failure. Those are additional implementation responsibilities, not reasons to abandon native elements.
The practical rule is small: choose the element that already does as much of the user's intended task as possible, then implement and test what remains. If an element only changes the page's appearance, a plain div may be the most accurate choice.
