HTML intermediate: semantic structure, forms, and accessibility

HTML · July 25, 2026
HTML intermediate: semantic structure, forms, and accessibility

If you read the beginner lesson, you can put together a page with headings, paragraphs, links, and images. Good. Now we are going to level up. This lesson is about writing HTML the way professional web developers do, HTML that is not just functional but also semantically meaningful, accessible to people using screen readers, and friendly to search engines.



What does "semantic" mean?


Semantic HTML means using the tag that best describes the role of the content, not just any tag that makes it look right. For example, if you have a navigation menu, the semantically correct tag is <nav>, not a generic <div> with some styling. The browser does not care, but a screen reader does. A screen reader will let a blind user jump directly to the navigation. A search engine does the same. Even a future developer maintaining the code will thank you.



HTML5 introduced a set of semantic tags that describe the most common parts of a webpage. Here are the ones you will use most often.



The page-level semantic tags



  • <header> — the top section of a page or a section. Usually contains the logo, primary navigation, and a search bar.

  • <nav> — a block of navigation links. You can have more than one, for example a primary nav in the header and a footer nav with legal links.

  • <main> — the unique main content of the page. Use it only once per page, and do not put sidebars or footers inside it.

  • <article> — a self-contained piece of content that could be syndicated on its own, like a blog post, a news article, a product card, or a forum reply.

  • <section> — a thematic grouping of content, usually with a heading. Use it to divide a long page into chapters.

  • <aside> — content that is tangentially related to the main content, like a sidebar, a pull quote, or a list of related links.

  • <footer> — the bottom of a page or section, usually with copyright, contact info, and a secondary navigation.



A realistic page skeleton


Here is how a professionally-structured blog post page might look. Notice how the tags describe what each region is, instead of just being a stack of unnamed <div> elements.



<code>&amp;lt;body&amp;gt;
&amp;lt;header&amp;gt;
&amp;lt;a href=&quot;/&quot;&amp;gt;&amp;lt;img src=&quot;logo.svg&quot; alt=&quot;Mangobaz&quot;&amp;gt;&amp;lt;/a&amp;gt;
&amp;lt;nav aria-label=&quot;Primary&quot;&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&quot;/&quot;&amp;gt;Home&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&quot;/articles&quot;&amp;gt;Articles&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&quot;/contact&quot;&amp;gt;Contact&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/nav&amp;gt;
&amp;lt;/header&amp;gt;

&amp;lt;main&amp;gt;
&amp;lt;article&amp;gt;
&amp;lt;h1&amp;gt;How to structure a blog post in HTML&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;Published on &amp;lt;time datetime=&quot;2026-07-20&quot;&amp;gt;July 20, 2026&amp;lt;/time&amp;gt; by Hadi.&amp;lt;/p&amp;gt;

&amp;lt;section&amp;gt;
&amp;lt;h2&amp;gt;Introduction&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;...&amp;lt;/p&amp;gt;
&amp;lt;/section&amp;gt;

&amp;lt;section&amp;gt;
&amp;lt;h2&amp;gt;The main point&amp;lt;/h2&amp;gt;
&amp;lt;p&amp;gt;...&amp;lt;/p&amp;gt;
&amp;lt;/section&amp;gt;
&amp;lt;/article&amp;gt;

&amp;lt;aside&amp;gt;
&amp;lt;h2&amp;gt;Related posts&amp;lt;/h2&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&quot;/post/a&quot;&amp;gt;Post A&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;li&amp;gt;&amp;lt;a href=&quot;/post/b&quot;&amp;gt;Post B&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&amp;lt;/aside&amp;gt;
&amp;lt;/main&amp;gt;

&amp;lt;footer&amp;gt;
&amp;lt;p&amp;gt;&amp;amp;copy; 2026 Mangobaz.&amp;lt;/p&amp;gt;
&amp;lt;/footer&amp;gt;
&amp;lt;/body&amp;gt;</code>


Notice the aria-label="Primary" on the &lt;nav&gt;. That is an ARIA attribute, used to give extra context to assistive technology. When a screen reader user lands on this page, they can ask for a list of all landmarks, and they will hear "Primary navigation", "Main", "Article", "Aside", "Footer" instead of just "navigation, navigation, region, region". The label also disambiguates when you have multiple navs on a page.



Building a real, accessible form


Forms are where beginners get the most frustrated and where accessibility mistakes hurt the most users. Let us build a contact form the right way.



<code>&amp;lt;form action=&quot;/contact&quot; method=&quot;post&quot; novalidate&amp;gt;
&amp;lt;div class=&quot;field&quot;&amp;gt;
&amp;lt;label for=&quot;name&quot;&amp;gt;Your name&amp;lt;/label&amp;gt;
&amp;lt;input type=&quot;text&quot; id=&quot;name&quot; name=&quot;name&quot;
required minlength=&quot;2&quot; maxlength=&quot;80&quot;
autocomplete=&quot;name&quot;&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class=&quot;field&quot;&amp;gt;
&amp;lt;label for=&quot;email&quot;&amp;gt;Email address&amp;lt;/label&amp;gt;
&amp;lt;input type=&quot;email&quot; id=&quot;email&quot; name=&quot;email&quot;
required autocomplete=&quot;email&quot;
inputmode=&quot;email&quot;&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class=&quot;field&quot;&amp;gt;
&amp;lt;label for=&quot;topic&quot;&amp;gt;What is this about?&amp;lt;/label&amp;gt;
&amp;lt;select id=&quot;topic&quot; name=&quot;topic&quot; required&amp;gt;
&amp;lt;option value=&quot;&quot;&amp;gt;Please choose&amp;lt;/option&amp;gt;
&amp;lt;option value=&quot;general&quot;&amp;gt;General question&amp;lt;/option&amp;gt;
&amp;lt;option value=&quot;support&quot;&amp;gt;Support request&amp;lt;/option&amp;gt;
&amp;lt;option value=&quot;press&quot;&amp;gt;Press inquiry&amp;lt;/option&amp;gt;
&amp;lt;/select&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;fieldset&amp;gt;
&amp;lt;legend&amp;gt;How should we reply?&amp;lt;/legend&amp;gt;
&amp;lt;label&amp;gt;&amp;lt;input type=&quot;radio&quot; name=&quot;reply&quot; value=&quot;email&quot; checked&amp;gt; Email&amp;lt;/label&amp;gt;
&amp;lt;label&amp;gt;&amp;lt;input type=&quot;radio&quot; name=&quot;reply&quot; value=&quot;phone&quot;&amp;gt; Phone&amp;lt;/label&amp;gt;
&amp;lt;label&amp;gt;&amp;lt;input type=&quot;radio&quot; name=&quot;reply&quot; value=&quot;no-reply&quot;&amp;gt; No reply needed&amp;lt;/label&amp;gt;
&amp;lt;/fieldset&amp;gt;

&amp;lt;div class=&quot;field&quot;&amp;gt;
&amp;lt;label for=&quot;msg&quot;&amp;gt;Your message&amp;lt;/label&amp;gt;
&amp;lt;textarea id=&quot;msg&quot; name=&quot;message&quot; rows=&quot;6&quot;
required minlength=&quot;10&quot;&amp;gt;&amp;lt;/textarea&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;div class=&quot;field&quot;&amp;gt;
&amp;lt;label&amp;gt;
&amp;lt;input type=&quot;checkbox&quot; name=&quot;agree&quot; required&amp;gt;
I agree to be contacted about this enquiry.
&amp;lt;/label&amp;gt;
&amp;lt;/div&amp;gt;

&amp;lt;button type=&quot;submit&quot;&amp;gt;Send message&amp;lt;/button&amp;gt;
&amp;lt;/form&amp;gt;</code>


Notice several things that are easy to miss.



Every input has a &lt;label&gt; tied to it via the for attribute, and the input has the matching id. Clicking the label focuses the input. A screen reader announces the label when the input is focused. If you skip this step, a blind user has no idea what each field is for.



The type="email" on the email field tells the browser to expect an email address, so on mobile the keyboard shows the @ key by default. The autocomplete attributes let the browser autofill from the user saved addresses, which is a huge usability win. The required, minlength, and maxlength attributes are built-in client-side validation.



The radio group is wrapped in a &lt;fieldset&gt; with a &lt;legend&gt;. This tells screen readers that the three radios belong together as a single question. Without the fieldset, the user only hears the label of the currently focused radio and does not know there are two more choices.



Multimedia the right way


Embedding a video or an audio file is one line, but accessibility takes a little more.



<code>&amp;lt;figure&amp;gt;
&amp;lt;video controls width=&quot;800&quot; poster=&quot;preview.jpg&quot;&amp;gt;
&amp;lt;source src=&quot;intro.mp4&quot; type=&quot;video/mp4&quot;&amp;gt;
&amp;lt;source src=&quot;intro.webm&quot; type=&quot;video/webm&quot;&amp;gt;
&amp;lt;track kind=&quot;captions&quot; src=&quot;intro.en.vtt&quot; srclang=&quot;en&quot; label=&quot;English&quot; default&amp;gt;
&amp;lt;track kind=&quot;subtitles&quot; src=&quot;intro.ur.vtt&quot; srclang=&quot;ur&quot; label=&quot;Urdu&quot;&amp;gt;
Your browser does not support video. &amp;lt;a href=&quot;intro.mp4&quot;&amp;gt;Download the video&amp;lt;/a&amp;gt; instead.
&amp;lt;/video&amp;gt;
&amp;lt;figcaption&amp;gt;A short introduction to the team.&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;</code>


The &lt;track&gt; elements provide captions and subtitles for deaf and hard of hearing visitors, and for anyone watching with the sound off. The fallback link inside the &lt;video&gt; element is shown by older browsers that do not support HTML5 video.



Common intermediate mistakes



Putting &lt;div&gt; on clickable things instead of &lt;button&gt;. A &lt;div&gt; is not focusable by keyboard, and screen readers do not announce it as interactive. If something should be clickable and trigger an action, use &lt;button&gt;. If it navigates, use &lt;a&gt; with an href.



Skipping the label because the placeholder is enough. The placeholder disappears as soon as the user types. A label stays visible. Always use a real label.



Using a &lt;div&gt; for a table. If your data really is a table, use &lt;table&gt;, &lt;thead&gt;, &lt;tbody&gt;, &lt;tr&gt;, &lt;th&gt;, and &lt;td&gt;. Screen readers can navigate them cell by cell. A div-based grid cannot.



What is next


Now that your HTML has real structure and is accessible, the natural next step is to make it look the way you want. That is the job of CSS, and that is exactly what the CSS beginner and intermediate lessons cover.


← More in HTML