This is the course capstone: you will build a complete marketing website for a cricket statistics product, backed by a production-grade design system — the full combination of every skill across all six modules. The site has a sticky nav, a hero with fluid typography, a responsive feature grid with animated cards, a pricing section, a form, a dark-mode toggle, and a footer, all driven by a tiered token system that supports three themes and passes accessibility checks throughout.
The design system behind it is the real deliverable: a token architecture, a component library, accessible interaction patterns, production build optimisation, responsive engineering at every breakpoint and container, and conventions that would hold up under team maintenance. The site is what users see; the design system is what a professional team would ship and maintain.
There is no single correct implementation. The rubric measures architecture quality — are tokens tiered, are components semantic, are states accessible, does it re-theme cleanly — rather than pixel perfection. The goal is a codebase a hiring manager could open and recognise as the work of someone who understands Tailwind at a senior level, not just at a user level.
Learning Objectives
- Apply the full token architecture (primitive, semantic, component tiers) with at least two brand themes plus dark mode, all switching without component-code changes.
- Build a complete page section set (nav, hero, features, pricing, form, footer) that is mobile-first, responsive at every viewport from 360px to 1440px, and fluid between breakpoints with clamp().
- Animate the interface appropriately — entrance animations, hover states, microinteractions — using compositor-friendly properties, with full motion-reduce coverage and no jank under CPU throttle.
- Meet WCAG AA accessibility requirements throughout: focus-visible rings on every interactive element, sufficient contrast in every theme and mode, screen-reader labels, and status not reliant on color alone.
- Package the component set as a small library (Button, Card, Input, Badge, StatTile, Nav, Hero, FeatureCard) with clean variant APIs, accessible defaults, and override-safe className merging.
- Demonstrate production readiness: accurate content globs, a gzipped build under 20KB, no raw-color token leaks, prettier and eslint tooling enforced, and a Lighthouse accessibility score above 90.
Architecture
The architecture mirrors the course's progression: tokens at the bottom, components in the middle, pages at the top. Tokens live in two CSS files (primitives and themes), the Tailwind config maps semantic tokens to utilities, and the component library is a set of React components that speak only in semantic tokens and expose clean variant APIs. The pages are assembled from those components, with no inline styling that bypasses the token system.
The design system's correctness is verifiable: switch the root class to a brand theme and nothing changes in the component files — every pixel re-themes automatically because every component references semantics. Run the lint rule and zero raw-color violations appear. Tab through the page and every interactive element has a visible ring. Run the production build and the gzipped stylesheet is small. These are the tests that prove the architecture works, not just that the pixels look right.
The responsive architecture uses the two-level approach: viewport breakpoints for page-level structure (the navigation collapses to hamburger at md, the feature grid goes from 1 to 2 to 3 columns from sm to lg) and container queries for components (feature cards adapt to their column width). Fluid typography via clamp() means no discrete text-size jumps between breakpoints — the headline scales continuously from phone to desktop.
# Project structure — layers explicit in the directory layout
# cricstat-marketing/
# ├─ tailwind.config.js <- maps semantic tokens to utilities
# ├─ src/
# │ ├─ tokens/
# │ │ ├─ primitives.css <- raw palette, scale values
# │ │ └─ themes.css <- semantic tokens: :root, .dark, .theme-emerald, .theme-rose
# │ ├─ lib/ <- the component layer
# │ │ ├─ cn.js <- clsx + tailwind-merge
# │ │ ├─ Button.jsx <- primary/secondary/danger, sm/md/lg, accessible
# │ │ ├─ Card.jsx <- surface token, elevated variant
# │ │ ├─ Input.jsx <- labelled, focus-visible, native invalid state
# │ │ ├─ Badge.jsx <- semantic tone variants
# │ │ ├─ StatTile.jsx <- composes Card + tokens + type scale
# │ │ └─ Nav.jsx <- sticky, responsive, frosted-glass, focus-visible
# │ ├─ sections/ <- page sections assembled from lib/
# │ │ ├─ Hero.jsx <- fluid type (clamp), two-column lg, animated entrance
# │ │ ├─ Features.jsx <- responsive grid, container-adaptive FeatureCard
# │ │ ├─ Pricing.jsx <- comparison cards, highlighted plan
# │ │ ├─ SignupForm.jsx <- accessible form, native validation, sr-only labels
# │ │ └─ Footer.jsx <- flex wrap columns, semantic tokens
# │ └─ App.jsx <- page composition + theme switcher
# └─ .eslintrc.js + prettier.config.js + tailwind.config.jsPhase 1 — Token System and Core Components
Phase 1 builds the foundation: the three-tier token architecture plus the Button, Card, and Input components. Define primitives, then semantic mappings for light, dark, and the two brand themes, then map to Tailwind utilities in the config. Build Button with cva and the cn helper (primary, secondary, danger, three sizes, all states, accessible); build Card consuming only surface and body tokens; build Input with associated label, focus-visible ring, and native invalid state. Validate by switching the root theme class and confirming all three components re-theme instantly.
/* src/tokens/themes.css — three-tier token system + three themes */
:root { /* SEMANTIC layer, light theme defaults */
--color-primary: var(--blue-700);
--color-surface: var(--slate-50);
--color-body: var(--slate-900);
--color-muted: var(--slate-500);
--color-border: var(--slate-200);
}
.dark {
--color-primary: var(--blue-400);
--color-surface: var(--slate-900);
--color-body: var(--slate-50);
--color-border: var(--slate-700);
}
.theme-emerald { --color-primary: var(--emerald-600); }
.theme-emerald.dark { --color-primary: var(--emerald-400); }
.theme-rose { --color-primary: var(--rose-600); }
.theme-rose.dark { --color-primary: var(--rose-400); }
// src/lib/Button.jsx — cva variant API, semantic tokens, fully accessible
import { cva } from 'class-variance-authority';
import { cn } from './cn';
const btn = cva(
'inline-flex items-center justify-center font-semibold rounded-card transition-colors ' +
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 ' +
'disabled:opacity-50 disabled:pointer-events-none',
{ variants: {
variant: {
primary: 'bg-primary text-surface hover:opacity-90 focus-visible:ring-primary',
secondary: 'bg-surface text-body border border-border hover:bg-muted/10 focus-visible:ring-muted',
danger: 'bg-danger text-surface hover:opacity-90 focus-visible:ring-danger'
},
size: { sm:'px-3 py-1.5 text-sm', md:'px-4 py-2', lg:'px-6 py-3 text-lg' }
},
defaultVariants: { variant: 'primary', size: 'md' }
}
);
export function Button({ variant, size, className, ...p }) {
return <button {...p} className={cn(btn({ variant, size }), className)} />;
}Phase 2 — Page Sections
Build the Nav, Hero, Features, Pricing, Form, and Footer sections. The Nav is sticky with a frosted-glass backdrop-blur effect (small, stable surface), a mobile-first hamburger toggle, and a focus-visible ring on every link. The Hero uses clamp() for fluid headings, a two-column layout from lg, and an animated entrance (fill-mode forwards, motion-reduce aware). The Features section uses a responsive grid with container queries on each FeatureCard so cards adapt to their column width. The Form is fully accessible — labelled inputs, native validation feedback, no display:none-hidden controls.
// src/sections/Hero.jsx — fluid type + entrance animation + two-column lg
// tailwind.config.js: keyframes: { 'fade-up': { '0%':{opacity:'0',transform:'translateY(1rem)'}, '100%':{opacity:'1',transform:'translateY(0)'} }},
// animation: { 'fade-up': 'fade-up 600ms ease-out forwards' }
export function Hero() {
return (
<section className="mx-auto max-w-6xl px-4 py-16 lg:py-24">
<div className="flex flex-col lg:flex-row lg:items-center gap-10">
<div className="flex-1 text-center lg:text-left motion-safe:animate-fade-up">
<h1
className="font-extrabold text-body leading-tight"
style={{ fontSize: 'clamp(2rem, 1.5rem + 3vw, 4.5rem)' }}>
Every ball. Every stat. <span className="text-primary">One app.</span>
</h1>
<p className="mt-4 text-muted text-lg max-w-prose mx-auto lg:mx-0">
Live scores, deep player analytics, and historic scorecards — all in your pocket.
</p>
<div className="mt-6 flex flex-wrap gap-3 justify-center lg:justify-start">
<Button size="lg">Get started</Button>
<Button variant="secondary" size="lg">Watch demo</Button>
</div>
</div>
<div className="flex-1 grid grid-cols-2 gap-4 motion-safe:animate-fade-up [animation-delay:150ms]">
<StatTile value="264" label="Highest ODI" />
<StatTile value="4.6" label="Bumrah econ" />
<StatTile value="50" label="Kohli tons" className="col-span-2" />
</div>
</div>
</section>
);
}Phase 3 — Polish, Theming and Production Hardening
Phase 3 connects the theme switcher, adds the final microinteractions, and hardens everything for production. The theme switcher toggles the root class, persists the choice, and sets the initial theme before first paint via an inline head script to prevent flash. Feature cards get hover animations (scale + shadow, compositor-friendly). The pricing section highlights the recommended plan with a primary-color accent. The form's submit button shows a loading spinner (animate-spin on an SVG) during submission.
Hardening means running the full checklist: prettier and eslint pass with zero errors, lint finds no raw-color violations, the production build is under 20KB gzipped, Lighthouse accessibility is above 90, and the whole site is keyboard-navigable with a visible focus ring on every interactive element in every theme and mode. These are the gates that distinguish a finished production deliverable from a finished-looking prototype.
// src/App.jsx — theme switcher, pre-paint, assembly
import { useState, useEffect } from 'react';
// (other section imports)
const THEMES = ['', 'theme-emerald', 'theme-rose'];
// Inline script to add to <head> to prevent theme flash:
// <script>
// var saved = localStorage.getItem('theme');
// var pref = matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : '';
// document.documentElement.className = (saved || pref);
// </script>
export function App() {
const [brand, setBrand] = useState('');
const [dark, setDark] = useState(() => matchMedia('(prefers-color-scheme:dark)').matches);
useEffect(() => {
const cls = [brand, dark ? 'dark' : ''].filter(Boolean).join(' ');
document.documentElement.className = cls;
localStorage.setItem('theme', cls);
}, [brand, dark]);
return (
<div className="min-h-screen bg-surface text-body">
<Nav brand={brand} setBrand={setBrand} THEMES={THEMES} dark={dark} setDark={setDark} />
<Hero />
<Features />
<Pricing />
<SignupForm />
<Footer />
</div>
);
}Evaluation Rubric
- Token architecture: three themes switch by root-class change alone, zero component edits; lint confirms zero raw-color violations in component files; config maps only semantic tokens.
- Responsive engineering: mobile-first at 360px, all sections adapt correctly to 768px and 1440px; feature cards use container queries; hero heading scales with clamp() between breakpoints.
- Animation quality: all motion uses compositor-friendly properties (transform/opacity); motion-reduce disables or calms every animation; no jank under 4x CPU throttle in DevTools.
- Accessibility: Lighthouse accessibility score above 90; every interactive element has a visible focus-visible ring in every theme and mode; all status and error states use multi-channel cues (not color alone); icon buttons have sr-only labels.
- Production build: gzipped stylesheet under 20KB; Lighthouse performance score above 80; no render-blocking CSS (or a preload hint added); Prettier and ESLint pass with zero errors.
- Component library quality: each of the eight components (Button, Card, Input, Badge, StatTile, Nav, Hero, FeatureCard) exposes a clean variant API, spreads props for behaviour pass-through, and merges className last via tailwind-merge.
- Code quality: no @apply on markup you control; extraction follows the three-uses threshold; class ordering is automated; the token system is tiered with no primitive-bypass in components.
Extension Challenges: (1) Generate the primitive and semantic tokens from a Figma export or tokens.json, and write a build script that updates the CSS token files automatically from the design source. (2) Add a fifth section — a testimonials carousel using native scroll snap — replacing a JavaScript carousel library. (3) Publish the component library as an npm package and consume it in a second app (a dashboard), proving the system scales across products. (4) Add a high-contrast accessibility theme as a fourth theme option that raises all text contrast ratios above 7:1 for WCAG AAA.