A few months ago I got tired of the same loop: spin up a React project, install Tailwind, configure PostCSS, add a dozen dev dependencies, and end up with a 200 MB node_modules folder — all for a single landing page that serves static content.
So I set myself a challenge: build a collection of production-ready landing page templates using nothing but vanilla HTML and CSS. No frameworks, no build tools, no JavaScript dependencies. Just files you can open in a browser and deploy anywhere.
I ended up building eight of them — SaaS, Portfolio, Startup, Mobile App, Agency, Course, Newsletter, and Restaurant — and I learned a surprising amount along the way. Here are the big takeaways.
CSS Custom Properties Make Theming Ridiculously Easy
The single best decision I made was putting every color, font size, spacing value, and border radius into CSS custom properties on :root. Each template has a theme block at the top of the stylesheet that looks roughly like this:
:root {
--color-primary: #4f46e5;
--color-primary-light: #818cf8;
--color-surface: #ffffff;
--color-text: #1e293b;
--color-text-muted: #64748b;
--font-heading: 'Inter', sans-serif;
--font-body: 'Inter', sans-serif;
--radius-sm: 0.375rem;
--radius-md: 0.75rem;
--radius-lg: 1.5rem;
--space-unit: 1rem;
}
Want a dark mode? Override a handful of variables inside a prefers-color-scheme media query and you're done. Want to re-skin the entire page for a different brand? Change six hex values. No find-and-replace across 400 lines of CSS.
The trick that really leveled things up was using hsl() with separate channel variables so I could derive hover states and transparent overlays from a single source color:
:root {
--primary-h: 239;
--primary-s: 84%;
--primary-l: 67%;
--color-primary: hsl(var(--primary-h), var(--primary-s), var(--primary-l));
--color-primary-hover: hsl(var(--primary-h), var(--primary-s), 57%);
--color-primary-ghost: hsla(var(--primary-h), var(--primary-s), var(--primary-l), 0.12);
}
One variable set, multiple derived values. No Sass needed.
Responsive Design Without a Framework Is Not That Hard
I was bracing myself for responsive pain, but modern CSS has closed the gap dramatically. The approach I settled on uses just three tools:
1. A fluid type scale. Instead of breakpoint-driven font sizes, clamp() handles everything in one declaration:
.hero-title {
font-size: clamp(2rem, 5vw + 0.5rem, 4.5rem);
line-height: 1.1;
}
That single line gives you a title that looks great on a 320px phone and a 2560px ultrawide — no media queries at all.
2. Auto-fit grids. For card layouts (pricing tiers, feature grids, team members), this one-liner replaced dozens of breakpoint rules:
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
}
Cards wrap naturally when space runs out. The 280px minimum keeps them readable; 1fr lets them stretch on larger screens.
3. Container queries for component-level control. On a couple of the templates I used container queries so that a testimonial card, for example, switches layout based on its own width rather than the viewport. That makes the components genuinely portable — drop them into a sidebar or a full-width section and they just adapt.
I ended up needing only two or three @media breakpoints per template, mostly for navigation toggling and hero layout shifts. Everything else was handled by fluid sizing and intrinsic layouts.
CSS-Only Animations That Don't Feel Cheap
I wanted subtle motion — fade-ins on scroll, floating background shapes, gentle hover lifts — without pulling in a JS animation library. Two patterns carried me through almost every case.
Scroll-triggered fade-in with @keyframes and animation-timeline:
.fade-up {
opacity: 0;
transform: translateY(2rem);
animation: fadeUp 0.6s ease forwards;
animation-timeline: view();
animation-range: entry 0% entry 40%;
}
@keyframes fadeUp {
to {
opacity: 1;
transform: translateY(0);
}
}
No Intersection Observer, no scroll library. Pure CSS. Browser support is solid in Chromium and Firefox now, and the fallback is that elements just appear immediately — totally fine.
Hover lifts with box-shadow transitions:
.card {
transition: transform 0.25s ease, box-shadow 0.25s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1);
}
Simple, performant (only composited properties), and it adds a tactile feel that makes the page feel polished.
Things I Wouldn't Do Again
Skipping a CSS reset at first. I started without one and quickly ran into cross-browser margin and padding inconsistencies. A minimal modern reset (box-sizing border-box, zero margins on body, sensible image defaults) saved me hours of debugging.
Over-nesting selectors. Without a preprocessor to keep things tidy, deep nesting in vanilla CSS gets ugly fast. I moved to a flat BEM-ish naming convention early on (.hero__title, .pricing-card--featured) and specificity problems disappeared.
Not testing on actual phones early enough. The responsive stuff looked great in DevTools' device emulator. On a real iPhone SE, a couple of the hero sections had overflow issues from absolute-positioned decorative elements. Lesson learned: test on hardware, not just emulators.
The Eight Templates
Each template targets a different use case so I could explore different layout challenges:
- SaaS — feature grids, pricing table, CTA-heavy
- Portfolio — image-forward, masonry-style project grid
- Startup — bold typography, investor-friendly metrics section
- Mobile App — phone mockup hero, app store badges, feature carousel
- Agency — case study cards, team section, client logos
-
Course — curriculum accordion (CSS-only with
<details>), instructor bio - Newsletter — single-focus CTA, minimal distractions, social proof
- Restaurant — menu grid, reservation form, full-bleed food imagery
All of them are fully responsive, use semantic HTML, and score 90+ on Lighthouse across the board. Every file is self-contained — open the HTML, see the page.
Grab Them If They're Useful
I packaged all eight templates up on Gumroad if anyone wants them: Premium Landing Page Templates -- 8 Pack. They're $29 for the full set. No frameworks, no build steps — just clean HTML and CSS you can drop into any project, customize in minutes, and deploy on Netlify, Vercel, GitHub Pages, or literally any static host.
Wrapping Up
The biggest thing I took away from this project is that vanilla HTML and CSS in 2026 is genuinely capable. Between clamp(), auto-fit grids, CSS custom properties, container queries, and scroll-driven animations, you can build polished, responsive pages without touching a single npm install.
That said, I know plenty of developers prefer component-based frameworks for maintainability at scale, and that's totally valid.
What's your go-to approach for landing pages? Do you reach for a framework first, use a template, or hand-code everything from scratch? I'd love to hear what works for you in the comments.
Top comments (0)