{ "$schema": "../schemas/code-crispies-module-schema.json", "id": "units-variables", "title": "CSS Units & Variables", "description": "Understand the variety of CSS measurement units and how to define and use custom properties for maintainable styles.", "difficulty": "beginner", "lessons": [ { "id": "units-1", "title": "Relative Units", "description": "CSS offers two types of units: absolute (like px) and relative (like % and rem). Relative units adapt to their context, making layouts flexible and accessible.

Common relative units:
% – Relative to parent element
rem – Relative to root font size (typically 16px)
em – Relative to element's font size

A common pattern for readable content: set width: 100% so it fills available space, then max-width: 40rem to cap line length for readability.", "task": "This article text runs too wide on large screens. Add max-width: 40rem to .article for optimal reading width.", "previewHTML": "

The Art of Typography

Good typography is invisible. When text is set well, readers absorb information without noticing the design decisions that make it comfortable to read. Line length is crucial—too wide and eyes get lost, too narrow and reading becomes choppy.

The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.

", "previewBaseCSS": "body { font-family: Georgia, serif; padding: 1rem; background: #f9f9f9; } .article { background: white; padding: 2rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .article h2 { margin: 0 0 1rem; color: #333; } .article p { margin: 0 0 1rem; line-height: 1.6; color: #444; } .article p:last-child { margin-bottom: 0; }", "sandboxCSS": "", "codePrefix": ".article {\n ", "initialCode": "", "codeSuffix": "\n}", "solution": "max-width: 40rem;", "previewContainer": "preview-area", "validations": [ { "type": "property_value", "value": { "property": "max-width", "expected": "40rem" }, "message": "Set max-width: 40rem" } ] }, { "id": "units-2", "title": "CSS Variables", "description": "CSS custom properties (variables) let you define reusable values. Define them with --name and use them with var(--name). Variables defined on :root are available everywhere.", "task": "Define --brand: steelblue in :root, then use it as the background color for .btn.", "previewHTML": "
", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .actions { display: flex; gap: 1rem; } .btn { color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #ccc; }", "sandboxCSS": "", "codePrefix": ":root {\n ", "initialCode": "", "codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}", "solution": "--brand: steelblue;", "previewContainer": "preview-area", "validations": [ { "type": "contains", "value": "--brand", "message": "Define --brand variable", "options": { "caseSensitive": false } }, { "type": "contains", "value": "steelblue", "message": "Set the value to steelblue", "options": { "caseSensitive": false } } ] }, { "id": "units-3", "title": "calc() Function", "description": "The calc() function lets you mix different units in calculations. This is essential for layouts that combine fixed and flexible sizing, like a sidebar layout.", "task": "The main content should fill the remaining space after the 200px sidebar. Set width: calc(100% - 200px) on .main.", "previewHTML": "

Main Content

This area should fill the remaining width after accounting for the fixed-width sidebar.

", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .layout { display: flex; gap: 1rem; } .sidebar { width: 200px; background: #1a1a2e; color: white; padding: 1rem; border-radius: 8px; flex-shrink: 0; } .main { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .main h2 { margin: 0 0 8px; } .main p { margin: 0; color: #666; }", "sandboxCSS": "", "codePrefix": ".main {\n ", "initialCode": "", "codeSuffix": "\n}", "solution": "width: calc(100% - 200px);", "previewContainer": "preview-area", "validations": [ { "type": "regex", "value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)", "message": "Set width: calc(100% - 200px)", "options": { "caseSensitive": false } } ] }, { "id": "units-4", "title": "Viewport Units", "description": "Viewport units size elements relative to the browser window:
vw – 1% of viewport width
vh – 1% of viewport height

These are perfect for full-screen sections like hero banners.", "task": "Make this hero section fill the viewport height by setting min-height: 100vh on .hero.", "previewHTML": "

Welcome

Scroll down to explore

", "previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .hero { background: linear-gradient(135deg, #1a1a2e 0%, steelblue 100%); color: white; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 2rem; } .hero h1 { margin: 0 0 1rem; font-size: 2.5rem; } .hero p { margin: 0; opacity: 0.8; }", "sandboxCSS": "", "codePrefix": ".hero {\n ", "initialCode": "", "codeSuffix": "\n}", "solution": "min-height: 100vh;", "previewContainer": "preview-area", "validations": [ { "type": "property_value", "value": { "property": "min-height", "expected": "100vh" }, "message": "Set min-height: 100vh" } ] } ] }