Semantic HTML in Blazor Server

Say what it is, not how it looks

How meaningful markup makes Blazor applications more accessible, maintainable, resilient, and understandable.

That last detail matters.

A polished Blazor application can look correct while still being difficult to navigate with a keyboard, confusing to assistive technology, hard for search engines to interpret, and unnecessarily complicated to maintain. Semantic HTML helps solve these problems by giving content a clear purpose.

Instead of describing how an element should look, semantic HTML describes what the element is.

<!-- Describes presentation through class names -->
<div class="top-navigation">...</div>
<div class="page-content">...</div>

<!-- Describes purpose through native elements -->
<nav aria-label="Primary navigation">...</nav>
<main>...</main>

Both versions can be styled identically. The second version also communicates meaning to the browser, accessibility APIs, search tools, and anyone maintaining the component later.

This article walks through the decisions demonstrated in the accompanying Blazor Server sample application.

What semantic HTML means

Semantic HTML uses elements according to the purpose of their content.

For example:

      <nav> identifies a group of navigation links.

      <main> contains the page's unique primary content.

      <article> represents a composition that can stand on its own.

      <section> groups content around a specific topic, normally with a heading.

      <aside> contains related information that is not essential to the main flow.

      <button> performs an action.

      <a href> navigates to another location.

A <div> is not inherently wrong. It is useful when an element exists only for layout, styling, or grouping and no more specific element fits. Problems begin when every part of the interface becomes a <div>, regardless of purpose.

That pattern is sometimes called "div soup." It makes the document's meaning dependent on class names and application code.

Why semantic HTML is important in Blazor

1. It improves accessibility by default

Browsers use semantic elements to build an accessibility tree. Screen readers and other assistive technologies use that tree to communicate an interface's structure, roles, names, and states.

When a Blazor layout contains real landmarks, a screen-reader user can move directly between navigation, main content, and supporting regions. When a control is a real <button>, it receives the expected keyboard behavior and is announced as a button without adding a custom role.

Consider an action implemented with a generic element:

<div class="button" @onclick="Save">Save</div>

This may respond to a mouse click, but it is not automatically focusable; it does not support the expected keyboard activation behavior, and it is not exposed as a button.

The native version is both shorter and more capable:

<button type="button" @onclick="Save">Save</button>

Semantic HTML does not eliminate the need for accessibility testing, but it gives every component a much stronger starting point.

2. It reduces custom code

Native HTML includes behavior that developers frequently rebuild with C#, JavaScript, and ARIA attributes.

The sample application uses <details> and <summary> for disclosure content:

<details>
    <summary>Why not build a custom accordion?</summary>
    <p>
        The details element already provides an expanded state,
        keyboard interaction, and a focusable trigger.
    </p>
</details>

No Blazor event handler is required. The control continues to work while a server circuit is reconnecting because the behavior belongs to the browser.

The same principle applies to labels, fieldsets, tables, progress indicators, meters, links, and buttons. Native elements are usually the smallest reliable implementation.

3. It makes components easier to understand

Razor components are read far more often than they are written. Meaningful elements make a component's structure visible without requiring the reader to trace CSS classes.

Compare these two blog-card components:

<div class="post">
    <div class="title">Semantic HTML in Blazor</div>
    <div class="meta">August 22, 2026</div>
    <div class="content">...</div>
    <div class="related-note">...</div>
</div>

<article>
    <header>
        <h2>Semantic HTML in Blazor</h2>
        <time datetime="2026-08-22">August 22, 2026</time>
    </header>

    <p>...</p>

    <aside>...</aside>
</article>

The second version communicates the relationship between the title, publication date, article body, and supporting note. It is easier to review and less dependent on naming conventions.

4. It creates a better document for search and automation

Search engines and other automated tools use document structure as one signal when interpreting a page. Clear headings, article boundaries, navigation regions, figures, captions, and machine-readable dates make content easier to understand.

Semantic HTML is not a shortcut to search rankings. It does, however, provide a clearer and more consistent representation of the content being indexed.

5. It makes the application more resilient

Blazor Server applications normally depend on an active server connection for interactive C# event handlers. Native HTML behavior does not have that dependency.

A link still navigates. A disclosure widget still opens. A labeled input still has an accessible name. A table still communicates its row and column relationships.

This is progressive enhancement in practice: start with a useful HTML document, then add Blazor behavior where application-specific interactivity is genuinely needed.

Start with a semantic application shell

The sample places site-wide landmarks in MainLayout.razor. This is the natural location because the layout already owns the content repeated across routes.

@inherits LayoutComponentBase

<a class="skip-link" href="#main-content">
    Skip to main content
</a>

<header class="site-header">
    <a class="brand" href="/" aria-label="Elemental home">
        Elemental
    </a>

    <nav aria-label="Primary navigation">
        <ul>
            <li><a href="/#principles">Principles</a></li>
            <li><a href="/#before-after">Examples</a></li>
            <li><a href="/#checklist">Checklist</a></li>
        </ul>
    </nav>
</header>

<main id="main-content" tabindex="-1">
    @Body
</main>

<footer class="site-footer">
    Built with Razor components and native HTML.
</footer>

This layout establishes several useful conventions:

      The skip link lets keyboard users bypass repeated navigation.

      The navigation landmark has an accessible name.

      The page has one visible <main> landmark.

      Repeated site content remains outside the main region.

      @Body supplies the unique content for the active route.

The tabindex="-1" on <main> allows it to receive programmatic focus without adding it to the normal tab order. This is useful for skip-navigation behavior and route-change focus management.

Build each page as a real document

Inside the layout, the sample's home page is an <article> because it is a complete field guide that could stand on its own. Its major topics are represented by labeled sections.

<article aria-labelledby="guide-title">
    <header>
        <p>A field guide for Blazor developers</p>
        <h1 id="guide-title">Semantic HTML in Blazor Server</h1>
        <p>...</p>
    </header>

    <section aria-labelledby="principles-title">
        <h2 id="principles-title">Start with meaning</h2>
        <p>...</p>
    </section>

    <section aria-labelledby="elements-title">
        <h2 id="elements-title">Choose by purpose</h2>
        <p>...</p>
    </section>

    <footer>
        Published <time datetime="2026-08-22">August 22, 2026</time>
    </footer>
</article>

The heading hierarchy is deliberate:

1.    The page has one <h1> describing its overall purpose.

2.    Each major topic uses an <h2>.

3.    Subtopics inside those sections use <h3> elements.

Heading levels should reflect nesting, not preferred font size. CSS controls presentation; HTML communicates structure.

Choose links and buttons by behavior

Links and buttons often look similar after styling, but they have different responsibilities.

Use a link when the result is navigation:

<a href="/articles/semantic-html">Read the article</a>

Use a button when the result is an action on the current page:

<button type="button" @onclick="OpenPreview">Open preview</button>

Do not choose between them based on appearance. A link can be styled like a prominent call to action, and a button can be visually subtle. The element should describe what activating it does.

Give structured data real relationships

Semantic HTML is particularly valuable for forms and tables because relationships are difficult to communicate through visual layout alone.

Group related form controls

Use a <fieldset> and <legend> when several controls answer one question:

<fieldset>
    <legend>Which layer should own meaning?</legend>

    <label>
        <input type="radio" name="meaning" value="html" />
        HTML structure
    </label>

    <label>
        <input type="radio" name="meaning" value="css" />
        CSS classes
    </label>
</fieldset>

The legend gives the group a shared name, while each label names an individual option.

Describe tables with headers and captions

<table>
    <caption>Common interface needs and their native elements</caption>
    <thead>
        <tr>
            <th scope="col">When you need</th>
            <th scope="col">Start with</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th scope="row">Navigation</th>
            <td><code>&lt;a href&gt;</code></td>
        </tr>
    </tbody>
</table>

The visual position of a cell may be obvious to a sighted reader. The caption, header cells, and scope attributes preserve those relationships when the table is read through assistive technology.

Use ARIA to fill gaps-not replace HTML

ARIA can describe roles, states, and relationships that native HTML cannot express. It is valuable when building advanced composite widgets or naming multiple landmarks of the same type.

It should not be the first tool used to recreate an element that already exists.

<!-- Avoid recreating a button -->
<div role="button" tabindex="0">Save</div>

<!-- Prefer the native element -->
<button type="button">Save</button>

Adding role="button" does not automatically add keyboard activation, form behavior, disabled behavior, or all browser conventions associated with a real button. Native HTML provides the complete package.

A practical rule is: use the correct native element first, then add ARIA only when the element needs information that HTML cannot provide by itself.

A five-minute semantic review for Blazor pages

Before shipping a Razor component, perform this short review.

Read only the headings

Do the headings describe the page when read by themselves? Do their levels reflect the structure without skipping from <h1> to <h3>?

List the landmarks

Is there one main region? Does each navigation landmark have a useful name? Are supporting notes truly appropriate as asides?

Tab through the interface

Can every interactive element be reached? Is focus visible? Does each control have a clear name? Do links navigate and buttons perform actions?

Check the page without relying on CSS

Does the source order still make sense? Are relationships communicated through elements and labels rather than position, color, or class names alone?

Inspect the browser's accessibility tree

Confirm the roles, names, states, heading levels, and landmark structure produced by the rendered application. Testing the Razor source is not enough; the browser's final interpretation is what users receive.

The takeaway

Semantic HTML is not an optional layer added after a Blazor application is complete. It is part of the component's design.

Choosing meaningful elements gives browsers more information, gives users more reliable behavior, and gives developers clearer code. It can reduce custom event handling, reduce unnecessary ARIA, improve resilience during server reconnects, and make every Razor component easier to understand.

Blazor gives us a powerful way to build web applications with C#. Semantic HTML ensures the result is still a strong web document.

The best question to ask before adding another element is simple:

What is this content, and which HTML element already describes it?

Start with that answer. Add CSS for appearance and Blazor for application behavior. Let HTML carry the meaning.


[source code]

Comments

Popular posts from this blog

Customizing PWA Manifest and Icons for a Polished User Experience 🚀

Offline-First Strategy with Blazor PWAs: A Complete Guide 🚀

Yes, Blazor Server can scale!