Cheat SheetsHTML, CSS & ResponsiveSemantic HTML

Semantic HTML — Cheat Sheet

HTML, CSS & Responsive · 5 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
Semantic HTML
HTML, CSS & Responsive5 topicsQuick revision reference
1

Document Structure and Landmarks

The element you choose is an API. A div says nothing; a nav, a main and a heading tell the browser, the screen reader and Google what the page is.

  • lang on the html element decides which voice a screen reader uses; without it English is read with the wrong phonetics
  • Without the viewport meta tag a phone renders at 980px and every responsive rule is ignored
  • header, nav, main, aside and footer are landmarks a screen-reader user can jump between — exactly one main per page
  • Headings are an outline: one h1, no skipped levels, ordered by meaning rather than by size
  • A section without a heading should be a div; the element you choose is information, not decoration
charset, viewport, lang, title
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Two Sum — AiCanCode</title>
  <meta name="description" content="Solve Two Sum with a hash map in O(n).">
  <link rel="canonical" href="https://aicancode.org/dsa/two-sum">
</head>
<body> … </body>
</html>

// lang="en" tells a screen reader which voice to use. Without it,
// English is read with the user's default language phonetics —
// which is unintelligible.

// Without the viewport meta, a phone renders the page at 980px and
// zooms out. Every responsive rule you write is then ignored.

// title is the browser tab, the bookmark, the search result heading
// and the first thing a screen reader announces. Front-load the
// specific part: "Two Sum — AiCanCode", not "AiCanCode — Two Sum".
2

Text, Links and Buttons

A link goes somewhere, a button does something. Getting that one distinction right fixes more accessibility problems than any ARIA attribute.

  • A link changes the URL; a button acts on the current page — that single question decides the element
  • A button inside a form defaults to type="submit", which is why a Cancel button can reload the page
  • Link text is the accessible name and is heard out of context, so "click here" is unusable
  • target="_blank" needs rel="noopener" and a visible or screen-reader warning
  • strong and em carry announced meaning; b and i are purely visual, and lists announce their item count
Does the URL change?
// Navigates -> a link
<a href="/dsa/problems">All problems</a>

// Performs an action on this page -> a button
<button type="button" onclick="openDialog()">Show hint</button>

// The differences you get for free with the right element:
//              <a href>            <button>
// keyboard     Enter               Enter AND Space
// Ctrl+click   opens a new tab     nothing
// history      yes                 no
// announced    "link"              "button"
// right-click  copy link address   nothing

// The two failures:
<a href="#" onclick="save()">Save</a>       // jumps to top, adds history
<div onclick="go()">Go</div>                // unreachable by keyboard

// Inside a form, button defaults to type="submit" —
// which is why a "Cancel" button reloads the page:
<button>Cancel</button>                     // submits!
<button type="button">Cancel</button>       // correct
3

Forms — Labels, Input Types and Autofill

The right input type gives a phone the right keyboard, the right autocomplete lets a browser fill a form in one tap, and a real label makes the field usable at all.

  • Every input needs a real label — a placeholder is not announced, vanishes on typing and fails contrast
  • A label also enlarges the tap target, which matters most on phones
  • The input type sets the mobile keyboard and native validation; type="number" is wrong for phone numbers, OTPs and PINs
  • inputmode picks the keyboard without changing validation semantics
  • autocomplete tokens let the browser and password manager fill a form in one tap — current-password and new-password are the important pair
label, sr-only, aria-describedby
<label for="email">Email address</label>
<input id="email" name="email" type="email">

// Or wrap, which needs no id
<label>
  Email address
  <input name="email" type="email">
</label>

// A real label also expands the click target — tapping the text
// focuses the field, which matters a lot on a phone.

// Placeholder as label: fails on four counts
<input placeholder="Email address">      // no announced name,
// disappears on typing, low contrast, and looks pre-filled.

// Visually hidden when the design has no room — still announced
<label for="q" class="sr-only">Search problems</label>
<input id="q" type="search" placeholder="Search…">

// Help text and errors, linked so they are announced with the field
<input id="pw" aria-describedby="pw-help pw-error" aria-invalid="true">
<p id="pw-help">At least 8 characters.</p>
<p id="pw-error" role="alert">Include a number.</p>

// Group related controls
<fieldset><legend>Difficulty</legend> …radios… </fieldset>
4

Tables — Real Data, and Making It Fit a Phone

A table is the right element for tabular data and the wrong one for layout. Headers with scope make it navigable; a phone needs a deliberate strategy, not a horizontal scrollbar by accident.

  • scope="col" and scope="row" let a screen reader announce the header with each cell
  • caption is the table's accessible name and is announced when a user enters it
  • A horizontally scrolling table wrapper needs tabindex and a label, or keyboard users cannot scroll it
  • Stacking rows into cards with data-label pseudo-content is the usual small-screen alternative
  • Use a table when rows and columns intersect meaningfully; a div grid means writing every ARIA role by hand
caption, thead, th scope, tfoot
<table>
  <caption>Submissions this week</caption>

  <thead>
    <tr>
      <th scope="col">Problem</th>
      <th scope="col">Difficulty</th>
      <th scope="col">Runtime</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <th scope="row">Two Sum</th>      <!-- the row's own header -->
      <td>Easy</td>
      <td>42 ms</td>
    </tr>
  </tbody>

  <tfoot><tr><td colspan="3">3 of 12 solved</td></tr></tfoot>
</table>

// scope="col" and scope="row" are what let a screen reader say
// "Two Sum, Difficulty, Easy" instead of just "Easy".

// caption is the table's accessible name, announced on entry.
// It also survives when the surrounding heading does not.

// Numbers align right and use tabular figures so columns line up:
td.num { text-align: right; font-variant-numeric: tabular-nums }
5

Images, Media and Page Metadata

Images are usually the heaviest thing on a page and the most common cause of layout shift. Metadata decides how the page looks in a search result and in a shared link.

  • width and height on images reserve space and prevent the layout shift that dominates CLS
  • alt describes purpose — empty for decorative images, and never omitted, or the filename is read aloud
  • loading="lazy" belongs below the fold; on the hero image it delays LCP, so preload that one instead
  • Captions serve far more people than deaf users, and autoplay with sound is blocked and unwelcome
  • Open Graph tags control link previews and og:image must be an absolute URL or the preview is blank
Dimensions, formats, lazy, and purposeful alt
<img src="cover.webp" width="640" height="360" alt="" loading="lazy" decoding="async">

// width and height prevent layout shift — the browser reserves the
// space from the ratio before the file arrives. This is the single
// biggest cause of CLS.

// alt describes PURPOSE, not appearance
<img alt="">                                   // decorative — correctly empty
<img alt="Akshay Sonalkar">                    // a portrait
<img alt="Solved problems rose from 12 to 48"> // a chart: state the finding
<img alt="Two Sum solution screenshot">        // not "image of a screenshot"

// Never omit alt entirely — a screen reader then reads the filename.

// Modern formats and responsive sources
<picture>
  <source type="image/avif" srcset="hero.avif">
  <source type="image/webp" srcset="hero.webp">
  <img src="hero.jpg" alt="…" width="1200" height="630">
</picture>

// loading="lazy" for below the fold; NEVER on the hero image —
// it delays your LCP. Preload that one instead:
<link rel="preload" as="image" href="hero.webp" fetchpriority="high">
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/html-css