Use semantic structure, document metadata, native controls, forms, media, and accessible relationships as the foundation of every interface.
Structure documents with semantic elements and landmarks
Choose native elements based on meaning and behavior
Build accessible forms with labels, validation, and descriptions
Use links, images, tables, and media correctly
Understand where ARIA helps and where native HTML is better
A valid HTML document establishes language, metadata, and a body for visible content. The document head contains information for browsers, search engines, and social platforms.
A clear structure improves accessibility, rendering, and maintainability before CSS or JavaScript is added.
Declare the document type
Set the page language
Include responsive viewport metadata
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1"
/>
<title>Learning Lab</title>
<meta
name="description"
content="Practice frontend concepts with guided exercises."
/>
</head>
<body>
<main>
<h1>Learning Lab</h1>
<p>Choose a course to begin.</p>
</main>
</body>
</html>Semantic elements communicate the role of content to browsers and assistive technology. They create meaningful landmarks and relationships without requiring custom JavaScript.
Choose an element for what the content is. CSS can change its appearance without discarding its meaning.
Use one clear page heading and a logical heading hierarchy
Represent navigation, main content, and complementary content with landmarks
Use sectioning elements when the content has a meaningful structural role
<header>
<a href="/" aria-label="Learning Lab home">
Learning Lab
</a>
<nav aria-label="Primary">
<a href="/courses">Courses</a>
<a href="/practice">Practice</a>
</nav>
</header>
<main>
<h1>React foundations</h1>
<section aria-labelledby="overview-title">
<h2 id="overview-title">Overview</h2>
<p>Learn rendering, props, state, and composition.</p>
</section>
</main>
<footer>
<small>© 2026 Learning Lab</small>
</footer>Headings establish the outline of a page and help users scan content. Paragraphs, lists, blockquotes, figures, and other elements should match the meaning of the content they contain.
Do not choose heading levels for visual size. Use CSS for appearance and HTML for structure.
Keep heading levels logical
Use lists for genuine collections
Use figure and figcaption for self-contained media with a caption
<article>
<h1>Understanding React state</h1>
<p>
State represents information that can change over time.
</p>
<h2>Rules to remember</h2>
<ul>
<li>Treat state as read-only.</li>
<li>Use setters to request updates.</li>
<li>Derive values when possible.</li>
</ul>
<figure>
<img
src="/images/state-flow.svg"
alt="State flowing into a React render"
/>
<figcaption>
React renders UI from the current state snapshot.
</figcaption>
</figure>
</article>Buttons, links, inputs, selects, textareas, and details elements include keyboard, focus, and assistive behavior that is expensive to recreate correctly.
Start with the native element matching the interaction. Styling can change appearance without losing behavior.
Use button for actions
Use anchor for navigation
Avoid clickable div elements for standard controls
<div class="lessonActions">
<button type="button">
Run code
</button>
<a href="/courses/react/next">
Next lesson
</a>
<details>
<summary>Show hint</summary>
<p>
Think about which component owns the state.
</p>
</details>
</div>Forms collect and submit user input. Every form control needs an accessible name, usually from a visible label associated through for and id.
Grouping related fields with fieldset and legend gives users additional context.
Associate labels and controls explicitly
Use fieldset and legend for groups of related choices
Choose input types that match the expected data
<form action="/account" method="post">
<div>
<label for="display-name">
Display name
</label>
<input
id="display-name"
name="displayName"
type="text"
autocomplete="name"
required
/>
</div>
<fieldset>
<legend>Preferred theme</legend>
<label>
<input
type="radio"
name="theme"
value="light"
/>
Light
</label>
<label>
<input
type="radio"
name="theme"
value="dark"
/>
Dark
</label>
</fieldset>
<button type="submit">
Save settings
</button>
</form>HTML provides validation attributes such as required, min, max, minlength, maxlength, pattern, and specialized input types.
Native validation handles many basic rules, while server-side validation remains necessary because client-side constraints can be bypassed.
Use required for mandatory fields
Use input types and constraints that describe expected data
Always validate again on the server
<form>
<label for="email">
Email address
</label>
<input
id="email"
name="email"
type="email"
autocomplete="email"
required
/>
<label for="password">
Password
</label>
<input
id="password"
name="password"
type="password"
minlength="12"
autocomplete="new-password"
aria-describedby="password-help"
required
/>
<p id="password-help">
Use at least 12 characters.
</p>
<button type="submit">
Create account
</button>
</form>Images need alternative text when they communicate content. Decorative images should use an empty alt attribute so assistive technology can ignore them.
Responsive image attributes and lazy loading can reduce unnecessary downloads while preserving content quality.
Describe meaningful images with concise alt text
Use alt="" for purely decorative images
Provide width and height to reduce layout shift
<figure>
<img
src="/images/editor-800.webp"
srcset="
/images/editor-480.webp 480w,
/images/editor-800.webp 800w,
/images/editor-1200.webp 1200w
"
sizes="(max-width: 700px) 100vw, 700px"
width="1200"
height="750"
alt="Code editor showing a React component"
loading="lazy"
/>
<figcaption>
Practice exercises use an IDE-style editor.
</figcaption>
</figure>Tables represent two-dimensional data relationships. They should not be used for page layout.
Headers, captions, and scope attributes help users understand how data cells relate to rows and columns.
Use caption to identify the table
Use th for header cells
Use scope when row or column relationships need to be explicit
<table>
<caption>
Course progress
</caption>
<thead>
<tr>
<th scope="col">Course</th>
<th scope="col">Lessons</th>
<th scope="col">Progress</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">React</th>
<td>12</td>
<td>75%</td>
</tr>
<tr>
<th scope="row">JavaScript</th>
<td>14</td>
<td>50%</td>
</tr>
</tbody>
</table>Interactive elements need an accessible name that explains their purpose. Visible text is usually the strongest source because it helps every user.
ARIA can add relationships or missing context, but it does not recreate native keyboard behavior for an unsuitable element.
Give icon-only controls an accessible name
Associate help and error text with the relevant control
Prefer native semantics before adding ARIA
<button
type="button"
aria-label="Close lesson panel"
>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
width="20"
height="20"
>
<path
d="M6 6l12 12M18 6L6 18"
stroke="currentColor"
stroke-width="2"
/>
</svg>
</button>Custom data-* attributes attach application-specific metadata to HTML without inventing invalid attributes.
Progressive enhancement starts with useful HTML and adds CSS or JavaScript behavior without making the basic experience depend on scripting.
Use data-* for application metadata, not styling meaning that belongs in classes
Keep core content and form submission functional without JavaScript when practical
Enhance native behavior rather than replacing it unnecessarily
<article
class="courseCard"
data-course-id="react-foundations"
data-difficulty="beginner"
>
<h2>React foundations</h2>
<p>
Learn rendering, props, state, and composition.
</p>
<a href="/courses/react-foundations">
Start course
</a>
</article>