Maintainability is a property of the reader
It is tempting to define maintainable code by its internal qualities — small functions, low duplication, tidy naming. Those help, but they are proxies. The property that matters is whether the next person can make a safe change with a small amount of reading.
On a corporate website the next person is frequently not a specialist frontend engineer. They are a developer maintaining six other things, or a marketing manager who has been shown how to edit a JSON file. Design for that reader and the internal qualities tend to follow.
Name for structure, not for appearance
A class called .card tells you what a thing is. A class called
.rounded-white-box tells you what it currently looks like, which is
exactly the thing most likely to change. When the brand shifts, structural names
survive and appearance names become lies.
Block, element, modifier
.card { } /* the block */
.card__title { } /* an element that only exists inside the block */
.card__media { }
.card--linked { } /* a variation of the block */
.card--ruled { }
The convention matters less than the consistency. What it buys you is the ability to
search for card__ and see the whole component, and the confidence that
deleting a block deletes everything belonging to it.
Put decisions in tokens
Every value that appears in more than one place is a decision, and decisions belong in one location. Custom properties give you that without a build step.
Tokens, then usage
:root {
--colour-border: #E0D9D0;
--space-md: 1.5rem;
--card-radius: 2px;
}
.card {
padding: var(--space-md);
border: 1px solid var(--colour-border);
border-radius: var(--card-radius);
}
The test of a good token layer is whether a rebrand touches only the token file. If changing the accent colour requires editing nine components, the tokens are decorative rather than load-bearing.
Note
Token names should describe role, not value. --colour-accent survives a
change from copper to teal; --colour-copper does not.
Keep responsive behaviour in one place
Scattering media queries through component files means that understanding a component at a given width requires reading every file it appears in. We keep every breakpoint in a single stylesheet, loaded last. The component file describes the mobile behaviour; the responsive file describes how it changes.
This is a trade: you lose locality, and you gain the ability to answer "what changes at 64em?" by reading one block. On a site with roughly sixty components, the second is worth more.
Separate behaviour from structure with data attributes
Using classes as JavaScript hooks couples behaviour to styling: rename the class for a
visual reason and the behaviour silently stops. A data- attribute is an
explicit contract.
Behaviour hook separate from styling hook
<div class="grid grid--3" data-latest-articles data-limit="3"></div>
// js/content.js
const host = document.querySelector('[data-latest-articles]');
if (!host) return;
const limit = Number(host.dataset.limit) || 3;
The early return is not incidental. Every behaviour module on a multi-page static site runs on every page, so each must be able to find nothing and exit quietly. A module that throws when its hook is absent takes the rest of the page down with it.
Give components a spacing contract
The most common source of visual drift is components that assume the margins of their neighbours. Decide once whether components own their outer spacing or whether the layout does, then hold the line. We use the second: layout primitives own the gaps, components own only their internal spacing.
Watch out
If your reset removes all default margins, every element that needs vertical rhythm must get it back deliberately. Otherwise a heading followed by a paragraph will sit flush, and the fix will be applied ad hoc in twenty places over the following year.
Write down the contract
For each component, three lines is usually enough: what it is for, what markup it requires, and what it must not be used for. The third line is the one people skip and the one that prevents the most damage.
| Component | Required markup | Not for |
|---|---|---|
| Accordion | Trigger with aria-controls matching the panel id |
Content that must be findable by in-page search |
| Card | Title element; optional media and footer | Long-form reading — use prose |
| Statistic | Value and label | Figures you cannot evidence |
Delete more than you add
A component library grows by accretion unless someone actively prunes it. Twice a year, search the codebase for each component's class and remove anything with no callers. The variants nobody uses are not free: they are read, considered and imitated by the next person, and they make the system look larger and more intimidating than it is.