The Shared Design System
Learning Website with Next.js
Chapter 5 ยท The Shared Design System
Eight sites should look like one family, and each should be recognisable at a glance. This chapter moves the Django project's
theme into a package, @lw/ui, that every app imports: one set of stylesheets, a frame component, and a colour for
each site. It answers one real question about Next.js (CSS Modules or global CSS?) and then measures whether the result looks
the same as the Django site, instead of saying so.
Tokens: the One Place a Colour Is Decided
tokens.css holds every colour as a variable, and nothing else in the design writes a colour of its own. It is the
Django project's file unchanged. Two neutral families exist (a warmer one for the language courses, a cooler one for the
technical courses), and each site has one accent. The accent is a CSS variable on the <html>
element, so one stylesheet serves every site and only that value differs:
| Site | Family | Accent |
|---|---|---|
| Languages | language | #a78bfa purple |
| Web Development | technical | #38bdf8 sky blue |
| Programming | technical | #44b78b green |
| Systems | technical | #f0938a coral |
| AI and Data | technical | #fbbf24 amber |
| Humanities | language | #fb923c orange |
| Life Skills | language | #6ee7b7 mint |
| Creative Tools | technical | #f472b6 pink |
Rules That Are Tests
- Every site has a style, and its own accent.
- Every accent is readable as text on both neutral surfaces: a contrast of at least 4.5 (WCAG level AA). The
test reads the real colours out of
tokens.cssinstead of copying them into the test, so changing a token changes what the test sees. The contrast function itself is checked against known values (black on white is exactly 21;#777777on white is 4.48, the famous “just fails”). - No
!importantin any shared stylesheet. - Shared component rules stay weaker than a fragment's own (no ids, at most two parts), explained below.
CSS Modules or Global CSS?
CSS Modules rename every class to something unique, so one can never clash with another. That is exactly right for the
site's own parts 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 stylesheet would hold a renamed
class and the fragment would match nothing. So the choice follows from one fact: who writes the class name.
| Styles for | Kind | Why |
|---|---|---|
| The site's frame: header, footer, skip link | CSS Module | Only the site's own code names them; nothing in a fragment ever does |
.tip-box, .code-block, tables, .page-title | Global CSS | Fragments name them, so the names must not change |
The built output shows the difference:
A fragment also brings its own scoped <style> (for example .hu-lesson .tip-box { ... }).
The shared rules use a single class, so the fragment's rule is more specific and wins. That is deliberate: an old page keeps its
look. A test reads components.css and fails if any selector has an id or more than two parts.
Does It Look the Same as the Django Site?
Both were started, and a real headless Chrome, driven through the DevTools protocol, opened the same Hungarian lesson on each and read the computed style of 27 properties: the accent variable, the body, the main column, the page title, a tip box, a dialog card, a vocabulary table, a speaker label.
| Run | Result |
|---|---|
| As built | 26 identical, 0 different, 1 absent on both sites (a link in the page body that this page does not have) |
| Page background and text tokens each changed by one step | 24 identical, 2 different: rgb(15, 17, 23) against rgb(15, 17, 24), and 236 against 237 |
| Radius changed from 8 to 10 px and the language surface by one step | 26 identical, 0 different |
A style guide page (/styleguide, in both apps) shows every shared building block at once. Looked at by eye:
the Languages site is purple on the warmer neutral surface, the Web Development site is sky blue on the cooler one, and the
warning and finding boxes keep their own colours on both.
prefers-color-scheme switch. Fonts are the system fonts; font
loading is Chapter 8. The menus, breadcrumbs, search and account rules of the Django stylesheet come in the chapters that
need them.
Hands-On Exercises
Move the design tokens into a shared package and give each site its own accent through a CSS variable on the html element. Write tests that read the real stylesheet: every accent readable on both surfaces, no two the same, every site styled.
Decide which styles are CSS Modules and which are global, from the fact of who writes the class name. Build the site frame as a module, keep the fragment-facing classes global, and show the difference in the built output. Guard the specificity rule with tests.
๐ View solutionMeasure whether a real lesson looks the same on the Django site and the Next.js site by comparing computed styles in a real browser. Show that your check can fail, and say what it cannot see.
๐ View solutionChapter 5 Quick Reference
@lw/ui:styles.css(tokens, base, components),SiteLayout,htmlProps(site),StyleGuide, contrast helpers- Colours live only in
tokens.css; each site's accent is--accenton<html>, fromSITE_STYLEin the site package - Two neutral families (language, technical); eight accents, all different, all at least 4.5 contrast on both surfaces
- Modules for the site's own frame; global CSS for classes that fragments write (
tip-box,code-block, tables) - Built proof: module classes become
NMm9mq_bar;.tip-boxkeeps its name - Shared rules: one class each, no ids, no
!important, so a fragment's own scoped rule wins - Parity with Django: 26 of 26 comparable styles identical; the check fails on a one-step colour change; it cannot see tokens that fragments override
- Dark only; no light theme; system fonts; only one lesson and Chrome compared