Part 5: Accessibility, SEO, and Modern HTML5
Introduction
Accessibility and SEO are not afterthoughts you bolt on at the end. The structural and semantic choices made in the first four parts of this series β using <nav> instead of <div class="nav">, writing meaningful alt text, labelling every input β are already accessibility and SEO wins. This part covers the remaining layer: ARIA attributes, meta tags, structured data, and modern HTML5 features that make pages more robust.
I built several static documentation sites and tool pages over time. The improvements in discoverability and usability that came from getting these details right were consistent and measurable.
Accessibility (A11y)
Why It Matters
Accessibility means building pages that work for people who use keyboard navigation, screen readers (NVDA, JAWS, VoiceOver), voice input, switch access, and other assistive technologies. In practical terms, accessible HTML is also more robust HTML β it works even when CSS or JavaScript fail to load.
Most accessibility in HTML comes from using the right elements. A <button> is already focusable, keyboard-operable, and announced as a button by screen readers. A <div> with an onclick handler requires significant extra work to replicate that behaviour.
ARIA (Accessible Rich Internet Applications)
ARIA attributes extend HTML's accessibility semantics. The first rule of ARIA: use it only when a native HTML element cannot do the job.
role
roleOverrides the element's implicit role when a semantic element cannot be used:
<!-- Avoid: use <button> instead -->
<div role="button" tabindex="0" onclick="doThing()">Click me</div>
<!-- Prefer: native element -->
<button onclick="doThing()">Click me</button>When you genuinely need a custom component:
aria-label
aria-labelProvides an accessible name when visible text is absent or insufficient:
Without aria-label, a screen reader would announce the navigation elements as "navigation", "navigation", "navigation" with no differentiation.
aria-labelledby and aria-describedby
aria-labelledby and aria-describedbyLink elements to labels or descriptions elsewhere in the DOM:
aria-hidden
aria-hiddenHides elements from the accessibility tree (screen readers) while keeping them visible:
Do not use aria-hidden="true" on elements that receive focus.
aria-expanded
aria-expandedIndicates whether a collapsible element is open:
JavaScript toggles aria-expanded between "true" and "false" and removes/adds the hidden attribute.
aria-live
aria-liveAnnounces dynamic content changes to screen readers:
politeβ waits for the user to finish their current action before announcingassertiveβ interrupts immediately (use sparingly, only for errors)
Keyboard Navigation
All interactive elements (<a>, <button>, <input>, <select>, <textarea>) are focusable by default. Tab moves focus forward, Shift+Tab moves backward.
tabindex
tabindextabindex="0"
Element enters the natural tab order
tabindex="-1"
Element can receive focus programmatically but is not in tab order
tabindex="1" (or any positive value)
Avoid β disrupts natural tab order
Skip Links
Allow keyboard users to jump past repetitive navigation:
The link is visually hidden until focused by keyboard navigation.
SEO Meta Tags
Essential Meta Tags
Title tag rules:
50β60 characters for search results
Most important keyword near the start
Unique per page
Meta description rules:
150β160 characters
Accurate summary of the page content
Not a ranking factor directly, but affects click-through rate
Canonical URL: Tells search engines the definitive URL for the page β prevents duplicate content issues when the same page is accessible at multiple URLs (e.g., with/without trailing slash, HTTP/HTTPS).
Open Graph Tags
The recommended OG image size is 1200Γ630 pixels. It appears when links are shared on Slack, LinkedIn, Facebook, iMessage, and other platforms that render previews.
Twitter Card Tags
Structured Data (Schema.org)
Structured data provides machine-readable context about page content. Search engines use it to generate rich results (star ratings, FAQ dropdowns, breadcrumbs, etc.) in search results.
Structured data is embedded in a <script type="application/ld+json"> block:
Common @type values:
Article
Blog posts, documentation articles
BreadcrumbList
Navigation breadcrumbs
FAQPage
Pages with Q&A content
SoftwareApplication
Tools and applications
Person
Author profiles
WebSite
Site-wide metadata
Modern HTML5 Features
<details> and <summary>
<details> and <summary>Native disclosure widget β no JavaScript required:
Add open to make it expanded by default:
<dialog>
<dialog>Native modal element:
When opened with showModal(), the browser traps focus within the dialog and adds an ::backdrop pseudo-element for the overlay. This is full modal behaviour natively, without JavaScript libraries.
<output>
<output>Represents the result of a calculation or user action:
<progress> and <meter>
<progress> and <meter><meter> changes colour automatically based on low, high, and optimum thresholds.
<template>
<template>A client-side content template β not rendered, but available to JavaScript for cloning:
data-* Attributes
data-* AttributesStore custom data on elements without abusing class or id:
Performance Attributes
loading on <img> and <iframe>
loading on <img> and <iframe>defer and async on <script>
defer and async on <script>Use defer for application scripts. Use async only for independent scripts (analytics, tracking) that do not depend on other scripts or the DOM.
<link rel="preload">
<link rel="preload">Tell the browser about critical resources early so it can start fetching them sooner:
Final Checklist
Before shipping any HTML page:
Document
Structure
Images
Forms
Accessibility
Performance
Summary
ARIA roles
Use only when native elements cannot do the job
aria-label
Name elements without visible text
aria-live
Announce dynamic content to screen readers
Skip links
Let keyboard users bypass navigation
Meta description
150β160 chars, unique per page
Canonical URL
Prevent duplicate content in search indexing
Open Graph tags
Control social share previews
Structured data (ld+json)
Rich search results
<details> / <summary>
Native disclosure without JavaScript
<dialog>
Native modal with focus trap and backdrop
data-* attributes
Custom data storage on elements
defer / async
Non-blocking script loading
Series Complete
This series covered the full HTML foundation:
From here, CSS handles visual presentation and JavaScript handles behaviour β but both build on the structural and semantic foundation covered in this series.
Last updated