A surprising number of one-page landing sites get built with a full JavaScript framework, a component library, and a build pipeline — for a page that's a hero, three feature blocks, and a signup form. That's not wrong exactly, but it's often more infrastructure than the problem needs. A single landing page rendered with plain HTML and CSS loads faster, has nothing to break during a framework version bump, and can be understood top to bottom by opening one file. Here's one built section by section.
Before any visual section, set up the values everything else will reference. This is the five minutes that saves an hour of search-and-replace later if the brand color changes:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Product Name</title>
<style>
:root {
--ink: #1a1a2e;
--muted: #6b6b80;
--accent: #5b4bff;
--bg: #fafafa;
--radius: 12px;
--max-width: 1080px;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
color: var(--ink);
background: var(--bg);
line-height: 1.6;
}
.wrap { max-width: var(--max-width); margin: 0 auto; padding: 0 24px; }
</style>
</head>
<body>
<!-- sections go here -->
</body>
</html>
The .wrap class is doing the layout work every section below reuses — centering content and capping its width — rather than repeating max-width and margin on every section individually.
<section class="hero">
<div class="wrap">
<h1>Invoice clients in 30 seconds, not 30 minutes</h1>
<p>Built for freelancers who bill by the hour and hate spreadsheets.</p>
<a href="#signup" class="btn">Start free trial</a>
</div>
</section>
<style>
.hero { text-align: center; padding: 96px 0 72px; }
.hero h1 { font-size: clamp(30px, 5vw, 46px); margin-bottom: 16px; }
.hero p { color: var(--muted); font-size: 18px; margin-bottom: 28px; }
.btn {
display: inline-block;
background: var(--accent);
color: white;
padding: 14px 28px;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
}
</style>
Note there's no image or illustration required here — the headline and subhead are doing the persuading. Adding a product screenshot later is fine, but it should support this text, not replace it.
<section class="features">
<div class="wrap features-grid">
<div class="feature">
<h3>Auto-reminders</h3>
<p>Late invoices follow up themselves, politely.</p>
</div>
<div class="feature">
<h3>One-click PDF</h3>
<p>No template wrangling — export in one click.</p>
</div>
<div class="feature">
<h3>Stripe payouts</h3>
<p>Get paid directly, no manual bank transfers.</p>
</div>
</div>
</section>
<style>
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 32px;
padding: 64px 0;
}
.feature h3 { margin-bottom: 8px; }
.feature p { color: var(--muted); font-size: 15px; }
</style>
auto-fit with minmax(220px, 1fr) means this grid reflows from three columns down to one automatically as the viewport narrows — no separate mobile media query needed for the column count, which is one less thing to keep in sync as features get added or removed later.
Static HTML/CSS pages still need a way to actually receive a form submission. Point the form at a hosted form service instead of writing backend code — the markup itself stays plain HTML:
<section id="signup" class="signup">
<div class="wrap">
<form action="https://formspree.io/f/your-id" method="POST">
<input type="email" name="email" placeholder="[email protected]" required>
<button type="submit" class="btn">Get started</button>
</form>
</div>
</section>
Because this is a standard HTML form submission (not a JavaScript fetch call), it works even if JavaScript fails to load for any reason — a real resilience advantage a JS-dependent form doesn't have by default. Most hosted form services also handle spam filtering and email notifications out of the box, so there's genuinely nothing server-side left to build for a simple email-capture form.
UIXDraft's landing page templates follow this exact plain HTML/CSS approach — no build step, no framework, ready to edit and deploy.
Browse the templates →On mobile, scrolling back up to a hero button is friction most visitors won't bother with. A sticky bottom bar keeps the primary action reachable at all times, and it's pure CSS positioning — no scroll-listener script required:
<div class="mobile-cta">
<a href="#signup" class="btn">Get started</a>
</div>
<style>
.mobile-cta { display: none; }
@media (max-width: 640px) {
.mobile-cta {
display: block;
position: fixed;
bottom: 0; left: 0; right: 0;
padding: 12px 16px;
background: white;
box-shadow: 0 -4px 16px rgba(0,0,0,.08);
}
.mobile-cta .btn { display: block; text-align: center; }
}
</style>
position: fixed combined with a media query that only activates the bar below 640px keeps this purely presentational — the same HTML renders differently at each breakpoint with zero script involved, which matters for a component that shows up on every scroll position.
This approach earns its simplicity on a single page with no interactive state beyond a form submission. The moment the page needs client-side interactivity that persists across views — a shopping cart, a multi-step wizard, live-updating content — a framework starts paying for itself. The mistake isn't picking a framework; it's reaching for one by default on a page simple enough that forty lines of CSS would have shipped faster and with less to maintain. A useful gut check: if the page can be described in one sentence without the word "state" appearing in it, plain HTML/CSS is very likely enough.
Because there's no compilation step, deployment is copying the file. Static hosts like Cloudflare Pages, Netlify, or GitHub Pages accept a raw HTML file directly — drag the folder in, or connect a git repo, and it's live with a free SSL certificate in under a minute. There's no npm install, no build command, and nothing that can fail between "the file works locally" and "the file works in production," because they're the exact same file.
Yes — arguably better than a heavy JavaScript-rendered page, since there's no client-side rendering delay for search crawlers to work around. Fast load times and clean semantic markup are ranking factors search engines can evaluate immediately, without waiting for JavaScript to execute.
CSS handles most landing page animation needs on its own — transition for hover states, @keyframes for looping effects, and the :target pseudo-class for simple toggle interactions triggered by an anchor link. A JS animation library is only worth adding for genuinely complex sequenced animations — coordinating many elements with precise timing dependencies — that CSS alone can't express cleanly.
<style> tag instead of a separate file?For a single-page site, no — it actually saves an extra network request, which can measurably help first-paint speed on a simple page. Once a project spans multiple pages sharing the same styles, moving to a separate linked stylesheet avoids duplicating the same CSS across every file.