Overview
Semantic HTML refers to the practice of using HTML markup to reinforce the meaning of content rather than merely defining its presentation. Each semantic element describes the type of content it contains, creating a structured document that both humans and machines can interpret without relying on visual styling.
The concept emerged as web standards evolved beyond the presentation-focused HTML of the 1990s. HTML5 introduced numerous semantic elements like <article>, <section>, <nav>, and <aside> to replace the generic <div> elements that previously dominated web markup. This shift separated content structure from visual presentation, aligning with the CSS separation of concerns principle.
Semantic HTML serves three primary audiences. Screen readers and assistive technologies parse semantic elements to construct an accessibility tree, enabling users to navigate content efficiently. Search engines interpret semantic markup to understand page structure and content hierarchy, influencing search rankings. Human developers benefit from self-documenting code where element names indicate content purpose.
<!-- Non-semantic approach -->
<div class="header">
<div class="nav">
<div class="nav-item">Home</div>
</div>
</div>
<!-- Semantic approach -->
<header>
<nav>
<a href="/">Home</a>
</nav>
</header>
The semantic version requires no class names to convey structure. The <header> element explicitly identifies page header content. The <nav> element marks navigation functionality. Assistive technologies recognize these elements and provide appropriate navigation shortcuts to users.
Consider a blog post structure. Non-semantic markup might use nested <div> elements with descriptive class names. Semantic markup uses <article> for the post, <header> for metadata, <time> for publication date, and <section> for content segments. This structure creates machine-readable metadata that search engines index and screen readers announce.
Key Principles
Semantic HTML operates on the principle that markup should describe content meaning rather than appearance. Every element selection should answer "what is this content?" not "how should this content look?" Visual presentation belongs in CSS, while HTML defines document structure and meaning.
Document structure follows a hierarchical organization where elements nest to create parent-child relationships. The <html> root contains <head> and <body>. The body contains sectioning elements like <header>, <main>, <aside>, and <footer>. These elements group related content and establish document landmarks.
Heading elements (<h1> through <h6>) create a document outline that represents content hierarchy. Each heading level indicates relative importance and nesting. Screen readers generate navigation menus from heading structure, allowing users to skip between sections. Search engines weight content based on heading hierarchy, treating <h1> content as more significant than <h6>.
<article>
<h1>Main Article Title</h1>
<section>
<h2>First Section</h2>
<p>Section content...</p>
<section>
<h3>Subsection</h3>
<p>Subsection content...</p>
</section>
</section>
<section>
<h2>Second Section</h2>
<p>Section content...</p>
</section>
</article>
The accessibility tree represents the semantic structure browsers expose to assistive technologies. When a browser parses HTML, it constructs a DOM tree and derives an accessibility tree containing only semantically meaningful nodes. Elements like <div> and <span> typically do not appear in this tree unless given explicit ARIA roles. Semantic elements automatically provide appropriate roles, names, and properties.
Sectioning content divides documents into logical regions. The <article> element represents self-contained content that makes sense independently, like blog posts or news articles. The <section> element groups thematically related content, typically with a heading. The <aside> element contains tangentially related content, like sidebars or pull quotes. The <nav> element marks navigation links.
Landmark roles emerge from certain semantic elements. The <header> element creates a banner landmark when it's a direct child of <body>. The <main> element creates a main content landmark. The <nav> element creates a navigation landmark. These landmarks enable assistive technology users to jump directly to major page regions without traversing intervening content.
Text-level semantics provide meaning for inline content. The <strong> element indicates importance, urgency, or seriousness, while <em> indicates stress emphasis. The <mark> element highlights relevant text. The <time> element represents dates and times in a machine-readable format. The <abbr> element defines abbreviations with full expansions.
<p>
The meeting is scheduled for
<time datetime="2025-10-15T14:00">October 15th at 2:00 PM</time>.
This is <strong>extremely important</strong> for all team members.
We will discuss <abbr title="Application Programming Interface">API</abbr> design.
</p>
Form semantics establish relationships between controls and labels. The <label> element associates descriptive text with form controls, improving accessibility and expanding click targets. The <fieldset> element groups related controls, while <legend> provides group descriptions. The <output> element displays calculation results.
Ruby Implementation
Ruby web frameworks generate HTML through templating systems that abstract HTML creation. Rails provides view helpers that produce semantic markup, while templating engines like ERB, HAML, and Slim offer different syntaxes for HTML generation. Understanding how these tools create semantic HTML ensures generated markup maintains proper structure.
Rails view helpers encapsulate semantic patterns in reusable methods. The content_tag helper generates arbitrary HTML elements with proper escaping and attribute handling. Rather than interpolating strings, view helpers produce safe HTML that respects semantic structure.
# Generating semantic structure with content_tag
content_tag :article, class: 'blog-post' do
concat content_tag(:header) {
content_tag :h1, @post.title
}
concat content_tag(:section) {
content_tag :p, @post.body
}
concat content_tag(:footer) {
content_tag :time, @post.published_at, datetime: @post.published_at.iso8601
}
end
The tag helper creates void elements (self-closing tags) like <br>, <img>, and <input>. It ensures proper formatting without requiring closing tags. Combined with content_tag, it covers all HTML generation needs while maintaining semantic correctness.
Rails form helpers generate semantic form markup automatically. The form_with helper creates forms with proper <label> associations. Text fields, select boxes, and other inputs receive appropriate semantic attributes.
<%= form_with model: @article, local: true do |form| %>
<fieldset>
<legend>Article Details</legend>
<%= form.label :title %>
<%= form.text_field :title, required: true %>
<%= form.label :published_at, "Publication Date" %>
<%= form.datetime_field :published_at %>
<%= form.label :category_id, "Category" %>
<%= form.collection_select :category_id, Category.all, :id, :name,
prompt: "Select a category" %>
</fieldset>
<%= form.submit "Publish Article" %>
<% end %>
HAML provides indentation-based syntax that naturally enforces proper nesting. Its concise notation reduces boilerplate while maintaining semantic clarity. HAML's structure mirrors the semantic hierarchy visually through indentation.
# HAML semantic structure
%article.blog-post
%header
%h1= @post.title
%p.byline
By
%span.author= @post.author
on
%time{ datetime: @post.published_at.iso8601 }= @post.published_at.strftime("%B %d, %Y")
%section.content
= simple_format @post.body
%aside.related
%h2 Related Articles
%ul
- @post.related.each do |related|
%li
%a{ href: article_path(related) }= related.title
%footer
%p.tags
- @post.tags.each do |tag|
%span.tag= tag.name
Slim offers even more concise syntax while preserving semantic structure. Its Ruby-like appearance integrates naturally with Rails views. Slim enforces semantic HTML through its syntax constraints, making it difficult to create invalid nesting.
# Slim semantic structure
article.blog-post
header
h1 = @post.title
nav.article-nav
a href=edit_article_path(@post) Edit
a href=article_path(@post) data-method="delete" Delete
section.content
== @post.body_html
footer
p
| Published
time datetime=@post.published_at.iso8601
= @post.published_at.strftime("%B %d, %Y")
Rails content helpers generate semantic patterns for common scenarios. The link_to helper creates proper anchor tags with accessible text. The image_tag helper requires alt text for accessibility compliance. These helpers make it easier to create semantically correct markup than to create incorrect markup.
# Semantic navigation with Rails helpers
<nav aria-label="Main navigation">
<ul>
<li><%= link_to "Home", root_path, aria: { current: "page" } if current_page?(root_path) %></li>
<li><%= link_to "Articles", articles_path %></li>
<li><%= link_to "About", about_path %></li>
</ul>
</nav>
# Semantic image with required alt text
<figure>
<%= image_tag @product.image_url, alt: @product.image_description %>
<figcaption><%= @product.image_caption %></figcaption>
</figure>
View components encapsulate semantic patterns in reusable objects. A component might render an article card with consistent semantic structure across the application. This approach centralizes semantic decisions and prevents inconsistent markup.
# app/components/article_card_component.rb
class ArticleCardComponent < ViewComponent::Base
def initialize(article:)
@article = article
end
def call
tag.article(class: "article-card") do
safe_join([
render_header,
render_content,
render_footer
])
end
end
private
def render_header
tag.header do
tag.h2 { link_to @article.title, article_path(@article) }
end
end
def render_content
tag.section(class: "excerpt") do
tag.p @article.excerpt
end
end
def render_footer
tag.footer do
tag.time @article.published_at.strftime("%B %d, %Y"),
datetime: @article.published_at.iso8601
end
end
end
Practical Examples
Blog post markup demonstrates semantic structure for article content. The document uses <article> as the container, <header> for metadata, <section> for content blocks, and <aside> for tangential information. Each element choice reflects content purpose rather than visual layout.
<article itemscope itemtype="http://schema.org/BlogPosting">
<header>
<h1 itemprop="headline">Understanding Database Indexing</h1>
<p class="byline">
By <span itemprop="author">Sarah Chen</span>
on <time itemprop="datePublished" datetime="2025-10-10">October 10, 2025</time>
</p>
<p itemprop="description">
An exploration of how database indexes improve query performance
and the trade-offs involved in index design.
</p>
</header>
<section>
<h2>What Are Indexes?</h2>
<p>
Database indexes create additional data structures that enable
faster data retrieval at the cost of additional storage and
slower write operations.
</p>
</section>
<section>
<h2>Types of Indexes</h2>
<section>
<h3>B-Tree Indexes</h3>
<p>
B-tree indexes maintain sorted data in a tree structure,
supporting efficient range queries and ordered retrieval.
</p>
</section>
<section>
<h3>Hash Indexes</h3>
<p>
Hash indexes provide O(1) lookup for exact match queries
but cannot support range operations.
</p>
</section>
</section>
<aside>
<h2>Performance Tip</h2>
<p>
Index columns used in WHERE clauses, JOIN conditions,
and ORDER BY clauses to maximize query performance.
</p>
</aside>
<footer>
<p>
Tags:
<a href="/tags/databases" rel="tag">databases</a>,
<a href="/tags/performance" rel="tag">performance</a>
</p>
</footer>
</article>
The article uses microdata attributes (itemscope, itemtype, itemprop) to enhance semantic meaning with structured data. Search engines parse this markup to generate rich search results. The nested sections create a document outline that screen readers navigate hierarchically.
E-commerce product pages require semantic markup for product information, pricing, availability, and user reviews. Proper semantic structure enables rich snippets in search results and improves accessibility for users comparing products.
<article itemscope itemtype="http://schema.org/Product">
<header>
<h1 itemprop="name">Professional Noise-Canceling Headphones</h1>
</header>
<figure>
<img src="headphones.jpg"
alt="Black over-ear headphones with silver accents"
itemprop="image">
<figcaption>Available in black, silver, and navy</figcaption>
</figure>
<section>
<h2>Product Details</h2>
<p itemprop="description">
Premium wireless headphones with active noise cancellation,
30-hour battery life, and studio-quality audio.
</p>
<dl>
<dt>Weight</dt>
<dd itemprop="weight">250 grams</dd>
<dt>Battery Life</dt>
<dd>30 hours</dd>
<dt>Connectivity</dt>
<dd>Bluetooth 5.2, 3.5mm jack</dd>
</dl>
</section>
<section itemprop="offers" itemscope itemtype="http://schema.org/Offer">
<h2>Pricing</h2>
<p>
<data itemprop="price" value="299.99">$299.99</data>
<link itemprop="availability" href="http://schema.org/InStock">
<span class="availability">In Stock</span>
</p>
<form action="/cart" method="post">
<label for="quantity">Quantity:</label>
<input type="number" id="quantity" name="quantity"
min="1" max="10" value="1">
<button type="submit">Add to Cart</button>
</form>
</section>
<section>
<h2>Customer Reviews</h2>
<article itemprop="review" itemscope itemtype="http://schema.org/Review">
<header>
<h3 itemprop="name">Excellent sound quality</h3>
<p>
By <span itemprop="author">Mike Johnson</span>
on <time itemprop="datePublished" datetime="2025-09-15">September 15, 2025</time>
</p>
<p>
Rating:
<data itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating">
<span itemprop="ratingValue">5</span>/
<span itemprop="bestRating">5</span>
</data>
</p>
</header>
<p itemprop="reviewBody">
These headphones exceeded my expectations. The noise cancellation
works remarkably well, and the audio quality rivals studio monitors.
</p>
</article>
</section>
</article>
Application dashboards require semantic structure for navigation, data tables, and interactive controls. Proper landmark roles help assistive technology users navigate complex interfaces efficiently.
<body>
<header role="banner">
<h1>Analytics Dashboard</h1>
<nav aria-label="Account menu">
<ul>
<li><a href="/profile">Profile</a></li>
<li><a href="/settings">Settings</a></li>
<li><a href="/logout">Logout</a></li>
</ul>
</nav>
</header>
<nav aria-label="Main navigation">
<ul>
<li><a href="/dashboard" aria-current="page">Dashboard</a></li>
<li><a href="/reports">Reports</a></li>
<li><a href="/users">Users</a></li>
</ul>
</nav>
<main>
<h2>Overview</h2>
<section aria-labelledby="metrics-heading">
<h3 id="metrics-heading">Key Metrics</h3>
<div class="metric-cards">
<article>
<h4>Total Users</h4>
<p><data value="15234">15,234</data></p>
<p class="change">
<span aria-label="Increased by">↑</span>
<data value="8.3">8.3%</data> from last month
</p>
</article>
<article>
<h4>Revenue</h4>
<p><data value="432890">$432,890</data></p>
<p class="change">
<span aria-label="Increased by">↑</span>
<data value="12.5">12.5%</data> from last month
</p>
</article>
</div>
</section>
<section aria-labelledby="activity-heading">
<h3 id="activity-heading">Recent Activity</h3>
<table>
<caption class="sr-only">Recent user activity log</caption>
<thead>
<tr>
<th scope="col">User</th>
<th scope="col">Action</th>
<th scope="col">Time</th>
</tr>
</thead>
<tbody>
<tr>
<td>Sarah Chen</td>
<td>Created new report</td>
<td><time datetime="2025-10-10T14:32">2:32 PM</time></td>
</tr>
<tr>
<td>Mike Johnson</td>
<td>Updated user permissions</td>
<td><time datetime="2025-10-10T14:28">2:28 PM</time></td>
</tr>
</tbody>
</table>
</section>
</main>
<aside aria-labelledby="notifications-heading">
<h3 id="notifications-heading">Notifications</h3>
<ul>
<li>
<article>
<h4>System Update</h4>
<p>Scheduled maintenance tonight at <time datetime="2025-10-10T23:00">11:00 PM</time></p>
</article>
</li>
</ul>
</aside>
<footer role="contentinfo">
<p>© 2025 Analytics Platform. <a href="/privacy">Privacy Policy</a></p>
</footer>
</body>
Security Implications
Semantic HTML affects security through its interaction with content sanitization, XSS prevention, and accessibility features. Improper handling of semantic elements creates attack vectors that compromise user data and system integrity.
Cross-site scripting attacks exploit insufficient input sanitization. When user-generated content renders without proper escaping, attackers inject malicious scripts. Semantic elements do not inherently prevent XSS, but frameworks that generate semantic HTML typically include automatic escaping.
# Vulnerable: Direct interpolation without escaping
<article>
<h1><%= @post.title %></h1>
<section>
<%= @post.body %> # XSS vulnerability if body contains scripts
</section>
</article>
# Secure: Automatic escaping with ERB
<article>
<h1><%= @post.title %></h1> # Automatically escaped
<section>
<%== sanitize @post.body, tags: %w[p strong em a],
attributes: %w[href] %>
</section>
</article>
Rails sanitize helper strips dangerous HTML while preserving semantic structure. It removes script tags, event handlers, and dangerous attributes while allowing safe semantic elements. Configuration controls which elements and attributes pass through sanitization.
Content Security Policy headers restrict resource loading to prevent injected scripts from executing. CSP directives specify allowed script sources, preventing inline scripts from running even if they bypass HTML sanitization.
# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
policy.default_src :self
policy.script_src :self, :https
policy.style_src :self, :https, :unsafe_inline
policy.img_src :self, :https, :data
policy.font_src :self, :https, :data
policy.connect_src :self, :https
end
ARIA attributes create security concerns when misused. The aria-label and aria-describedby attributes accept arbitrary text that screen readers announce. If this text contains user input without sanitization, attackers might manipulate screen reader output to deceive users.
<!-- Vulnerable: Unsanitized user input in ARIA -->
<button aria-label="<%= @action.name %>">
Execute Action
</button>
<!-- Secure: Sanitized or validated input -->
<button aria-label="<%= sanitize(@action.name, tags: []) %>">
Execute Action
</button>
Link relationships affect security through the rel attribute. The rel="noopener noreferrer" combination prevents malicious sites accessed through links from accessing the referring page's window object. This protection matters for user-generated content containing external links.
<!-- Vulnerable: Opens security hole for target page -->
<a href="<%= @link.url %>" target="_blank">
<%= @link.title %>
</a>
<!-- Secure: Prevents window.opener access -->
<a href="<%= @link.url %>" target="_blank" rel="noopener noreferrer">
<%= @link.title %>
</a>
Form security depends on proper semantic structure combined with server-side validation. The autocomplete attribute controls whether browsers store sensitive information. Forms handling passwords, credit cards, or personal data should disable autocomplete for those specific fields.
<form action="/payment" method="post">
<fieldset>
<legend>Payment Information</legend>
<label for="card-number">Card Number</label>
<input type="text"
id="card-number"
name="card_number"
autocomplete="cc-number"
inputmode="numeric"
pattern="[0-9]{13,19}"
required>
<label for="cvv">CVV</label>
<input type="text"
id="cvv"
name="cvv"
autocomplete="cc-csc"
inputmode="numeric"
pattern="[0-9]{3,4}"
required>
</fieldset>
<%= hidden_field_tag :authenticity_token, form_authenticity_token %>
<button type="submit">Process Payment</button>
</form>
The form includes CSRF token protection through Rails authenticity token. This semantic pattern combines with server-side validation to prevent form submission forgery. The autocomplete attributes use standardized values that browsers recognize for secure password manager integration.
Design Considerations
Element selection determines document structure and semantic meaning. Each HTML element carries implicit semantics that affect accessibility, SEO, and code maintainability. Choosing between elements requires understanding their semantic purposes and use cases.
The <div> versus semantic element decision depends on whether content has inherent meaning. Use <div> only for purely presentational grouping without semantic significance. Use semantic elements like <article>, <section>, <nav>, or <aside> when content serves a specific purpose.
<!-- Appropriate div use: purely presentational wrapper -->
<article>
<h1>Article Title</h1>
<div class="columns"> <!-- Layout container only -->
<section>Content column one</section>
<section>Content column two</section>
</div>
</article>
<!-- Inappropriate div use: semantic meaning present -->
<div class="blog-post"> <!-- Should be <article> -->
<div class="post-header"> <!-- Should be <header> -->
<div class="post-title">Title</div> <!-- Should be <h1> -->
</div>
</div>
Heading hierarchy requires careful planning. Each page should have exactly one <h1> representing the primary topic. Subsequent headings follow numerical order without skipping levels. Heading structure creates the document outline that assistive technologies use for navigation.
<!-- Correct heading hierarchy -->
<article>
<h1>Web Application Security</h1>
<section>
<h2>Authentication</h2>
<section>
<h3>Password Storage</h3>
<p>Content...</p>
</section>
<section>
<h3>Session Management</h3>
<p>Content...</p>
</section>
</section>
<section>
<h2>Authorization</h2>
<p>Content...</p>
</section>
</article>
<!-- Incorrect: skips heading levels -->
<article>
<h1>Web Application Security</h1>
<h4>Password Storage</h4> <!-- Skips h2 and h3 -->
</article>
The <section> versus <article> distinction hinges on content independence. Use <article> for self-contained content that makes sense when syndicated or shared independently. Use <section> for thematic groupings within a larger context. Articles can contain sections, and sections can contain articles.
<!-- Article containing sections -->
<article>
<h1>Database Design Patterns</h1>
<section>
<h2>Normalization</h2>
<p>Content about normalization...</p>
</section>
<section>
<h2>Denormalization</h2>
<p>Content about denormalization...</p>
</section>
</article>
<!-- Section containing articles (blog index) -->
<section>
<h1>Recent Posts</h1>
<article>
<h2>First Post Title</h2>
<p>Excerpt...</p>
</article>
<article>
<h2>Second Post Title</h2>
<p>Excerpt...</p>
</article>
</section>
Button versus link semantics reflect different interaction patterns. Links navigate to different URLs or page locations. Buttons trigger actions, submit forms, or modify interface state. Misusing buttons as links breaks keyboard navigation and confuses screen reader users.
<!-- Correct: Link for navigation -->
<a href="/articles/123">Read More</a>
<!-- Correct: Button for action -->
<button type="button" onclick="toggleComments()">Show Comments</button>
<!-- Incorrect: Button for navigation -->
<button onclick="location.href='/articles/123'">Read More</button>
<!-- Incorrect: Link for action -->
<a href="#" onclick="toggleComments()">Show Comments</a>
List semantics distinguish between ordered, unordered, and description lists. Ordered lists (<ol>) represent sequences where order matters. Unordered lists (<ul>) represent sets where order is arbitrary. Description lists (<dl>) represent term-definition pairs.
<!-- Ordered list: steps in sequence -->
<ol>
<li>Clone the repository</li>
<li>Install dependencies</li>
<li>Run the test suite</li>
</ol>
<!-- Unordered list: no inherent order -->
<ul>
<li>Ruby</li>
<li>JavaScript</li>
<li>Python</li>
</ul>
<!-- Description list: term-definition pairs -->
<dl>
<dt>API</dt>
<dd>Application Programming Interface</dd>
<dt>REST</dt>
<dd>Representational State Transfer</dd>
</dl>
Table usage requires genuine tabular data. Tables represent relationships between row and column headers. Using tables for layout creates accessibility problems because screen readers interpret content as data relationships. Use CSS Grid or Flexbox for layout instead.
<!-- Correct: actual tabular data -->
<table>
<caption>Server Response Times</caption>
<thead>
<tr>
<th scope="col">Endpoint</th>
<th scope="col">Avg Response (ms)</th>
<th scope="col">95th Percentile (ms)</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">/api/users</th>
<td>45</td>
<td>120</td>
</tr>
<tr>
<th scope="row">/api/posts</th>
<td>67</td>
<td>180</td>
</tr>
</tbody>
</table>
Common Pitfalls
Semantic element misuse creates accessibility problems and confuses search engines. Developers frequently misapply semantic elements based on visual design rather than content meaning. This pattern produces markup that looks correct but fails accessibility requirements.
Using multiple <main> elements on a single page violates HTML specifications. Each page should have exactly one main landmark representing primary content. Multiple main elements confuse screen readers attempting to navigate to main content.
<!-- Incorrect: multiple main elements -->
<main>
<h1>Page Title</h1>
<p>Introduction</p>
</main>
<main>
<h2>Content Section</h2>
<p>More content</p>
</main>
<!-- Correct: single main with sections -->
<main>
<h1>Page Title</h1>
<p>Introduction</p>
<section>
<h2>Content Section</h2>
<p>More content</p>
</section>
</main>
Heading order violations break document structure. Developers skip heading levels to achieve desired font sizes, destroying the semantic outline. This forces screen reader users to navigate incomplete heading structures.
<!-- Incorrect: skips h2 for styling -->
<article>
<h1>Main Title</h1>
<h3>Subtitle</h3> <!-- Skips h2 -->
<p>Content</p>
</article>
<!-- Correct: use h2, adjust CSS -->
<article>
<h1>Main Title</h1>
<h2 class="subtitle">Subtitle</h2> <!-- Proper hierarchy -->
<p>Content</p>
</article>
Empty link text and buttons harm accessibility. Assistive technologies announce link purpose from link text. Using "click here" or "read more" provides no context about destination or action.
<!-- Incorrect: meaningless link text -->
<p>
To learn about database indexing,
<a href="/articles/indexing">click here</a>.
</p>
<!-- Correct: descriptive link text -->
<p>
Learn about <a href="/articles/indexing">database indexing strategies</a>.
</p>
<!-- Incorrect: icon-only button -->
<button type="button">
<svg>...</svg>
</button>
<!-- Correct: accessible icon button -->
<button type="button" aria-label="Delete item">
<svg aria-hidden="true">...</svg>
</button>
Excessive ARIA attributes override native semantics. Adding ARIA roles to semantic elements creates redundancy or conflicts. The first rule of ARIA states: use native HTML elements instead of adding ARIA when possible.
<!-- Incorrect: redundant ARIA on semantic element -->
<nav role="navigation" aria-label="navigation">
<ul role="list">
<li role="listitem">
<a href="/" role="link">Home</a>
</li>
</ul>
</nav>
<!-- Correct: native semantics without redundant ARIA -->
<nav aria-label="Main">
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
Misusing sectioning elements for styling breaks document structure. The <section> element requires a heading to establish its place in the document outline. Using sections as styling hooks without headings creates invalid structure.
<!-- Incorrect: section without heading -->
<article>
<h1>Article Title</h1>
<section class="columns"> <!-- No heading, just layout -->
<p>Column one</p>
<p>Column two</p>
</section>
</article>
<!-- Correct: div for layout, section for semantic grouping -->
<article>
<h1>Article Title</h1>
<div class="columns">
<section>
<h2>First Topic</h2>
<p>Column one content</p>
</section>
<section>
<h2>Second Topic</h2>
<p>Column two content</p>
</section>
</div>
</article>
Form labels disconnected from inputs reduce accessibility and usability. Every form control requires an associated label. The label text announces the control's purpose to screen readers and expands the clickable area.
<!-- Incorrect: label without association -->
<label>Email Address</label>
<input type="email" name="email">
<!-- Correct: explicit label association -->
<label for="email">Email Address</label>
<input type="email" id="email" name="email">
<!-- Also correct: implicit association -->
<label>
Email Address
<input type="email" name="email">
</label>
Missing alternative text for images blocks screen reader users from understanding visual content. Every image requires alt text describing its content or purpose. Decorative images should have empty alt attributes to prevent screen readers from announcing filenames.
<!-- Incorrect: missing alt attribute -->
<img src="graph.png">
<!-- Correct: descriptive alt text -->
<img src="graph.png"
alt="Line graph showing 40% increase in user engagement from January to March">
<!-- Correct: empty alt for decorative image -->
<img src="decorative-border.png" alt="">
Table headers without scope attributes confuse relationships between data cells. The scope attribute indicates whether a header applies to a row, column, row group, or column group. Complex tables require scope or headers/id associations for accessibility.
<!-- Incorrect: headers without scope -->
<table>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
<tr>
<td>Sarah</td>
<td>32</td>
</tr>
</table>
<!-- Correct: explicit scope attributes -->
<table>
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">Age</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Sarah</th>
<td>32</td>
</tr>
</tbody>
</table>
Reference
Sectioning Elements
| Element | Purpose | Usage Guidelines |
|---|---|---|
| article | Self-contained content | Blog posts, news articles, user comments, independent widgets |
| section | Thematic content grouping | Chapter sections, tabs, parts of an article with headings |
| nav | Navigation links | Primary site navigation, pagination, table of contents |
| aside | Tangentially related content | Sidebars, pull quotes, related links, advertising |
| header | Introductory content | Page headers, article headers, section headers with metadata |
| footer | Footer content | Copyright, contact info, related links, document metadata |
| main | Primary content | Page main content, one per page, excludes repeated content |
| address | Contact information | Email, physical address, social links for article or page author |
Text-Level Semantics
| Element | Purpose | Usage Guidelines |
|---|---|---|
| strong | Strong importance | Critical warnings, important information requiring attention |
| em | Stress emphasis | Vocal stress, changing meaning through emphasis |
| mark | Highlighted relevance | Search results highlighting, referenced text in quotes |
| code | Computer code | Inline code snippets, variable names, file paths |
| kbd | Keyboard input | Keys user should press, keyboard shortcuts |
| samp | Program output | Terminal output, program results, log entries |
| var | Mathematical variable | Variables in equations, placeholders in code examples |
| time | Date or time | Publication dates, event times, deadlines |
| abbr | Abbreviation | Acronyms and abbreviations with title attribute for full form |
| cite | Citation title | Book titles, article titles, work titles being referenced |
| q | Inline quotation | Short quotes, automatically adds quotation marks |
| blockquote | Block quotation | Extended quotes, use cite attribute for source URL |
| del | Deleted content | Removed text in document revisions |
| ins | Inserted content | Added text in document revisions |
| sub | Subscript | Mathematical subscripts, chemical formulas |
| sup | Superscript | Mathematical exponents, ordinal indicators |
Form Elements
| Element | Purpose | Usage Guidelines |
|---|---|---|
| label | Control label | Required for all inputs, use for attribute or wrap input |
| fieldset | Control grouping | Related form controls, radio button groups, checkbox groups |
| legend | Fieldset caption | Describes purpose of fieldset, first child of fieldset |
| input | User input | Many types, always include type attribute and associated label |
| select | Dropdown selection | Multiple options, group with optgroup, requires label |
| textarea | Multi-line text | Long-form text input, specify rows and cols or use CSS |
| button | Clickable button | Always specify type: submit, reset, or button |
| output | Calculation result | Display calculation output, associate with form attribute |
| datalist | Input suggestions | Autocomplete options for input, use list attribute on input |
| progress | Progress indicator | Operation progress, use value and max attributes |
| meter | Scalar measurement | Disk usage, vote percentages, use value, min, max |
Heading Hierarchy
| Pattern | Structure | Use Case |
|---|---|---|
| Single section | h1 > p | Simple page with no subsections |
| Two levels | h1 > h2 > p | Page with major sections |
| Three levels | h1 > h2 > h3 > p | Complex page with nested sections |
| Article outline | article > h1, section > h2, section > h3 | Self-contained article structure |
| Nested articles | section > h1, article > h2 | Blog index with article previews |
Decision Matrix
| If Content Is | Use Element | Not Element |
|---|---|---|
| Self-contained, redistributable | article | section, div |
| Navigation links | nav | div, aside |
| Thematically grouped with heading | section | div |
| Tangentially related | aside | section |
| Primary page content | main | section, div |
| Introductory or metadata | header | section, div |
| Appendix or metadata | footer | section, div |
| Important emphasis | strong | b, span |
| Stress emphasis | em | i, span |
| User input data | input | div with contenteditable |
| Tabular relationships | table | div with CSS grid |
| Ordered steps or ranking | ol | ul, div |
| Unordered set | ul | ol, div |
| Term-definition pairs | dl, dt, dd | ul, table |
ARIA Landmark Roles
| HTML Element | Implicit Role | Additional Considerations |
|---|---|---|
| header | banner when in body | None otherwise, use aria-label for multiple headers |
| nav | navigation | Use aria-label to distinguish multiple nav elements |
| main | main | Only one per page, skip link target |
| aside | complementary | Use aria-labelledby for identification |
| footer | contentinfo when in body | None otherwise, use aria-label for multiple footers |
| section | region when labeled | Requires aria-labelledby or aria-label |
| form | form when labeled | Use aria-label or aria-labelledby for identification |
| search | search | Use role=search on form element |
Accessibility Attributes
| Attribute | Purpose | Example Values |
|---|---|---|
| aria-label | Accessible name | "Main navigation", "Close dialog" |
| aria-labelledby | Name from element ID | id="section-heading" |
| aria-describedby | Additional description | id="help-text" |
| aria-hidden | Hide from screen readers | true, false |
| aria-current | Current item indicator | page, step, location, date, time |
| role | Explicit role | button, link, navigation, main |
| aria-live | Dynamic content announcements | polite, assertive, off |
| aria-expanded | Expandable state | true, false |
| aria-controls | Controlled element ID | id="panel-1" |
Microdata Schema
| Property | Element Pattern | Purpose |
|---|---|---|
| itemscope | article, div | Defines item boundary |
| itemtype | article, div | Schema type URL from schema.org |
| itemprop | span, data, time, meta | Property name and value |
| Article schema | article with itemprop headline, author, datePublished | Blog post structured data |
| Product schema | article with itemprop name, price, availability | E-commerce product data |
| Review schema | article with itemprop reviewRating, author, reviewBody | User review structured data |
| Breadcrumb schema | ol with itemprop itemListElement | Navigation breadcrumb trail |