Collections
A collection is a kind of thing you publish. Five routes, one section vocabulary, and one rule: it is the same design system arranged for a different subject.
A collection is a kind of thing you publish — travel, courses, a video series, a shop. Each one gets a folder holding the routes it needs, the sections those routes are built from, and one stylesheet written entirely in the system's tokens.
The point of doing it this way: a collection is not a new design. It is the same tokens, the same cards and the same type scale, arranged for a different subject. If a collection needs a colour or a spacing the system does not have, that is a question about the system — not a licence to invent one locally.
The five routes every collection has
Every collection answers the same five questions, so every collection has the same five routes. The names change; the shape does not.
| Route | The question it answers |
|---|---|
Index | what is here? travel: /travel |
Group | what is here, of this kind? travel: /travel/asia |
Place | what is here about this one thing? travel: /travel/japan |
Series | what is here, in order? travel: /travel/india-2026 |
Post | the thing itself travel: /travel/the-train-south |
Series is the one worth being careful about. A series is an ordered set of posts made as one body of work — a trip, a course, a season. A group is an unordered set that share an attribute — a region, a subject, a tag. They look similar and behave differently: a series has a first and a last and a progress through it; a group has none of those. That is why the series route gets a spine and the group route gets a grid.
What a collection folder holds
collection/
README.md the shared contract
collection.css the col- vocabulary - every section, once
collection.js the linked filters - the only thing CSS cannot do
shell.py the head, navbar and footer every route shares
_default/ the starting point: cp -r it and rename
build.py five routes with no subject in them
*.html
travel/
travel.css only what travel needs: globe, flight line, palm
build.py
*.html index, region, country, trip, post, components
blog/
build.py no CSS of its own - which is the test
*.html index, post
course/
course.css the syllabus scene, the level meter, the quiz, the cert
build.py
*.html index, track, topic, course, lesson, componentsfour collections, and only two of them need a stylesheet
Blog has no CSS of its own, and that is deliberate. If a second collection cannot be built out of the shared vocabulary, the vocabulary was really just the first collection wearing a general-sounding prefix. Travel keeps 44 lines — the globe, the flight line and the palm — because those genuinely belong to travel.
Course is the other direction. Its post route has a video in it rather than prose, and five sections were missing for that. They went into the shared sheet, not into course.css, because a podcast season and a video series want every one of them — and what a collection keeps for itself is the test of whether that split was honest.
The rules
| Rule | Why |
|---|---|
Tokens only | every value is a var(--…). A collection that hard-codes a colour cannot be rebranded, and has quietly left the system |
Prefix once | shared sections are col-; a collection's own extras take its name (trv-). Anything useful twice graduates from one to the other |
Reuse before adding | a place card is a card. If a section is 90% an existing component, it is that component with a modifier |
JS is additive | the filters degrade to showing everything. Every route must be readable and navigable with the script blocked |
State in ARIA | aria-pressed on a filter, aria-current on the current item. Never an .active class |
Starting a new one
The default collection exists to be copied. It has five working routes and nothing subject-specific in them, so the first thing you see is the shape rather than somebody else's content.
cp -r collection/_default collection/podcast
python3 collection/podcast/build.pychange the six lists at the top of build.py and the collection is yours
Auditing the rule
"Reuse before adding" is easy to agree with and hard to obey — the failure mode is not malice, it is not knowing the thing already exists. Two ways to check, both cheap enough to run on every build.
1 · Find classes used but never defined. A phantom class is worse than a missing style, because the markup looks right and renders as unstyled HTML. Two were found this way: .quote, used in five collections and defined nowhere — it only looked correct because those blockquotes sit inside .content, which styles blockquote directly — and .spec-table in the reusable docs template, which existed only in this repo's own docs stylesheet, so any project copying the template got an unstyled table.
2 · Find components defined but never used. This is where the reuse rule actually gets broken. The guides collection shipped its own .guide-cover while .book sat unused in 12-frame.css — already carrying a spine, a page edge, and a .book-shelf that tilts every second cover, with a doc comment naming the guides as what it was for. Every collection also hand-rolled a two-line footer while the real .footer — brand column, link columns, sign-off row — went unused, which meant the one place a sitemap gets to be exhaustive was a single sentence.
| Check | What it catches |
|---|---|
Used but undefined | grep every class="…" in the built HTML against every .selector in the compiled CSS. Should always be empty |
Defined but unused | the same two sets, inverted. Will never be empty — utilities exist to be optional — but a whole family at zero is a component nobody knows about |
The tell | a collection writing CSS for something the foundation already draws. If a new class describes a thing rather than a subject, it probably already exists |