Thoughts to pass along to K317h
Framing: the 12,000-line number is not really the obstacle, and the work you'd
have to do to make theming easy is much smaller than that number suggests. Most
of the hard part is already done — the token layer is genuinely good, the class
names are semantic, and the JS stays out of the way. A handful of changes would
turn "possible if you're stubborn" into "pleasant".
1. Wrap the site stylesheet in @layer. Highest leverage by far.
Any unlayered user CSS then beats every layered rule regardless of
specificity. .message { display: block } would just win against
div.messages .message. This single change:
- deletes the specificity arms race outright
- makes the ordering problem you raised mostly moot — a themer no longer has to
reason about what beats what, only about load order among their own rules - costs one wrapper and zero refactoring, and changes nothing about how the
site renders today
One caveat, and it happens to be fine here: !important inverts layer order,
so an !important rule inside a layer would still beat unlayered user CSS. The
sheet has exactly three, and none are on the reading surface — L299 is the
stickyMenusDisabled preference toggle, L8561–8562 are on the system
diagnostics table. So the claim holds as stated; just worth knowing that adding
!important to a reading-surface rule later would punch a hole in it.
If you wanted finer control later, @layer base, components, theme; lets you
publish a contract about which parts are overridable, but the single-layer
version already gets ~90% of the benefit.
2. Drop element qualifiers from selectors
1,371 of 2,351 selector instances are div.foo rather than .foo, and depth
runs to 6. Since the class names are already unique and semantic, the div/
section prefixes buy nothing but specificity. div.messages .message →
.message. This is grindy and lower priority than @layer — and @layer
makes it mostly unnecessary — but it also makes the stylesheet easier for
you to reason about.
3. Extend the token layer to spacing and layout
Colour and type are ~100% tokenised; layout is ~0%. Only 21% of reading-surface
declarations use var(). A themer hits a wall the moment they want to change
anything structural. Even a small set would cover most of what people ask for:
The single most-requested structural change will be "put the name and date
below the post like old ILX". That's the two-column grid at L1858. If it were
a documented toggle — a body class alongside Mode1–Mode4, or a
--messageLayout switch — it would eliminate the most awkward part of any
old-style theme, and it's a handful of rules on your side.
4. Document a small stable "theme API"
The fear that themers "break all the functions they can't see" is fair, and the
fix is a published contract rather than restraint. Something like: these ~40
tokens and these ~20 class names are supported and won't be renamed without
notice; everything else is internal. That lets people theme confidently and
lets you refactor everything else freely. It's the difference between a themer
guessing and a themer knowing.
Relatedly, the display-mode body classes (stickyMenusDisabled,
alwaysShowPostNameAndDate, alwaysShowPostMenu, hideEmbeds, Mode1–Mode4)
are already effectively a theming API — they just aren't documented as one.
5. On the mix-and-match ordering worry
Probably worth not solving. One override sheet, loaded last, covers nearly
every real use case; @layer removes the specificity half of the problem; and
users can already compose freely in Stylus if they want to. A one-slot
"override CSS" textarea in settings, loaded after style.css, is a complete
feature — the ordering complexity only appears if you build the mix-and-match
version, and it can wait until someone actually asks.
6. Two small things spotted in passing
A live bug. style.css L60:
but the definition at L167 is --PostTypeface (capital P). Custom properties
are case-sensitive, so --imageCaptionTypeface is invalid at computed-value
time and its one consumer at L2884 (font-family) silently falls back to
inherited. One-character fix. It's also a decent argument for a token lint,
since nothing else would have caught it.
Missing generic fallbacks. --uiTextTypeface: "univers-next-pro",
--uiHeadingTypeface: "neue-haas-grotesk-display" and --metadataTypeface:
"neue-haas-grotesk-text" have no fallback family, whereas --TextTypeface,
--PostTypeface and --HeadingTypeface* all carry full stacks. If Typekit is
slow or blocked, those three fall to the browser default rather than a chosen
one. Also worth harmonising the naming while you're in there — the file
currently mixes --grey-700/--surface-page (kebab) with
--PostTypeface/--linkColour/--borderRadius (camel and Pascal), which makes
the token list harder to search than it needs to be.