downwash
v0.1.0
Published
3-tier compositional CSS sizing system: token × app × scope.
Maintainers
Readme
downwash
3-tier compositional CSS sizing system. Every dimension is computed as:
final = token × app-tier × scope-tierEach tier is a CSS variable; tiers compose multiplicatively in a single calc(). Tenants override tokens; user prefs or wrappers set app multipliers; per-element overrides write to scope multipliers. No blast radius — setting --app-pad doesn't touch margins, fonts, or icons because those rules reference different vars.
The name is from aerospace — air pushed downward by a wing or rotor — because that's what the system does: values cascade down through tiers until they reach the element rendering the pixel.
Install
npm install downwashUse
@import "downwash/downwash.css";Or in JS bundlers (Next.js, Vite, etc.):
import "downwash/downwash.css";You now have:
- Tokens on
:root—--space-{0..12},--font-size-{xs..6xl},--icon-{xs..lg} - App multipliers on
:rootdefaulting to1—--app-pad,--app-margin,--app-size,--app-icon - Utility classes wired to the formula —
.text-*,.mt-*,.ml-*,.mr-*,.pb-*,.pr-*,.gap-*
The three tiers
Tier 0 — Tokens
Base values. Override per tenant or theme.
[data-tenant="acme"] {
--space-4: 1.125rem;
--font-size-base: 0.9375rem;
}Tier 1 — App
Defaults to 1. Override on :root or a wrapper to scale the whole app for one dimension without touching the others.
:root {
--app-size: 1.15; /* whole app feels bigger like browser zoom */
--app-pad: 0.9; /* but tighter padding */
}Tier 2 — Scope
Defaults to 1 via the , 1 fallback inside calc() — you don't need to declare them on :root. Set on any element to scale just that subtree.
.compact-card {
--scope-pad: 0.85;
--scope-size: 1.1;
}The formula
Every utility class is shaped:
property: calc(var(--token) * var(--app-X, 1) * var(--scope-X, 1));Examples:
.mt-4 {
margin-top: calc(
var(--space-4) * var(--app-margin, 1) * var(--scope-margin, 1)
);
}
.text-lg {
font-size: calc(
var(--font-size-lg) * var(--app-size, 1) * var(--scope-size, 1)
);
}A tenant configured at 80% (--app-pad: 0.8) and a card at 120% (--scope-pad: 1.2) compound to 96%. Cascade alone wouldn't do that; explicit multiplication does.
Why each dimension is its own var
Setting --app-pad: 1.2 cannot accidentally widen margins, scale fonts, or shrink icons — those rules reference --app-margin, --app-size, --app-icon respectively. The four dimensions are mathematically isolated by design.
Why fonts are rem, not em
Tokens are root-anchored (rem). Em tokens would compound twice for nested elements — once through cascade, once through the formula. The formula owns the multiplication; the cascade stays out.
Multi-scope projects
If you need multiple scope flavors (e.g. --chrome-* for navigation and --room-* for content panels), copy downwash.css into your project and rename --scope-* per surface, or write per-surface utility classes that consume the same tokens with different scope vars.
License
MIT — see LICENSE.
