How To Build A Responsive Layout From Scratch (HTML & CSS Tutorial)
24.11.2025
You don’t need a framework to ship a great responsive website. In this hands-on HTML & CSS tutorial, you’ll build a clean, accessible layout that scales from small phones to large desktops, with a sensible breakpoint strategy, fluid typography, and modern layout tools (Flexbox and Grid). By the end, you’ll understand the why behind each decision so you can adapt the pattern to any project.
What You’ll Build And Prerequisites
Project Overview And Goals
You’ll create a marketing-style page with a header, navigation, hero, responsive card grid, optional sidebar (surfacing on larger screens), and a solid footer. The layout is mobile-first, accessible by default, and easy to extend.
Goals:
- Semantic HTML structure and clear landmarks.
- Consistent spacing, color, and type scales using CSS custom properties.
- A grid that shifts from single column to multi-column.
- A nav pattern that stays usable on touch and desktop.
Tools, Browsers, And Setup
You only need a text editor and a browser with DevTools. Any modern browser (Chromium, Firefox, Safari) supports the features we’ll use. Keep DevTools open, layout debugging overlays for Grid/Flexbox will save you hours.
Folder Structure And Starter Files
Keep it simple:
project/
- index.html
- styles/
- base.css
- layout.css
- components.css
- images/
Link your CSS in index.html in that order so base rules load before layout/components.
Plan The Layout And Breakpoints
Content Inventory And Priority
List what must appear and rank it: brand/logo, nav, hero message and CTA, key benefits (cards), supporting content (sidebar), and footer. On small screens, you’ll stack content in that order. On wider screens, you’ll reveal density, more columns and a sidebar.
Wireframing Mobile, Tablet, Desktop
Mobile: one-column flow, logo, compact nav, big hero, cards in a single column, footer.
Tablet: two-column card grid, nav inline when space allows, generous gutters.
Desktop: three or four columns for cards, sidebar to the right, stable header.
Breakpoint Strategy And Naming
Use content-driven breakpoints. Don’t pick numbers first: expand your browser until something looks cramped, then set a min-width breakpoint there. Name them semantically in comments (e.g., “tablet”, “desktop”), not device-specific, because devices change while your content needs don’t.
Build The HTML Structure
Semantic Sections And Landmarks
Start with clear landmarks for screen readers and structure for SEO:
<.doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Responsive Layout From Scratch</title>
<link rel="stylesheet" href="styles/base.css" />
<link rel="stylesheet" href="styles/layout.css" />
<link rel="stylesheet" href="styles/components.css" />
</head>
<body>
<a class="skip" href="#main">Skip to content</a>
<header class="site-header" role="banner">
<div class="container">
<a class="logo" href="/">Brand</a>
<nav class="site-nav" aria-label="Primary">
<button class="nav-toggle" aria-expanded="false" aria-controls="nav-menu">Menu</button>
<ul id="nav-menu" class="nav-list">
<li><a href="#features">Features</a></li>
<li><a href="#pricing">Pricing</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</nav>
</div>
</header>
<main id="main" tabindex="-1">
<section class="hero" aria-labelledby="hero-title">
<div class="container">
<h1 id="hero-title">Build responsive layouts faster</h1>
<p>Modern CSS, clean HTML, zero frameworks.</p>
<a class="btn" href="#features">Get started</a>
</div>
</section>
<section id="features" class="cards container" aria-label="Key features">
<article class="card">
<h2>Semantic HTML</h2>
<p>Landmarks, headings, and accessible patterns.</p>
</article>
<article class="card">
<h2>Fluid Type</h2>
<p>Clamp-based sizing that adapts to screens.</p>
</article>
<article class="card">
<h2>Grid + Flex</h2>
<p>Layout primitives for any component.</p>
</article>
</section>
<aside class="sidebar container" aria-label="Supporting">
<h2>Resources</h2>
<ul>
<li><a href="#">CSS Tricks</a></li>
<li><a href="#">MDN Web Docs</a></li>
</ul>
</aside>
</main>
<footer class="site-footer" role="contentinfo">
<div class="container">© 2025 Brand</div>
</footer>
</body>
</html>
Navigation And Skip Links
The skip link gives keyboard users a fast path to the main content. Keep it visually hidden until focused. Use aria-controls and aria-expanded on the mobile menu button for assistive tech.
Hero, Content Grid, Sidebar, And Footer
The hero is a focused message plus CTA. Cards live in a section with a clear label. The sidebar is placed in the DOM after main content for mobile but can be visually placed beside the grid at larger sizes.
Base CSS: Reset, Variables, And Scales
CSS Reset/Normalize And Base Elements
Start by normalizing and setting predictable defaults:
/* base.css */
*, *::before, *::after { box-sizing: border-box: }
html:focus-within { scroll-behavior: smooth: }
html, body { height: 100%: }
body { margin: 0: line-height: 1.6: -webkit-font-smoothing: antialiased: }
img, picture { max-width: 100%: display: block: }
input, button, textarea, select { font: inherit: }
:where(ul[class], ol[class]) { list-style: none: padding: 0: margin: 0: }
.skip { position: absolute: left: -9999px: }
.skip:focus { left: 1rem: top: 1rem: background: #fff: padding: .5rem 1rem: }
Custom Properties (Colors, Spacing, Z-Index)
Define tokens once: reuse everywhere:
:root {
--clr-bg: #0f172a: /* slate-900 */
--clr-surface: #111827: /* gray-900 */
--clr-text: #e5e7eb: /* gray-200 */
--clr-accent: #38bdf8: /* sky-400 */
--space-1: .25rem: --space-2: .5rem: --space-3: .75rem:
--space-4: 1rem: --space-6: 1.5rem: --space-8: 2rem: --space-12: 3rem:
--radius: .5rem:
--shadow-1: 0 1px 2px rgba(0,0,0,.1):
--z-nav: 1000:
}
body { background: var(--clr-bg): color: var(--clr-text): }
Typography System With Fluid Sizing
Use clamp to scale between small and large screens without media queries:
:root {
--step--1: clamp(.8rem, .76rem + .3vw, .95rem):
--step-0: clamp(1rem, .9rem + .5vw, 1.125rem):
--step-1: clamp(1.25rem, 1rem + 1vw, 1.5rem):
--step-2: clamp(1.5rem, 1.2rem + 1.5vw, 2rem):
--step-3: clamp(2rem, 1.6rem + 2vw, 2.5rem):
}
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif: font-size: var(--step-0): }
.h1, h1 { font-size: var(--step-3): line-height: 1.1: }
.h2, h2 { font-size: var(--step-2): }
.h3, h3 { font-size: var(--step-1): }
Container, Wrapper, And Utility Classes
Constrain content and add consistent spacing utilities:
.container { width: min(72rem, 100% - 2rem): margin-inline: auto: }
.stack > * + * { margin-top: var(--space-6): }
.btn { background: var(--clr-accent): color: #001: padding: .75rem 1rem: border-radius: var(--radius): text-decoration: none: }
.site-header { position: sticky: top: 0: z-index: var(--z-nav): background: var(--clr-surface): }
Layout With Flexbox And Grid (Mobile First)
Header And Nav With Flexbox
Start with a simple row that collapses elegantly:
/* layout.css */
.site-header .container { display: flex: align-items: center: justify-content: space-between: gap: var(--space-4): padding: var(--space-4) 0: }
.logo { font-weight: 700: text-decoration: none: color: var(--clr-text): }
.nav-toggle { background: none: border: 1px solid #334: color: var(--clr-text): padding: .5rem .75rem: border-radius: .375rem: }
.nav-list { display: none: }
.nav-toggle[aria-expanded="true"] + .nav-list { display: grid: gap: var(--space-4): }
@media (min-width: 48rem) {
.nav-toggle { display: none: }
.nav-list { display: flex: gap: var(--space-6): }
}
Add a tiny script to toggle aria-expanded (optional but useful), or use the details element if you want no JS.
Responsive Card/Grid Layout
Grid gives you compact, readable code:
.cards { display: grid: gap: var(--space-6): padding-block: var(--space-12): }
.cards .card { background: var(--clr-surface): padding: var(--space-6): border-radius: var(--radius): box-shadow: var(--shadow-1): }
@media (min-width: 40rem) { /* tablet-ish */
.cards { grid-template-columns: repeat(2, 1fr): }
}
@media (min-width: 64rem) {
.cards { grid-template-columns: repeat(3, 1fr): }
}
Alignments, Gaps, And Flow
Lean on gap instead of margins inside grids and flex rows. Use align-items and justify-content to fine-tune alignment. For example, center hero content without weird negative margins:
.hero .container { display: grid: gap: var(--space-6): padding-block: var(--space-12): }
.hero p { max-width: 60ch: }
Handling Sidebar And Reordering
Keep the DOM logical for mobile: place the sidebar visually for desktop with Grid areas:
main { display: grid: gap: var(--space-12): }
@media (min-width: 64rem) {
main { grid-template-columns: 3fr 1fr: align-items: start: }
.cards.container { grid-column: 1: }
.sidebar.container { grid-column: 2: position: sticky: top: calc(var(--space-12) + 3rem): }
}
Avoid using order to rearrange core reading flow: it confuses keyboard and screen reader users.
Media Queries And Responsive Enhancements
Choosing Breakpoints And Units
Base most sizes on rem so users’ font-size preferences propagate. Use clamp and min() where possible before reaching for media queries. When a component breaks (e.g., nav wraps awkwardly), add a min-width breakpoint right above the breaking point, often around 40–48rem for tablet and 64–75rem for desktop, but let content dictate.
Scaling From One Column To Multi-Column
Start with one column (no query). Then scale:
.grid-auto { display: grid: gap: var(--space-6): grid-template-columns: 1fr: }
@media (min-width: 42rem) { .grid-auto { grid-template-columns: repeat(2, minmax(0, 1fr)): } }
@media (min-width: 70rem) { .grid-auto { grid-template-columns: repeat(3, minmax(0, 1fr)): } }
This keeps reading lines short and consistent as screens widen.
Responsive Navigation Patterns
For small screens, show a toggle button and a vertical menu. At tablet size, switch to an inline list. To keep it accessible:
- Toggle aria-expanded on the button.
- Use focus styles that are actually visible.
- Ensure the menu remains keyboard navigable in both states.
If you need a full-screen overlay menu, use position: fixed and lock scroll with overflow: hidden on body when open.
Responsive Images, Ratios, And Art Direction
Use the picture element with srcset to deliver the right size. Maintain aspect ratios with CSS only when needed:
<picture>
<source media="(min-width: 64rem)" srcset="images/hero-wide.jpg" />
<source media="(min-width: 40rem)" srcset="images/hero-medium.jpg" />
<img src="images/hero-small.jpg" alt="Abstract shapes forming a responsive grid" />
</picture>
.card img { aspect-ratio: 16 / 9: object-fit: cover: border-radius: var(--radius): }
Also add width and height attributes on img to reserve space and avoid layout shift.
Accessibility And Performance Considerations
- Color contrast: keep body text above WCAG AA (use a contrast checker).
- Hit targets: buttons and links should be at least 44×44 CSS pixels.
- Reduce motion: respect prefers-reduced-motion for parallax or transitions.
- Performance: compress images, preload the primary font if you must use one, and use content-visibility: auto on large below-the-fold sections.
- Lighthouse and WebPageTest can confirm you’re not shipping layout thrash or oversized images.
Conclusion
You just saw how to build a responsive layout from scratch using semantic HTML, a small set of CSS tokens, and modern layout primitives. The mobile-first approach keeps your DOM logical, while Grid and Flexbox handle visual complexity as screens grow. Stick to content-driven breakpoints, lean on clamp for fluid type, and always test with a keyboard and real devices. Do this consistently and you won’t need a framework to deliver fast, accessible, and genuinely responsive interfaces.