Copy an old project's index.html as a starting point for a new one, and you inherit whatever mistakes and shortcuts lived in the original — a missing viewport tag nobody noticed because the old project happened to work fine on the one device it was tested on, or leftover script tags for a library the new project doesn't use. A real template is different: it's deliberately built to be a clean starting point, not whatever accumulated cruft the last project left behind.
Every good template starts with the same small set of head elements, and skipping any of them causes a specific, predictable problem later:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page Title</title>
<meta name="description" content="One or two sentences describing the page.">
<link rel="icon" href="/favicon.ico">
</head>
lang="en" isn't decorative — screen readers use it to select the correct pronunciation engine, and getting it wrong makes a page sound wrong when read aloud. The viewport meta tag is what makes a page respect actual device width instead of rendering at a fixed desktop-sized virtual viewport and shrinking everything to fit, which is the single most common reason a page "isn't responsive" despite otherwise-correct CSS.
Browsers ship with wildly inconsistent default styling — different default margins on headings, different form control appearances — which is exactly the kind of inconsistency a template exists to eliminate before real styling even begins:
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
img, picture, video {
max-width: 100%;
display: block;
}
button, input, textarea, select {
font: inherit;
}
box-sizing: border-box alone prevents one of the most common CSS confusions for beginners — padding and border adding to an element's declared width instead of being included within it.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My Project</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0b0e14;
--text: #e6e8ec;
--accent: #7c5cff;
}
body {
background: var(--bg);
color: var(--text);
font-family: system-ui, -apple-system, sans-serif;
line-height: 1.6;
}
</style>
</head>
<body>
<main>
<!-- page content -->
</main>
</body>
</html>
Using system-ui as the first font stack entry means text renders with each OS's native UI font instead of downloading a web font just to display placeholder content — a small detail that keeps a starter template genuinely fast by default.
A single-page template is straightforward, but a five-page site built from copy-pasted HTML files creates a maintenance problem the moment the nav needs a new link — now it's five files to edit instead of one, and it's easy to miss one and ship an inconsistent header. Static templating solves this without a full framework:
<!-- Using a simple include, e.g. via an 11ty/Eleventy or PHP include -->
<!--#include file="partials/header.html" -->
<main>
...page-specific content...
</main>
<!--#include file="partials/footer.html" -->
Even without a build tool, most static hosts (and simple PHP or SSI includes) support this pattern — the header and footer live in one file, referenced everywhere, so a nav change is a one-file edit instead of a find-and-replace across the whole site.
Lorem ipsum text is a habit worth breaking in a template meant for reuse — it hides real problems, like a heading that looks fine at 3 words but breaks awkwardly at the 7-word headline a real project will actually use. A good template ships with realistic placeholder copy at roughly the length real content will be, so layout issues surface immediately instead of after content gets dropped in:
<!-- Weak: hides real length problems -->
<h1>Lorem ipsum dolor sit amet</h1>
<!-- Better: reflects realistic heading length and tone -->
<h1>Ship your landing page in an afternoon, not a sprint</h1>
A hand-rolled boilerplate makes sense for a personal project or something you'll iterate on for months, since you understand every line and can extend it without archaeology. A pre-built template makes more sense under time pressure, or for a category of site you don't build often — a coming-soon page, a one-off landing page — where the goal is shipping something correct quickly, not building a reusable personal system.
UIXDraft's 180+ HTML/CSS templates already include the meta tags, reset, and structure covered here — built and tested, not assembled from scratch each time.
Browse the templates →If you're maintaining a personal boilerplate across projects, put it in its own small git repository rather than copy-pasting the files each time — a template that lives as a real, versioned project can be improved incrementally, with each fix or addition available to every future project that starts from it, instead of every copy silently drifting apart from the others over time.
A template that "looks right" in one browser at one window size hasn't actually been tested — it's just been looked at once. A quick pass worth doing on any template before adopting it as a base: resize the browser window slowly from wide to narrow and watch for anything that breaks mid-resize (not just at the endpoints), zoom text to 150% and confirm nothing overlaps, and check that clicking every link and button actually does something reasonable rather than being a dead placeholder href="#".
<div class="header"> instead of <header> loses free accessibility and SEO benefits for no real gainAn HTML template is a static starting file you edit directly — no build step, no dependencies to install. A framework starter scaffolds a project structure with a build pipeline, package manager, and often a component system. For a simple static page, a plain HTML template is usually faster to actually ship; for an interactive app, a framework starter earns its complexity.
A small custom reset (a handful of rules, as shown above) is usually enough for a modern template and keeps the file dependency-free. Full normalize.css libraries made more sense when browser inconsistencies were larger; today's evergreen browsers agree on defaults closely enough that a lightweight reset covers most real cases.
Revisit it every few months and check whether newer CSS (container queries, :has(), nesting) can replace a workaround that used to require JavaScript or extra markup. A template that hasn't changed in two years is usually carrying dead weight — vendor prefixes no longer needed, polyfills for now-native features. Treat it as living code, not a one-time artifact you built once and never revisit.