727 lines
14 KiB
Markdown
727 lines
14 KiB
Markdown
# 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
|
|
|
|
1. **Accessibility**: Screen readers and assistive technologies rely on semantic HTML
|
|
2. **SEO**: Search engines understand content structure better
|
|
3. **Maintainability**: Code is more readable and self-documenting
|
|
4. **Styling**: Easier to style with CSS selectors
|
|
5. **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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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 with `target="_blank"`
|
|
|
|
**Example:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
{/* 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:**
|
|
```tsx
|
|
<input
|
|
type="text"
|
|
placeholder="Enter your name"
|
|
className="border rounded px-3 py-2"
|
|
/>
|
|
```
|
|
|
|
#### `<textarea>`
|
|
|
|
**Use for:**
|
|
- Multi-line text input
|
|
|
|
**Example:**
|
|
```tsx
|
|
<textarea
|
|
rows={4}
|
|
placeholder="Enter your message"
|
|
className="border rounded px-3 py-2"
|
|
/>
|
|
```
|
|
|
|
#### `<select>`
|
|
|
|
**Use for:**
|
|
- Dropdown menus
|
|
|
|
**Example:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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 `alt` attribute (describe the image or use empty string for decorative)
|
|
- Always include `src` attribute
|
|
|
|
**Example:**
|
|
```tsx
|
|
{/* 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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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:**
|
|
```tsx
|
|
<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:
|
|
|
|
```tsx
|
|
{/* 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:
|
|
|
|
```tsx
|
|
{/* 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
|
|
|
|
1. **Using `<div>` for everything**
|
|
- ❌ `<div className="header">...</div>`
|
|
- ✅ `<header>...</header>`
|
|
|
|
2. **Using `<a>` for buttons**
|
|
- ❌ `<a onClick={handleClick}>Submit</a>`
|
|
- ✅ `<button type="button" onClick={handleClick}>Submit</button>`
|
|
|
|
3. **Using `<button>` for links**
|
|
- ❌ `<button onClick={() => navigate('/about')}>About</button>`
|
|
- ✅ `<a href="/about">About</a>`
|
|
|
|
4. **Skipping heading levels**
|
|
- ❌ `<h1>Title</h1><h3>Subtitle</h3>`
|
|
- ✅ `<h1>Title</h1><h2>Subtitle</h2>`
|
|
|
|
5. **Multiple `<main>` elements**
|
|
- ❌ Two `<main>` on the same page
|
|
- ✅ Only one `<main>` per page
|
|
|
|
6. **Empty `alt` attributes on content images**
|
|
- ❌ `<img src="chart.jpg" alt="" />`
|
|
- ✅ `<img src="chart.jpg" alt="Sales chart showing growth" />`
|
|
|
|
7. **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
|