14 KiB
Semantic HTML Guide
This guide provides recommendations for choosing appropriate HTML tags when converting Figma designs to React components, ensuring accessibility and semantic correctness.
Why Semantic HTML Matters
- Accessibility: Screen readers and assistive technologies rely on semantic HTML
- SEO: Search engines understand content structure better
- Maintainability: Code is more readable and self-documenting
- Styling: Easier to style with CSS selectors
- Future-proofing: Standards-compliant code ages better
Semantic HTML Elements
Document Structure
<header>
Use for:
- Site header
- Page header
- Section header
- Article header
Don't use for:
- Every heading (use h1-h6 instead)
- Random containers
Figma Indicators:
- Top section of a page or component
- Contains logo, navigation, or title
- Labeled "Header" in Figma
Example:
<header className="flex items-center justify-between p-4">
<img src="logo.png" alt="Company Logo" />
<nav>...</nav>
</header>
<nav>
Use for:
- Primary navigation
- Breadcrumb navigation
- Table of contents
- Pagination
Don't use for:
- Social media links (use
<ul>instead) - Footer links (unless it's primary navigation)
Figma Indicators:
- Navigation menu
- Labeled "Navigation" or "Nav" in Figma
- Contains multiple links to different sections
Example:
<nav className="flex gap-4">
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/contact">Contact</a>
</nav>
<main>
Use for:
- Main content of the page
- The primary content area
Don't use for:
- Sidebars
- Headers or footers
- Navigation
Rules:
- Only one
<main>per page - Should not be inside
<article>,<aside>,<footer>,<header>, or<nav>
Figma Indicators:
- Central content area of a page
- Labeled "Main Content" or "Content" in Figma
Example:
<main className="container mx-auto p-8">
{/* Primary page content */}
</main>
<aside>
Use for:
- Sidebars
- Related content
- Callout boxes
- Pull quotes
- Advertising
Don't use for:
- Main content
Figma Indicators:
- Sidebar sections
- "Related articles" sections
- Callout boxes
- Labeled "Sidebar" or "Aside" in Figma
Example:
<aside className="w-64 bg-gray-100 p-4">
<h3>Related Articles</h3>
<ul>...</ul>
</aside>
<footer>
Use for:
- Site footer
- Page footer
- Section footer
- Article footer
Don't use for:
- Every bottom section (assess if it's truly footer content)
Figma Indicators:
- Bottom section of a page or component
- Contains copyright, links, contact info
- Labeled "Footer" in Figma
Example:
<footer className="bg-gray-900 text-white p-8">
<p>© 2024 Company Name</p>
</footer>
<section>
Use for:
- Thematic groupings of content
- Chapters or sections of a page
- Tabbed content areas
Don't use for:
- Generic containers (use
<div>instead)
Rule: Each <section> should have a heading (h1-h6)
Figma Indicators:
- Distinct content sections on a page
- Sections with headings
- Labeled "Section" in Figma
Example:
<section className="py-16">
<h2>Our Services</h2>
<p>...</p>
</section>
<article>
Use for:
- Blog posts
- News articles
- Forum posts
- Comments
- Product cards (in some contexts)
- Independent, self-contained content
Don't use for:
- Generic content containers
Rule: Content inside <article> should make sense independently
Figma Indicators:
- Blog post layouts
- News article cards
- Comment sections
- Product cards
Example:
<article className="border rounded p-6">
<h2>Article Title</h2>
<p className="text-gray-600">Published on ...</p>
<p>Article content...</p>
</article>
Content
Headings: <h1> to <h6>
Use for:
- Section titles
- Content hierarchy
Don't use for:
- Styling purposes (use CSS instead)
Rules:
- Only one
<h1>per page (page title) - Don't skip levels (h1 → h2 → h3, not h1 → h3)
- Use in descending order
Figma Indicators:
- Text labeled "Heading", "Title", "H1", "H2", etc.
- Large, bold text at the start of sections
- Text styles named "Heading 1", "Heading 2", etc.
Example:
<h1 className="text-4xl font-bold">Page Title</h1>
<section>
<h2 className="text-3xl font-semibold">Section Title</h2>
<h3 className="text-2xl font-medium">Subsection Title</h3>
</section>
<p>
Use for:
- Paragraphs of text
- Body content
Don't use for:
- Headings
- Lists
- Single words or short phrases (use
<span>instead)
Example:
<p className="text-base leading-relaxed">
This is a paragraph of text...
</p>
<a>
Use for:
- Hyperlinks
- Navigation links
Don't use for:
- Buttons (use
<button>instead)
Rule: Always include href attribute
Accessibility:
- Use descriptive link text (not "click here")
- Add
rel="noopener noreferrer"for external links withtarget="_blank"
Example:
<a href="/about" className="text-blue-600 hover:underline">
Learn more about our company
</a>
{/* External link */}
<a
href="https://example.com"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600"
>
Visit example.com
</a>
<button>
Use for:
- Interactive buttons
- Form submissions
- Actions that don't navigate
Don't use for:
- Links (use
<a>instead)
Rule: Always include type attribute (button, submit, or reset)
Example:
<button
type="button"
onClick={handleClick}
className="px-4 py-2 bg-blue-500 text-white rounded"
>
Click Me
</button>
Lists
<ul> (Unordered List)
Use for:
- Lists without order
- Navigation menus (within
<nav>) - Feature lists
Example:
<ul className="list-disc list-inside">
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
</ul>
<ol> (Ordered List)
Use for:
- Lists with order/sequence
- Step-by-step instructions
- Rankings
Example:
<ol className="list-decimal list-inside">
<li>First step</li>
<li>Second step</li>
<li>Third step</li>
</ol>
<dl>, <dt>, <dd> (Definition List)
Use for:
- Term-definition pairs
- Metadata
- Key-value pairs
Example:
<dl>
<dt className="font-semibold">Name</dt>
<dd className="ml-4">John Doe</dd>
<dt className="font-semibold">Email</dt>
<dd className="ml-4">john@example.com</dd>
</dl>
Forms
<form>
Use for:
- All form inputs
- Search bars
- Login forms
Rule: Always include action or handle submit with JavaScript
Example:
<form onSubmit={handleSubmit} className="flex flex-col gap-4">
{/* form fields */}
</form>
<label>
Use for:
- Input labels
Rule: Always associate with an input using htmlFor or by wrapping
Example:
{/* Using htmlFor */}
<label htmlFor="email" className="font-medium">
Email
</label>
<input id="email" type="email" />
{/* Wrapping */}
<label className="flex flex-col gap-1">
<span className="font-medium">Email</span>
<input type="email" />
</label>
<input>
Use for:
- Text input
- Checkboxes
- Radio buttons
- File uploads
- Dates
- Many other input types
Rule: Always include type attribute
Example:
<input
type="text"
placeholder="Enter your name"
className="border rounded px-3 py-2"
/>
<textarea>
Use for:
- Multi-line text input
Example:
<textarea
rows={4}
placeholder="Enter your message"
className="border rounded px-3 py-2"
/>
<select>
Use for:
- Dropdown menus
Example:
<select className="border rounded px-3 py-2">
<option value="">Choose an option</option>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
</select>
<fieldset> and <legend>
Use for:
- Grouping related form fields
- Radio button groups
- Checkbox groups
Example:
<fieldset className="border rounded p-4">
<legend className="font-semibold px-2">Choose a size</legend>
<label>
<input type="radio" name="size" value="small" /> Small
</label>
<label>
<input type="radio" name="size" value="large" /> Large
</label>
</fieldset>
Media
<img>
Use for:
- Images
Rules:
- Always include
altattribute (describe the image or use empty string for decorative) - Always include
srcattribute
Example:
{/* Content image */}
<img
src="/photo.jpg"
alt="A scenic mountain landscape"
className="w-full rounded"
/>
{/* Decorative image */}
<img
src="/decoration.png"
alt=""
className="w-8 h-8"
/>
<figure> and <figcaption>
Use for:
- Images with captions
- Code snippets with descriptions
- Diagrams
Example:
<figure>
<img src="/chart.png" alt="Sales data chart" />
<figcaption className="text-sm text-gray-600">
Figure 1: Sales data for Q4 2024
</figcaption>
</figure>
<video> and <audio>
Use for:
- Video and audio content
Example:
<video controls className="w-full">
<source src="/video.mp4" type="video/mp4" />
Your browser does not support video.
</video>
Text-level Semantics
<strong> vs <b>
<strong>: Use for important text (semantic)<b>: Use for stylistic bold text (rare, prefer CSS)
Prefer <strong> in most cases.
<em> vs <i>
<em>: Use for emphasized text (semantic)<i>: Use for stylistic italic text (rare, prefer CSS)
Prefer <em> in most cases.
<span>
Use for:
- Inline text that needs styling
- Wrapping text for JavaScript manipulation
Don't use for:
- Semantic meaning (use
<strong>,<em>, etc.)
Example:
<p>
This is <span className="text-blue-600">highlighted</span> text.
</p>
<div>
Use for:
- Generic containers
- Layout purposes
Don't use for:
- Semantic meaning (use
<section>,<article>, etc. instead)
Example:
<div className="flex gap-4">
{/* content */}
</div>
Decision Tree: Choosing the Right Tag
For Content Sections
Is it the main content of the page?
├─ YES → <main>
└─ NO
├─ Is it a navigation menu?
│ └─ YES → <nav>
└─ NO
├─ Is it a sidebar or related content?
│ └─ YES → <aside>
└─ NO
├─ Is it a header section?
│ └─ YES → <header>
└─ NO
├─ Is it a footer section?
│ └─ YES → <footer>
└─ NO
├─ Is it a self-contained article or post?
│ └─ YES → <article>
└─ NO
├─ Is it a thematic section with a heading?
│ └─ YES → <section>
└─ NO → <div>
For Interactive Elements
Is it a link that navigates somewhere?
├─ YES → <a href="...">
└─ NO
└─ Is it a button that performs an action?
└─ YES → <button type="button">
For Text
Is it a heading?
├─ YES → <h1> to <h6> (based on hierarchy)
└─ NO
├─ Is it a paragraph?
│ └─ YES → <p>
└─ NO
├─ Is it emphasized/important?
│ ├─ Important → <strong>
│ └─ Emphasized → <em>
└─ NO → <span>
Common Figma → HTML Mappings
| Figma Element | Semantic HTML | Notes |
|---|---|---|
| Frame labeled "Header" | <header> |
Site/page header |
| Frame labeled "Nav" | <nav> |
Navigation menu |
| Frame labeled "Main" | <main> |
Primary content |
| Frame labeled "Sidebar" | <aside> |
Sidebar content |
| Frame labeled "Footer" | <footer> |
Site/page footer |
| Frame labeled "Section" | <section> |
Content section |
| Frame labeled "Card" | <article> or <div> |
Depends on content |
| Text layer "Heading" | <h1> to <h6> |
Based on hierarchy |
| Text layer "Body" | <p> |
Paragraph text |
| Button component | <button> |
Interactive button |
| Link component | <a> |
Hyperlink |
| Image layer | <img> |
Image element |
| Auto Layout (vertical) | <div className="flex flex-col"> |
Layout container |
| Form | <form> |
Form container |
| Input field | <input> |
Form input |
Accessibility Considerations
ARIA Attributes
When semantic HTML is not enough, use ARIA attributes:
{/* Button that controls a menu */}
<button
type="button"
aria-haspopup="true"
aria-expanded={isOpen}
>
Menu
</button>
{/* Navigation landmark */}
<nav aria-label="Primary navigation">
{/* links */}
</nav>
{/* Region landmark */}
<section aria-labelledby="section-title">
<h2 id="section-title">Section Title</h2>
</section>
Focus Management
Ensure interactive elements are keyboard accessible:
{/* Custom clickable div (avoid if possible) */}
<div
role="button"
tabIndex={0}
onClick={handleClick}
onKeyPress={handleKeyPress}
>
Click me
</div>
{/* Better: use semantic button */}
<button type="button" onClick={handleClick}>
Click me
</button>
Common Mistakes
-
Using
<div>for everything- ❌
<div className="header">...</div> - ✅
<header>...</header>
- ❌
-
Using
<a>for buttons- ❌
<a onClick={handleClick}>Submit</a> - ✅
<button type="button" onClick={handleClick}>Submit</button>
- ❌
-
Using
<button>for links- ❌
<button onClick={() => navigate('/about')}>About</button> - ✅
<a href="/about">About</a>
- ❌
-
Skipping heading levels
- ❌
<h1>Title</h1><h3>Subtitle</h3> - ✅
<h1>Title</h1><h2>Subtitle</h2>
- ❌
-
Multiple
<main>elements- ❌ Two
<main>on the same page - ✅ Only one
<main>per page
- ❌ Two
-
Empty
altattributes on content images- ❌
<img src="chart.jpg" alt="" /> - ✅
<img src="chart.jpg" alt="Sales chart showing growth" />
- ❌
-
Using
<br>for spacing- ❌
<p>Text<br><br><br>More text</p> - ✅ Use CSS margin/padding instead
- ❌
Tips
- Use semantic HTML first: Before adding ARIA, check if semantic HTML solves the problem
- Test with screen readers: Use tools like VoiceOver (Mac) or NVDA (Windows)
- Validate HTML: Use W3C validator to check for errors
- Think about structure: How would this content be read aloud?
- Consult ARIA authoring practices: https://www.w3.org/WAI/ARIA/apg/
- When in doubt, use
<div>or<span>: Don't force semantic meaning where it doesn't exist