Architecture & the layer model
Porchlight ships full and partial CSS bundles authored as small modules. Every framework 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. For normal
stylesheet declarations, later layers win: components beat base and utilities
beat components. !important reverses layer precedence.
| 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 (.pl-c-button, .pl-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 */
}
Normal unlayered styles beat normal layered styles. Important declarations follow different precedence; Porchlight uses them for safeguards such as hidden content, reduced motion, and print visibility. See MDN cascade-layer precedence.
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 --pl-c-* tokens inside a @scope block (e.g.
@scope (.pl-c-card) { … }). The scope root does not add
specificity to the rules inside it. Layer order takes precedence over
selector specificity and scope proximity for normal stylesheet declarations.
This means you can override component tokens from plain selectors in
@layer app — no specificity hacks needed:
@layer app {
/* This wins over @scope (.pl-c-card) because layer order beats scope. */
.pl-c-card {
--pl-c-card-padding: 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, competing normal stylesheet declarations inappwin — 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.
If an override is not working, check the layer order, selector, and target element. Component-local tokens must be set on the component root: a value on an ancestor cannot override a default declared directly on the component.