Architecture & the layer model
Porchlight is a single CSS file authored as small modules. Every rule lives
inside one top-level cascade layer: porchlight.
The wrap
@layer porchlight {
@layer reset, tokens, themes, base, layout, components, utilities,
enhancements, print;
}
Inside porchlight, nine sub-layers declare a deterministic order. Later
layers win over earlier ones, so a component always beats base, a utility
always beats a component - and nothing needs !important.
| Layer | Purpose |
|---|---|
reset | Minimal browser normalization only. |
tokens | Primitive and semantic design tokens. |
themes | Theme, brand, density, contrast, and mode overrides. |
base | HTML element defaults. |
layout | Layout primitives and page/app shells. |
components | Component classes (.c-button, .c-card, …). |
utilities | Small single-purpose helpers. |
enhancements | Progressive @supports-gated features. |
print | Print behavior. |
How a host app overrides Porchlight
Place the framework in the cascade before you import it:
/* app.css */
@layer porchlight, app; /* app wins */
@import "@cawalch/porchlight";
@layer app {
/* your product styles here */
}
Unlayered styles always beat layered styles, so unlayered app CSS wins too.
Either way, you never fight !important - Porchlight reserves !important
for accessibility-critical utilities only (screen-reader helpers, [hidden]).
Do not pass
layer(...)to the@import. Each Porchlight module self-wraps in@layer porchlight.<name>; addinglayer(...)would double-nest it.
Why no internal overrides layer?
Some frameworks ship a trailing overrides layer for consumers. With the
porchlight wrap that’s redundant: overrides live in your own layers
outside porchlight (or unlayered). One wrap, one mental model.
Overriding component tokens
Each component defines --c-* tokens inside a @scope block (e.g.
@scope (.c-card) { … }). These scoped rules look higher-specificity than a
flat .c-card { … } rule in your app layer, but layer order takes
precedence over @scope proximity in the cascade.
This means you can override component tokens from plain selectors in
@layer app — no specificity hacks needed:
@layer app {
/* This wins over @scope (.c-card) because layer order beats scope. */
.c-card {
--c-card-pad: var(--pl-space-6);
}
}
How it works
- All Porchlight rules are in
@layer porchlight.*. - Your rules are in
@layer app(declared afterporchlight). - The cascade resolves layers in declared order. Since
appcomes afterporchlight, any rule inappbeats any rule inporchlight— regardless of selector specificity or@scopeproximity. - You only need higher specificity if you’re overriding within the same layer (e.g., overriding one Porchlight component from another Porchlight component). That’s not a consumer concern.
Bottom line: if your override isn’t working, check that it’s inside
@layer app (or unlayered). Do not reach for !important.