Theming
Drive appearance through the --lq-* design-token cascade and the token override provider.
Every visual value in Liquidify — colour, radius, glass tuning, motion — resolves
from a --lq-* design-token custom property emitted by @liquidify/tokens. The
library's own CSS never hard-codes a raw value; it reads tokens. That single rule
is what makes theming a cascade concern rather than a per-component prop drill.
There are two layers to theming:
- Named environment providers —
Theme,Material, andBackdrop— select a coherent bundle of token values (colour scheme, default material, backdrop volatility) for a subtree. LiquidTokenProvider— the one seam where a consumer writes explicit--lq-*values to override individual tokens for a subtree.
Environment providers
Theme is re-exported from @liquidify/react so the common single-package setup
needs no second install. Material and Backdrop live in @liquidify/glass.
import { Theme } from "@liquidify/react"
import { Backdrop, Material } from "@liquidify/glass"
export function AppShell({ children }: { children: React.ReactNode }) {
return (
<Theme type="system">
<Material default="regular">
<Backdrop type="low">{children}</Backdrop>
</Material>
</Theme>
)
}| Provider | Setting | Values | Default |
|---|---|---|---|
Theme | type | light, dark, system | light |
Material | default | frosted, regular, clear | regular |
Backdrop | type | static, low, high | low |
Providers are nestable and the nearest one wins for its setting. See Providers for the full contract.
Overriding tokens
LiquidTokenProvider accepts a tokens map of --lq-* custom properties and
writes them onto its own display: contents root, so every descendant inherits
them through the CSS cascade. A key that does not start with --lq- is dropped —
a development no-op, never a silent leak.
import { LiquidTokenProvider } from "@liquidify/glass"
import { Button } from "@liquidify/react"
export function BrandedRegion() {
return (
<LiquidTokenProvider tokens={{ "--lq-color-accent": "rebeccapurple" }}>
<Button variant="borderedProminent">Continue</Button>
</LiquidTokenProvider>
)
}Because the override lands as a plain CSS custom-property value that the existing
rules already consume, it wins with no specificity fight and needs no
component-level style or per-instance prop. This is the architecture the
library deliberately chose over prop drilling.
Nesting merges
An inner provider merges with the nearest ancestor: inner keys win, parent keys are inherited. Only a provider's own keys are written to its root; the cascade carries the rest for free.
import { LiquidTokenProvider } from "@liquidify/glass"
import { Slider } from "@liquidify/react"
export function NestedTokens() {
return (
<LiquidTokenProvider tokens={{ "--lq-color-accent": "teal" }}>
<LiquidTokenProvider tokens={{ "--lq-radius-control": "0.25rem" }}>
{/* accent: teal (inherited), radius: 0.25rem (own) */}
<Slider aria-label="Amount" defaultValue={0.4} />
</LiquidTokenProvider>
</LiquidTokenProvider>
)
}