Styling
How to restyle a component that already exists. Bump a knob, override a property that isn’t a knob yet, or hand-tune a state. Scoped variables plus BEM plus cascade order mean you never need !important.
Where your CSS goes
Everything on this page is a few lines of CSS you write yourself, and they belong in @layer components — the same cascade layer Stisla’s own component rules live in. Layers are native CSS, so this works whether or not you run a build. Putting your rules in that layer lands them beside the framework’s, where the ordinary cascade decides: higher specificity wins, and at equal specificity the rule authored later wins. Your stylesheet loads after Stisla’s, so an equal-specificity override is yours.
Without a build step. Link your stylesheet after stisla.css and wrap your rules in the layer.
With Vite and Tailwind. Same layer. You just pull in Tailwind and the source theme at the top first, the same two setups from Theming.
Only the @import lines differ between the two. The rules inside the layer are identical, so the examples below show the rule alone — drop it into @layer components either way.
Tweaking a knob that exists
The everyday case. You like the button, you just want the icon a bit larger. --button-icon-size defaults to --spacing(4) on every .button. Three escalation levels from least invasive to most. Stop at the one that matches how broad the change really is.
Option A. Inline override
Bumps the icon size inside this one button. Tone, padding, height all stay default.
Option B. Your own scoped class
You want the icon-prominent treatment in more than one place. Write a modifier class once, apply it where you want it.
Specificity matches Stisla’s own rule (one class), and cascade order resolves it. No !important.
Option C. Wrap a region
Every button inside a region gets the bigger icon. Useful for toolbars and command bars where the icon is the dominant read.
Higher specificity (two classes beat one), so this wins even if your stylesheet loads before Stisla’s. Reach for it when you can’t control load order, or when the change really belongs to a region rather than a button modifier.
Tweaking when there’s no knob
Plenty of properties aren’t exposed as variables, by design. The Specification covers which decisions become knobs and which stay literal. You still have a clean path: the same three escalation options, only now you’re targeting the property directly.
Scenario: you want uppercase button labels. .button doesn’t set text-transform, and there’s no --button-text-transform knob. You add the property directly.
Option A. Inline style
Inline always wins. No specificity battle.
Option B. Your own scoped class
Same specificity as Stisla’s .button rule. Your stylesheet loads after, so cascade order resolves it.
Option C. Region-scoped
Two classes beat one. Wins regardless of load order.
There’s a feedback loop here. If you find yourself overriding the same property in three or more places (one specific text-transform, one specific padding, one specific font-weight), that’s the signal it should become a Stisla variable. File an issue. The user-side overrides are the input to the public knob surface. That’s how --button-rim-mix got exposed when .button-group needed to lift it. Skip the issue when your override is genuinely one-off, project-specific, or just personal preference.
No !important needed
You may have noticed none of the options above reach for !important. That’s the point of the model, and it holds in every case. Token changes inherit, so the cascade isn’t even involved in the use of a variable. Property overrides resolve on the ordinary rules: inline beats any class, two classes beat one, and at equal specificity your stylesheet loads after Stisla’s so it wins. Set the variable on a wrapper and every descendant inherits it, no fight at all.
State derivation and escape hatches
Stisla derives hover, active, and focus colors from a single source via color-mix(in oklch, …). Change one variable and every state recomputes. This is the easy path. It’s also escapable at three levels when you want hand-tuned states. The reference is .button, but the same pattern shows up in alert, badge, dialog, kbd, list-group, scroll-area, table, and a dozen others.
Level 1. Keep color-mix, swap the source
You like the derivation curve, you want a different brand color. Override --button-tone for this button only, or --color-primary for every primary-toned component (buttons, links, focus rings, soft highlights).
Either way, hover, active, rim, and bevel all recompute through the same recipe (hover at 88%, active at 78%, rim at 85%). One write, full state machinery intact. Hover the buttons to see it.
Level 2. Keep the variable surface, bypass color-mix per state
You want hand-picked swatches at each state instead of the derived progression. Set each state’s background directly. color-mix never runs because you’re writing the final value. Reach for this when the auto-derived ramp doesn’t match a designer’s hand-picked swatch, or isn’t right for an unusual hue.
Level 3. Skip the variable layer entirely
Plain CSS. Cascade and specificity resolve it; the variable layer isn’t involved at all. Reach for this when you’re styling something that doesn’t belong in Stisla’s tone model (a designer-supplied gradient CTA, for instance).
Most design systems pick one mode (forced derivation, or fully manual). Stisla offers both, and you pick per situation. Level 1 for a color change, Level 2 for a curve change, Level 3 for leaving the system. The level you reach for is the level you need. Don’t over-commit.
Sizing model
Chip-like components (buttons, form fields, pagination chips, navbar buttons, toggles, tabs) ship three independent knobs. Shape, content, icon. Override any one without touching the others.
--*-heightis the shape knob. It defaults to a--spacing()multiple (the button’s is--spacing(9)), so it tracks--spacingalong with the rest of the system. Override with a plain value (3rem) for a rigid shape that ignores the base, or with your own--spacing()multiple to keep it tracking.--*-font-sizeis the label knob. Defaults tovar(--text-sm). Doesn’t affect shape; text sits centered inside whatever height you set.--*-icon-size(and similar) is the icon knob. Defaults to a--spacing()multiple (the button’s is--spacing(4)). Bump it to1.25emfor an icon-prominent UI without touching height or label size.
Each size variant (--sm, --lg) reassigns --*-height, --*-padding-inline, and --*-font-size so the sized chip lands on its small / default / large cadence at the default spacing base.
Make one element taller or shorter
Override --*-height on the element. A plain value gives a rigid shape that doesn’t track the spacing base.
Leave the height alone if you want the element to keep tracking the base. The two buttons below sit inside a region with a larger --spacing. The first one (rigid 3rem) stays put; the second one (default height, a --spacing() multiple) grows with the region.
Bigger content, same shape
Bump --*-icon-size or --*-font-size to grow the content without touching the shape. Content centers inside the height via flex, so it grows toward the box edges without breaking the chip silhouette.
Resize a region
Scope --spacing on a wrapper. Every button, field, and chip inside scales together. Use this when “the form is tight” or “the dashboard is roomy” describes a whole area. For a single element, the height override above is enough. To retune the whole app’s density, set --spacing globally instead, covered under Density.
Per-component variables
Each component exposes its own scoped variables on top of the global tokens. Set --button-radius to give buttons a different radius from cards. Set --dialog-bg to retune a dialog without retinting the rest of the system. Component variables fall back through to the matching global token, so leaving them alone gives you the global default.
Every component page ends with a Customization section that tables its --component-* variables. Button is the reference shape (Variable, Use). When you find yourself overriding the same property across three or more components, that’s the signal to file an issue for a new knob; the feedback loop above covers when to ask and when to leave the override on the project side.
When a pattern recurs enough that it deserves to be a modifier or its own component rather than a pile of overrides, see Composition.