What Goes in a Modern HTML Boilerplate
Build a modern HTML boilerplate with the five required lines, useful metadata, favicon and social tags. See what to keep, add or remove from old templates.
A modern HTML boilerplate needs five things: the HTML5 doctype, a lang attribute on <html>, a UTF-8 charset declaration, a viewport meta tag and a unique <title>. Everything else depends on whether the page will be shared, installed or bookmarked.
Most pages start from Emmet’s ! snippet, a framework’s index.html or an HTML5 Boilerplate copy that is ten years old. Nobody goes back and checks those lines, so outdated ones get copied from project to project.
This article goes through the document line by line. For each line it covers what the line does, what breaks without it, and whether it is required, optional or safe to delete. For head ordering and the wider metadata picture, read the companion piece on what actually belongs in the head of your document.
This is what Emmet’s ! produces:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
</html>
It’s a valid starting point. It has no description, canonical, social tags, icons or color-scheme, though, and its viewport includes a value you no longer need.
Key Takeaways
- The required lines in a modern HTML boilerplate are the doctype,
lang,<meta charset="UTF-8">, the viewport tag and<title>. Without any one of them, the page renders incorrectly, becomes less accessible or is invalid. - The
langattribute controls screen reader pronunciation, CSS hyphenation and browser translation. Google does not use it to detect a page’s language. maximum-scale,minimum-scaleanduser-scalable=nostop low-vision users from zooming. Delete them wherever they appear.- A complete favicon set is
favicon.ico,favicon.svg, a 180×180apple-touch-icon.pngand a web manifest with 192 and 512 pixel icons plus a maskable one. X-UA-Compatible,shrink-to-fit=noandtype="text/javascript"do nothing useful in a new project and can be removed.
Doctype and the lang Attribute
Required. <!DOCTYPE html> switches the browser into standards mode. Without it, the HTML parser falls back to quirks mode, which copies old browser layout bugs. You then get box-model and table sizing differences that no amount of CSS debugging explains.
Required. The lang attribute on <html> does three jobs:
- It tells screen readers which pronunciation rules to apply.
- It lets CSS
hyphens: autobreak words using the correct dictionary. - It helps browser translation features work out the page’s language.
Without it, a screen reader may read French text with English phonetics. The value is a BCP 47 language tag such as en, de or pt-BR, and child elements can override it, as MDN’s lang reference describes.
lang handles accessibility and language, not search ranking. Google Search states that it ignores lang and detects the language from the page content.
<!DOCTYPE html>
<html lang="en">
Charset First, Then Viewport
Required. <meta charset="UTF-8"> has to fall within the first 1024 bytes of the document. That’s why it goes first in <head>, ahead of <title> and any other text the browser might otherwise decode with the wrong encoding. If it’s missing or comes too late, accented characters and emoji in your title turn into mojibake.
Required. The viewport tag tells mobile browsers to lay the page out at the device’s width. Without it, phones render a desktop-width layout (usually around 980px) and shrink it down, so your media queries never fire. width=device-width is enough on its own, and initial-scale=1 is usually unnecessary. See MDN’s viewport reference for the full list of values.
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
Optional, test first. <meta name="text-scale" content="scale"> is defined in the CSS Fonts Level 5 draft, and only Chromium-based browsers ship it. It makes the root font size follow the text size the user picks in their OS or browser settings. As a result, only rem and em text grows, and fixed-height components can overflow. Increase the system text size and check your layouts before you ship it.
Title, Description and Canonical
Required. <title> names the page in browser tabs, bookmarks, history and search results. If you leave the default Document, every tab you open looks identical.
Optional but recommended. <meta name="description"> offers a summary for search snippets. Google may rewrite it if it finds page content that fits the query better, and it truncates snippets based on display width. That makes character-count rules unreliable.
Optional but recommended. <link rel="canonical"> names the preferred URL when the same content is reachable at more than one address, for example with query strings or trailing-slash variants. For more detail, see SEO-related HTML tags and how to optimize them.
<title>Page title – Site name</title>
<meta name="description" content="One or two sentences describing this page.">
<link rel="canonical" href="https://example.com/page">
Open Graph and Twitter Card Tags
Optional, required for anything people share. Open Graph tags control the link preview on social platforms and in chat apps. Without them, a shared link shows up as a bare URL or as a card built from whatever the scraper finds.
The Open Graph protocol lists og:title, og:type, og:image and og:url as required properties, and it says a page with no type should be treated as website. Set og:url to the same value as your canonical URL, so links that carry tracking parameters don’t get cached as separate pages. For og:image, Meta recommends an image of at least 1200×630 pixels, kept close to a 1.91:1 ratio.
X reads its own twitter:* tags, written with name rather than property, and falls back to the matching og:* values when they’re missing. In practice, the only X-specific tag you need is twitter:card, and only if you want the large-image card.
<meta property="og:title" content="Page title">
<meta property="og:description" content="Description for link previews.">
<meta property="og:image" content="https://example.com/og-image.jpg">
<meta property="og:url" content="https://example.com/page">
<meta name="twitter:card" content="summary_large_image">
To generate these tags per page, use the meta tag generator.
The Favicon Set
Optional, but expected. A complete favicon setup has four parts. Leave them out and you get a blank tab icon, a screenshot-style tile on iOS home screens and generic Android install icons.
- A 32×32
favicon.icofor older browsers. - A
favicon.svgthat can adapt toprefers-color-scheme. Safari has supported SVG favicons since Safari 26.0. - A 180×180
apple-touch-icon.pngfor iOS home screens. - A web app manifest listing 192×192 and 512×512 PNG icons plus a maskable variant for circular and squircle masks.
Keep both <link rel="icon"> elements, the favicon.ico one and the favicon.svg one. Browsers that can’t use SVG favicons, including Safari before version 26, then still get the ICO file.
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<style>
circle { fill: #1a1a2e; }
@media (prefers-color-scheme: dark) {
circle { fill: #f5f5f5; }
}
</style>
<circle cx="16" cy="16" r="14"/>
</svg>
{
"name": "Site name",
"short_name": "Site",
"icons": [
{ "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/icon-maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" }
]
}
For more on generating each file, see how to add a favicon to your website.
color-scheme and theme-color
Optional, recommended if you ship dark mode. <meta name="color-scheme" content="light dark"> tells the browser that the page supports both modes before any CSS arrives. Default backgrounds, form controls and scrollbars then follow the user’s preference. Without it, a dark-themed site can flash a white canvas on load. See MDN on color-scheme.
Optional. theme-color tints browser UI, and it accepts a media attribute so you can set separate light and dark values. It works in Chromium on Android, but MDN lists browser support as limited, so treat it as a nice extra rather than something every browser will show.
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#111111" media="(prefers-color-scheme: dark)">
A Reduced-Motion Default
Optional, strongly recommended. A reduced-motion default is a rule for your base stylesheet, not a head tag. It belongs in the boilerplate because nobody goes back and adds it later. A prefers-reduced-motion: reduce block shortens animations and transitions and turns off smooth scrolling for users who have asked their operating system for less motion.
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
The durations are set to 0.01ms rather than 0, so animationend and transitionend still fire. That way, JavaScript that waits for those events doesn’t hang. This is a blunt default. Individual components can opt back in to gentle motion, such as opacity fades, that doesn’t cause vestibular problems.
Which Lines Should You Delete From Old Boilerplates?
Delete. Four lines in old HTML boilerplates come from IE-era templates and early responsive starters, and none of them does anything useful today:
X-UA-Compatiblechose Internet Explorer document modes. No browser you support reads it.maximum-scale,minimum-scaleanduser-scalable=nostop people with low vision from pinch-zooming, which works against WCAG 1.4.4 Resize Text. Remove them from every viewport tag.shrink-to-fit=noworked around an old iOS Safari scaling behavior and no longer does anything.type="text/javascript"repeats the default. The HTML spec tells authors to leavetypeoff for classic scripts.
<!-- Before -->
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, shrink-to-fit=no">
<script type="text/javascript" src="/app.js"></script>
<!-- After -->
<meta name="viewport" content="width=device-width">
<script type="module" src="/app.js"></script>
A type="module" script is deferred by default, so it runs after parsing without blocking render. If a script can’t be a module, add defer to it.
The Full HTML Boilerplate
The complete modern HTML boilerplate below combines every required and recommended line from this article. Add site-specific extras such as feeds or preloads on top of it as needed. The skip link and the <main> landmark go in the body, and 10 essential HTML elements covers the semantic elements that come next.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width">
<!-- Optional, test with large OS text first: <meta name="text-scale" content="scale"> -->
<title>Page title – Site name</title>
<meta name="description" content="One or two sentences describing this page.">
<link rel="canonical" href="https://example.com/page">
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#111111" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="/styles.css">
<script type="module" src="/app.js"></script>
<link rel="icon" href="/favicon.ico">
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="manifest" href="/site.webmanifest">
<meta property="og:title" content="Page title">
<meta property="og:description" content="Description for link previews.">
<meta property="og:image" content="https://example.com/og-image.jpg">
<meta property="og:url" content="https://example.com/page">
<meta name="twitter:card" content="summary_large_image">
</head>
<body>
<a href="#main">Skip to content</a>
<main id="main"></main>
</body>
</html>
| Line | What it does | What breaks without it | Verdict |
|---|---|---|---|
| Doctype | Standards mode | Quirks-mode layout | Required |
lang | Pronunciation, hyphenation, translation | Wrong screen reader voice | Required |
charset | Declares UTF-8 | Garbled characters | Required |
viewport | Device-width layout | Shrunken desktop layout on phones | Required |
<title> | Names the page | Identical tabs and bookmarks | Required |
| Description, canonical | Snippet and preferred URL | Weaker snippets, duplicate URLs | Optional |
Open Graph, twitter:card | Link previews | Bare or generic cards | Optional |
| Favicon set | Tab and home screen icons | Blank or screenshot icons | Optional |
color-scheme | Native dark mode | White flash, light controls | Optional |
X-UA-Compatible, zoom limits, shrink-to-fit, type="text/javascript" | Legacy workarounds | Nothing | Delete |
Only five lines in an HTML boilerplate are required. Everything else is either optional, depending on whether the page gets shared, installed or themed, or a legacy line to delete. Open the index.html of your current project, check each line against the table above, and remove anything in the delete row before you copy the file into the next project.
FAQs
Is meta charset the same as meta http-equiv Content-Type?
Yes. Under the WHATWG HTML spec, the http-equiv Content-Type version does the same job as the charset attribute. Both count as a character encoding declaration and both must sit within the first 1024 bytes. A document must not contain both, and validators flag it as an error. Use the shorter meta charset='UTF-8'. If the server sends a charset in its HTTP Content-Type header, make sure it also says UTF-8.
Do meta and link tags need a trailing slash in HTML?
No. Meta, link, img and the other void elements have no closing tag, and the HTML parser ignores a trailing slash on them. A tag written with the slash and the same tag written without it produce the same element. The slash is valid but has no effect, so it comes down to style. The only case where it matters is a page served as XHTML with an XML MIME type.
Can I leave out the html, head and body tags?
Yes. The HTML spec marks the html, head and body tags as optional in many cases, and the parser creates those elements even when the tags are missing. A boilerplate should still write them out. The lang attribute needs an explicit html start tag to sit on, and explicit tags keep the boundary between head and body easy to read. If you omit the html tag, you also lose lang.
How do I change the default lang value in Emmet's ! snippet?
Set the lang variable in VS Code's emmet.variables setting. In settings.json, add a lang key with a value such as de to the emmet.variables object, and Emmet then writes that language code every time it expands the ! snippet. The same setting has a charset variable, which you should leave at UTF-8. The viewport line still contains initial-scale=1.0, so delete that by hand or define a custom snippet.
Gain control over your UX
See how users are using your site as if you were sitting next to them, learn and iterate faster with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Star on GitHub12k