Guide Porchlight CSS

Architecture & layer model

How Porchlight is layered, and how host applications override it.

52 components 51 stable 1 experimental

Command

Search Porchlight

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.

LayerPurpose
resetMinimal browser normalization only.
tokensPrimitive and semantic design tokens.
themesTheme, brand, density, contrast, and mode overrides.
baseHTML element defaults.
layoutLayout primitives and page/app shells.
componentsComponent classes (.c-button, .c-card, …).
utilitiesSmall single-purpose helpers.
enhancementsProgressive @supports-gated features.
printPrint 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>; adding layer(...) 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

  1. All Porchlight rules are in @layer porchlight.*.
  2. Your rules are in @layer app (declared after porchlight).
  3. The cascade resolves layers in declared order. Since app comes after porchlight, any rule in app beats any rule in porchlight — regardless of selector specificity or @scope proximity.
  4. 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.