learning-website-nextjs1-5 Exercise 2: CSS Modules for the Frame, Global CSS for the Fragments ================================================================================================= Next.js offers CSS Modules: a class written in a .module.css file is renamed to something unique, so it can never clash with another. That is exactly right for the site's OWN parts (header, footer, skip link) and exactly wrong for the classes the lesson fragments use. A fragment is a file of HTML written long ago, and it says class="tip-box". If tip-box were a module class, the browser would be sent class="tip-box" and the stylesheet would hold something like .Xk3d_tip-box: no match, no style. So: the site's frame (SiteLayout) CSS Module the page's own code uses it, nothing in a fragment ever names it .tip-box, .code-block, tables... global CSS the fragments name them, so the names must not change Save as packages/ui/src/SiteLayout.module.css: /* The site's own frame. These class names are made unique by Next (SiteLayout-module__bar__xxxx), so they can never collide with, or be styled by, anything inside a lesson fragment. */ .skip { position: absolute; left: -999px; top: 0; padding: .5rem 1rem; background: var(--accent); color: #0f1117; z-index: 10; } .skip:focus { left: 0; } .bar { display: flex; align-items: center; gap: 1.5rem; padding: .75rem 1.25rem; background: var(--surface); border-bottom: 2px solid var(--accent); } .name { color: var(--text); font-weight: 700; text-decoration: none; } .footer { border-top: 1px solid var(--border); color: var(--muted); text-align: center; padding: 1.25rem; font-size: .85rem; } @media print { .bar, .footer, .skip { display: none; } } Save as packages/ui/src/SiteLayout.tsx: import type { ReactNode } from "react"; import { SITES, type SiteName } from "@lw/sites"; import styles from "./SiteLayout.module.css"; /** The frame of every page: a skip link, the site's name, the page, and a footer. Menus come in Chapter 6. */ export function SiteLayout({ site, children }: { site: SiteName; children: ReactNode }) { return ( <> Skip to the content
{SITES[site].title}
{children}
); } Save as packages/ui/src/base.css: /* base.css: the page itself: typography, layout, links. Colours come from tokens.css. Only classes that the lesson FRAGMENTS use are global (.page-title, .page-body). The site's own header and footer are CSS modules (SiteLayout.module.css): nothing in a fragment ever refers to them. */ *, *::before, *::after { box-sizing: border-box; } html { background: var(--bg); } body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--font-body); line-height: 1.7; } a { color: var(--accent); } a:focus-visible, button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } main { max-width: var(--max-width); margin: 0 auto; padding: 1.5rem 1.25rem 3rem; } .page-title { color: #fff; font-size: 1.9rem; line-height: 1.25; margin: .5rem 0 1.25rem; } .page-body { overflow-wrap: anywhere; } @media print { body { background: #fff; color: #000; } } (components.css is the Django file unchanged. base.css here holds only the page-level rules; the menus, breadcrumbs, search and account rules of the Django base.css are added in the chapters that need them.) Save as packages/ui/src/styles.css: /* The shared design system. Load order matters: tokens (the only place a colour is decided), then the page itself, then the building blocks that lesson fragments already use. A fragment's own