Skip to main content

πŸ—οΈ Weekend Project: Modern CSS Layout

This is the capstone for Module 6 β€” a guided, hands-on build where you turn a folder of empty files into a polished, responsive, multi-page marketing site. No frameworks, no shortcuts: just Grid, Flexbox, custom properties, and a few tasteful animations, all authored by you. Follow the milestones and you'll finish the weekend with something worth putting in a portfolio.

🎯 Learning Objectives

By the end of this project, you will be able to:

  • Plan and scaffold a multi-page site with a shared, layered CSS architecture
  • Build a mobile-first responsive layout using CSS Grid for page structure and Flexbox for components
  • Drive a consistent design with custom properties (design tokens) for color, type, and spacing
  • Add purposeful animations that respect prefers-reduced-motion
  • Self-assess your work against a concrete "what good looks like" bar and ship it

Estimated Time: 8–12 hours across a weekend  β€’  Difficulty: Intermediate

Hands-on: Build a complete four-page responsive website from scratch, following the milestones below.

In This Lesson

The Project Brief

You'll build a four-page website for a fictional small business of your choosing β€” a cafΓ©, a photographer, a bike-repair shop, a community garden, whatever inspires you. The content is yours; the engineering bar is fixed. This project exists to prove you can assemble a real, coherent site from the Module 6 techniques without leaning on Bootstrap or Tailwind.

πŸ“– The non-negotiables

Four pages: Home, About, Services (or Products/Menu), and Contact β€” sharing one header and footer.

Layout: CSS Grid for at least one major page section; Flexbox for component-level arrangement (nav, cards, form rows).

Design tokens: a :root block of custom properties for color, typography, and spacing β€” used everywhere, hard-coded nowhere.

Responsive: mobile-first, correct from 320 px to 1440 px, with a working mobile navigation.

Motion: at least three distinct animations, all wrapped in a prefers-reduced-motion guard.

Constraint: vanilla HTML and CSS only. A tiny sprinkle of JavaScript for the mobile menu toggle is allowed; no CSS frameworks.

Why a full site instead of another isolated demo? Because the hard part of front-end work isn't any single technique β€” it's making a dozen techniques agree with each other across four pages and three screen sizes without the CSS turning into spaghetti. That integration skill is exactly what this weekend trains.

πŸ’‘ Scope it before you love it

The most common way this project goes sideways is over-scoping on Saturday morning. Pick a business you can describe in one sentence, write three sentences of real-ish copy per page, and grab placeholder images from a service like Lorem Picsum. Spend your weekend on layout and polish, not on inventing a brand.

The Milestone Map

Rather than "start coding and hope," you'll work through five milestones. Each one leaves you with something that works end-to-end, so if you run out of weekend you still have a shippable site β€” just with fewer bells. Build in this order:

flowchart LR M1[1 Β· Scaffold
& Tokens] --> M2[2 Β· Shared
Shell] M2 --> M3[3 Β· Page
Layouts] M3 --> M4[4 Β· Responsive
& Motion] M4 --> M5[5 Β· Polish
& Ship] M5 -.->|found a bug?| M3

Notice the dotted line: polishing almost always sends you back to fix a layout. That loop is normal and healthy β€” it's the "look back and refine" habit that separates a finished project from an abandoned one.

Home page wireframe A stacked wireframe: sticky header with logo and navigation, a hero band, a three-column feature grid, and a footer. Logo Home Β· About Β· Services Β· Contact Hero β€” headline Β· subtext Β· call-to-action Flexbox: text column + image column CSS Grid: repeat(3, 1fr) feature cards Footer β€” links & copyright
Figure 1 β€” A rough home-page wireframe. Sketch one like this for every page before you write CSS; it turns "make it look nice" into a concrete list of boxes.

Milestone 1 β€” Scaffold & Tokens

Goal: four HTML files that load a layered set of stylesheets, plus a single source of truth for your design tokens. When this milestone is done, every page is blank but correctly wired.

Folder structure

A layered CSS structure keeps a growing project sane. Each file has one job, and files load from most-generic to most-specific so later rules can build on earlier ones:

weekend-project/
β”œβ”€β”€ index.html          # Home
β”œβ”€β”€ about.html
β”œβ”€β”€ services.html
β”œβ”€β”€ contact.html
β”œβ”€β”€ css/
β”‚   β”œβ”€β”€ reset.css       # normalize the browser
β”‚   β”œβ”€β”€ tokens.css      # custom properties (design tokens)
β”‚   β”œβ”€β”€ base.css        # element defaults: type, links, containers
β”‚   β”œβ”€β”€ layout.css      # grid/flex utilities, header, footer
β”‚   β”œβ”€β”€ components.css  # buttons, cards, forms
β”‚   └── animations.css  # keyframes + reduced-motion guard
└── img/

The shared <head>

Every page links the same stylesheets in the same order. Order matters β€” this cascade is deliberate:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Home β€” Corner Bloom CafΓ©</title>
  <meta name="description" content="A neighborhood cafΓ© serving slow coffee and fresh pastries.">

  <link rel="stylesheet" href="css/reset.css">
  <link rel="stylesheet" href="css/tokens.css">
  <link rel="stylesheet" href="css/base.css">
  <link rel="stylesheet" href="css/layout.css">
  <link rel="stylesheet" href="css/components.css">
  <link rel="stylesheet" href="css/animations.css">
</head>

Design tokens

Tokens are the DNA of a consistent site. Define them once; reference them everywhere. Changing your brand color later becomes a one-line edit instead of a find-and-replace nightmare:

/* tokens.css */
:root {
  /* Color */
  --color-primary: #b4531f;
  --color-primary-dark: #8f3f14;
  --color-accent: #2f855a;
  --color-text: #24211e;
  --color-text-light: #6b645d;
  --color-bg: #ffffff;
  --color-bg-alt: #f6f2ec;
  --color-border: #e4ddd3;

  /* Typography β€” use a fluid scale via clamp() */
  --font-body: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-display: Georgia, "Times New Roman", serif;
  --step-0: 1rem;
  --step-1: clamp(1.15rem, 1rem + 0.7vw, 1.4rem);
  --step-2: clamp(1.5rem, 1.2rem + 1.4vw, 2.1rem);
  --step-3: clamp(2rem, 1.4rem + 2.6vw, 3.2rem);

  /* Spacing (a consistent scale beats magic numbers) */
  --space-xs: 0.5rem;
  --space-sm: 0.75rem;
  --space-md: 1.25rem;
  --space-lg: 2rem;
  --space-xl: 3.5rem;

  /* Layout & motion */
  --container: 1200px;
  --radius: 12px;
  --shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
  --transition: 0.25s ease;
}

βœ… Milestone 1 done when…

All four HTML pages open in the browser, link every stylesheet without a 404 in the console, and your tokens.css defines the full palette, type scale, and spacing scale. The pages can look completely unstyled β€” that's fine.

Milestone 2 β€” Shared Shell

Goal: a header and footer that look identical on all four pages, a sensible typographic baseline, and a centered content container. This is the frame every page hangs inside.

Base layer

Set element defaults once so individual pages need almost no per-element styling:

/* base.css */
body {
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.6;
  color: var(--color-text);
  background: var(--color-bg);
}

h1, h2, h3 {
  font-family: var(--font-display);
  line-height: 1.15;
  margin-bottom: var(--space-sm);
}
h1 { font-size: var(--step-3); }
h2 { font-size: var(--step-2); }
h3 { font-size: var(--step-1); }

p { margin-bottom: var(--space-md); }

a {
  color: var(--color-primary);
  text-decoration: none;
  transition: color var(--transition);
}
a:hover, a:focus-visible { color: var(--color-primary-dark); text-decoration: underline; }

.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--space-md);
}

Header with Flexbox nav

The header uses Flexbox to push the logo and nav to opposite ends. On mobile the nav collapses behind a toggle button; from the first tablet breakpoint up, it shows inline:

<header class="site-header">
  <div class="container site-header__inner">
    <a class="site-header__logo" href="index.html">Corner Bloom</a>
    <button class="nav-toggle" aria-expanded="false" aria-controls="primary-nav">
      <span class="sr-only">Menu</span>☰
    </button>
    <nav id="primary-nav" class="site-nav">
      <a href="index.html" aria-current="page">Home</a>
      <a href="about.html">About</a>
      <a href="services.html">Menu</a>
      <a href="contact.html">Contact</a>
    </nav>
  </div>
</header>
/* layout.css */
.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--color-bg);
  border-bottom: 1px solid var(--color-border);
}
.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: var(--space-sm);
}
.site-header__logo { font-family: var(--font-display); font-size: var(--step-1); }

.site-nav { display: flex; gap: var(--space-md); }
.site-nav a[aria-current="page"] { color: var(--color-primary); font-weight: 600; }

/* Mobile: hide inline nav, show toggle */
.nav-toggle { display: none; font-size: 1.5rem; background: none; border: 0; cursor: pointer; }
@media (max-width: 47.9em) {
  .nav-toggle { display: block; }
  .site-nav {
    display: none;
    position: absolute;
    inset: 100% 0 auto 0;
    flex-direction: column;
    gap: 0;
    background: var(--color-bg);
    border-bottom: 1px solid var(--color-border);
    padding: var(--space-sm) var(--space-md);
  }
  .site-nav.is-open { display: flex; }
}

The one bit of JavaScript you're allowed β€” toggling that menu β€” is genuinely tiny:

const toggle = document.querySelector('.nav-toggle');
const nav = document.querySelector('#primary-nav');

toggle.addEventListener('click', () => {
  const open = nav.classList.toggle('is-open');
  toggle.setAttribute('aria-expanded', String(open));
});

βœ… Milestone 2 done when…

The header sticks to the top, the footer sits at the bottom, and both are byte-for-byte identical across all four pages. The mobile menu opens and closes, and aria-expanded flips with it.

Milestone 3 β€” Page Layouts

Goal: real content laid out on all four pages, using Grid for the big structural blocks and Flexbox for the pieces inside them. Build the Home page first β€” it exercises the most patterns β€” then reuse those components everywhere else.

Hero with Flexbox

/* components.css */
.hero {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  align-items: center;
  padding-block: var(--space-xl);
}
@media (min-width: 48em) {
  .hero { flex-direction: row; }
  .hero__text, .hero__media { flex: 1; }
}
.btn {
  display: inline-block;
  padding: var(--space-sm) var(--space-lg);
  border-radius: var(--radius);
  background: var(--color-primary);
  color: #fff;
  font-weight: 600;
  transition: transform var(--transition), box-shadow var(--transition);
}
.btn:hover { transform: translateY(-2px); box-shadow: var(--shadow); text-decoration: none; }

Feature / service grid with CSS Grid

This is the "at least one major layout in Grid" requirement. The magic line is auto-fit + minmax(): cards flow into as many columns as fit, with zero media queries needed for the grid itself:

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: var(--space-lg);
}
.feature-card {
  background: var(--color-bg-alt);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-lg);
  transition: transform var(--transition), box-shadow var(--transition);
}
.feature-card:hover { transform: translateY(-5px); box-shadow: var(--shadow); }
<section class="container">
  <h2>What we do</h2>
  <div class="card-grid">
    <article class="feature-card">
      <h3>Slow coffee</h3>
      <p>Single-origin beans, pulled to order.</p>
    </article>
    <article class="feature-card">
      <h3>Fresh pastries</h3>
      <p>Baked in-house before sunrise.</p>
    </article>
    <article class="feature-card">
      <h3>A quiet corner</h3>
      <p>Fast Wi-Fi and slow afternoons.</p>
    </article>
  </div>
</section>

Contact form

Your Contact page needs one styled form with clear focus states. Native HTML validation (required, type="email") does the heavy lifting; CSS makes it feel intentional:

.field { margin-bottom: var(--space-md); }
.field label { display: block; margin-bottom: var(--space-xs); font-weight: 600; }
.field input,
.field textarea {
  width: 100%;
  padding: var(--space-sm);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  transition: border-color var(--transition), box-shadow var(--transition);
}
.field input:focus-visible,
.field textarea:focus-visible {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgb(180 83 31 / 0.18);
}
/* Native validation feedback, styled */
.field input:user-invalid,
.field textarea:user-invalid { border-color: var(--color-accent); }

⚠️ Watch for these traps

Don't set fixed heights on cards or heroes β€” content varies, and fixed heights cause overflow. Let content dictate height and use padding for breathing room.

Don't nest Grid inside Grid just because you can. Reach for Flexbox when you're arranging items in a single row or column; reserve Grid for two-dimensional structure.

Use :focus-visible, not :focus, so keyboard users get a ring while mouse-clickers don't see stray outlines.

βœ… Milestone 3 done when…

All four pages have their real content in place. The Home hero and the service grid render correctly on desktop, and the contact form's inputs show a clear focus ring. It doesn't need to be responsive yet β€” that's next.

Milestone 4 β€” Responsive & Motion

Goal: the site reads well from a 320 px phone to a 1440 px monitor, and three well-chosen animations bring it to life without being annoying.

Mobile-first breakpoints

You've already been writing base styles for the smallest screen and adding min-width queries to enhance upward β€” that's mobile-first, and it's the whole strategy. Keep breakpoints to a small, memorable set expressed in em so they respect the user's font size:

/* Base styles = mobile. Enhance upward: */
@media (min-width: 48em)  { /* ~768px β€” tablet */ }
@media (min-width: 64em)  { /* ~1024px β€” desktop */ }

Three animations, done right

The rule for tasteful motion: animate only transform and opacity (they're cheap and don't trigger layout), keep durations short, and always honor prefers-reduced-motion. Here are three that cover your requirement:

/* animations.css */

/* 1 β€” entrance fade-up for hero + cards */
@keyframes fade-up {
  from { opacity: 0; transform: translateY(1.5rem); }
  to   { opacity: 1; transform: translateY(0); }
}
.reveal { animation: fade-up 0.6s ease-out both; }

/* 2 β€” staggered reveal for a grid of cards */
.card-grid > * { animation: fade-up 0.5s ease-out both; }
.card-grid > *:nth-child(2) { animation-delay: 0.1s; }
.card-grid > *:nth-child(3) { animation-delay: 0.2s; }

/* 3 β€” gentle attention pulse on the primary CTA */
@keyframes pulse {
  50% { transform: scale(1.04); }
}
.btn--cta:hover { animation: pulse 1.2s ease-in-out infinite; }

/* The guard β€” non-negotiable */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

πŸ’‘ Test the small screen for real

Open DevTools (F12), toggle the device toolbar, and set the width to 320 px. This is where layouts break: text overflows, images push the page wide, and buttons collide. If it survives 320 px, everything larger tends to take care of itself.

βœ… Milestone 4 done when…

There is no horizontal scrollbar at 320 px, the layout reflows sensibly at each breakpoint, and your three animations play on load/hover β€” then vanish the moment you enable "reduce motion" in your OS settings.

Milestone 5 β€” Polish & Ship

Goal: the difference between "it works" and "it's finished." This milestone is a series of quick passes, each catching a class of problems.

The accessibility pass

  • Contrast: run your text/background pairs through a checker; aim for at least 4.5:1 on body text.
  • Keyboard: tab through every page. Can you reach and activate every link, button, and field? Is the focus ring always visible?
  • Images: every meaningful <img> has descriptive alt text; purely decorative images get alt="".
  • Landmarks: one <header>, one <main>, one <footer> per page, with headings in a sensible order.

The consistency pass

  • Search your CSS for hex colors and raw pixel values that should be tokens. Replace them.
  • Confirm the header, footer, and buttons look identical on every page.
  • Check spacing rhythm β€” are you using the spacing scale, or sprinkling arbitrary margins?

The performance pass

  • Compress images (a hero photo should be well under 300 KB) and set explicit width/height to prevent layout shift.
  • Delete dead CSS β€” rules for components you removed along the way.
  • Run Lighthouse (DevTools β†’ Lighthouse) and skim the top suggestions.

Ship it

Drag the project folder onto Netlify Drop and you'll have a live URL in under a minute β€” no account juggling, no build config. A real link you can share is the proper end to this project.

βœ… Milestone 5 done when…

Lighthouse accessibility and best-practices scores are green, there are no console errors, every value that should be a token is one, and the site is deployed to a public URL.

Build Checklist

Print this or keep it open in a tab. If you can honestly tick every box, you've met the brief.

πŸ“‹ Structure & architecture

  • ☐ Four pages (Home, About, Services, Contact) sharing one header & footer
  • ☐ Semantic HTML: header, nav, main, section, article, footer
  • ☐ Layered CSS files, loaded generic β†’ specific
  • ☐ A :root token block powering color, type, and spacing

πŸ“‹ Layout & responsiveness

  • ☐ CSS Grid drives at least one major section
  • ☐ Flexbox arranges components (nav, hero, form rows)
  • ☐ Mobile-first; correct from 320 px to 1440 px with no horizontal scroll
  • ☐ Working mobile navigation with correct aria-expanded

πŸ“‹ Motion, forms & polish

  • ☐ Three distinct animations, all transform/opacity based
  • ☐ A prefers-reduced-motion guard that actually disables them
  • ☐ One styled form with visible :focus-visible states
  • ☐ Alt text, sufficient contrast, keyboard-navigable
  • ☐ Deployed to a public URL

What Good Looks Like

"Done" is a range. Here's how to tell roughly where your project lands β€” aim for at least "Solid," and stretch for "Standout" if the weekend cooperates.

DimensionNeeds workSolid βœ…Standout ⭐
Layout Overlaps or breaks at some widths Grid + Flexbox used correctly; clean at every breakpoint Fluid, gap-free layouts using clamp() and auto-fit with few media queries
Consistency Hard-coded colors and one-off spacing Tokens used throughout; header/footer identical everywhere A coherent mini design system you could hand to a teammate
Motion Janky or missing; ignores motion preferences Three smooth, purposeful animations; reduced-motion honored Motion reinforces hierarchy and feels invisible-until-noticed
Accessibility Low contrast, no focus rings, missing alt text Keyboard-friendly, good contrast, semantic landmarks Green Lighthouse a11y score; tested with a screen reader
Delivery Only runs locally Deployed to a public URL, no console errors Live URL + a README documenting the architecture

πŸ’‘ The one-sentence test

Hand your live URL to a friend on their phone and say nothing. If they can navigate every page, read every section comfortably, and reach the contact form without pinch-zooming or hitting a broken layout β€” you've built a good site.

Summary & Quiz

πŸŽ‰ Key Takeaways

  • Build in milestones, each shippable, so you always have a working site.
  • Grid handles two-dimensional page structure; Flexbox arranges the pieces inside.
  • Design tokens in :root keep four pages visually consistent and easy to re-theme.
  • Mobile-first plus a small set of min-width breakpoints covers 320 px β†’ 1440 px cleanly.
  • Animate only transform/opacity, keep it short, and always guard with prefers-reduced-motion.

🎯 Quick Quiz

Question 1: Which tool is the best fit for the overall two-dimensional structure of a page section (rows and columns)?

Question 2: Why should animations be limited to transform and opacity where possible?

Question 3: What's the main benefit of defining colors, spacing, and type sizes as custom properties in :root?

πŸ“š Further Reading

πŸš€ What's Next?

You've now written a lot of hand-crafted CSS β€” and probably noticed the repetition. In Module 7 we introduce Sass/SCSS, which layers variables, nesting, and reusable mixins on top of the CSS you already know, so a project this size stays tidy as it grows.

πŸŽ‰ Module 6 complete!

You didn't just learn modern CSS β€” you shipped a real site with it. That's the milestone that matters.