Welcome to Vanilla Breeze
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
This bell pulls live notifications from /go/notify/messages — the same contract documented at /docs/concepts/service-contracts/. Static articles like this one are the no-JS / no-backend fallback.
How to override, tweak, and extend Vanilla Breeze without fighting the framework. The cascade layer architecture means your CSS always wins — no !important needed.
Vanilla Breeze ships all framework CSS inside @layer blocks:
@layer reset, base, theme, components, utilities;
This single architectural decision makes everything else on this page work. Any CSS you write outside a layer — a plain class, an ID rule, anything unlayered — beats all layered CSS automatically.
/* Framework — inside @layer, always loses to unlayered CSS */@layer components { .card { background: var(--color-surface); }} /* Your CSS — unlayered, always wins */.card { background: hotpink; }
This is the guarantee. The four levels below all build on it.
Switch to a different theme for any section of the page by setting data-theme on an element. All descendants inherit the new theme’s tokens — colour, type, spacing, shape — automatically.
<html data-theme="swiss"> <!-- This section switches to Cyber --> <section data-theme="cyber"> <h1>Cyber Heading</h1> <p>All tokens come from the Cyber theme here.</p> </section> <!-- Back to Swiss --> <section> <p>Swiss again.</p> </section></html>
Each [data-theme] block redefines CSS custom properties at that element. Because custom properties inherit down the DOM, every child picks up the new values. Nesting works to any depth — the nearest ancestor data-theme always wins.
Notice how the same card markup produces three distinct results: default tokens, Swiss (red, Helvetica, flat), and Cyber (cyan, monospace, dark). The nested example shows Swiss → Cyber → Swiss, each layer inheriting correctly.
Stay in a theme but adjust specific token values for a section or element. This is the most surgical option — you keep all the theme’s decisions except the ones you override.
/* Stay in Swiss, just adjust a few tokens */.editorial-section { --color-primary: #555; /* Swiss red → neutral grey */ --line-height-normal: 1.9; /* More breathing room */}
Set custom properties on any element. Everything inside that element consumes the adjusted values. Font, spacing, and all other tokens remain untouched.
The first card shows standard Swiss. The second swaps --color-primary to purple — only the accent changes. The third overrides primary colour, border radius, and shadow all at once, but the typography and structure remain Swiss.
Write CSS. That’s it. Because the framework uses @layer, your rules win automatically. This handles cases where token tweaks aren’t enough — you want to change a specific property on a specific element, add a behaviour the theme doesn’t model, or just write your own component.
/* No @layer, no !important — just CSS */.pull-quote { font-size: 1.5rem; font-style: italic; border-left: 4px solid var(--color-primary); padding-left: var(--space-4); color: var(--color-text-muted);}
You can still use framework tokens inside your own rules. The token values respond to whatever theme is active at that element’s position in the DOM. Your rules add to the system rather than fighting it.
The left column shows Swiss defaults. The right column shows identical markup with unlayered classes applied: purple rounded card, italic pull-quote, pill-shaped button. No !important anywhere — the unlayered rules win by the cascade spec.
Vanilla Breeze ships a @layer utilities with token-connected single-purpose classes. Unlike Tailwind utilities that bake in values at build time, VB utilities reference tokens. They respond to the active theme.
<!-- Swiss context: .bg-primary = red --><section data-theme="swiss"> <p class="bg-primary text-inverse p-2">Swiss red</p></section> <!-- Cyber context: .bg-primary = cyan --><section data-theme="cyber"> <p class="bg-primary text-inverse p-2">Cyber cyan</p></section>
The same .bg-primary class produces Swiss red in one context and Cyber cyan in another. This is the key difference from static utility frameworks — your utilities inherit meaning from the theme.
Utilities are inside @layer utilities. Your unlayered CSS overrides them the same way it overrides any framework style:
/* Utilities are in @layer utilities — your CSS wins */.custom-highlight { color: oklch(55% 0.2 265); /* overrides .text-primary */}
All four levels compose cleanly on the same page. This demo shows a realistic magazine-style layout that uses every level together:
--color-primary and wider leading in the editorial section.pull-quote and .author-byline components (unlayered CSS).font-bold, .text-muted, .text-sm) on the bylineNo conflicts. No specificity wars. Each level operates in its own lane because the cascade layer architecture keeps the framework out of your way.
!importantIt is never needed. If your rule isn’t winning, check that it is unlayered (not inside @layer). Unlayered CSS always wins.
/* Wrong — never needed */.card { background: hotpink !important; } /* Right — just write the rule */.card { background: hotpink; }
If you declare @layer components in your own stylesheet, rules in it compete with framework component rules by source order — not a reliable override path.
/* Wrong — fights the framework by source order */@layer components { .card { background: hotpink; }} /* Right — unlayered, always wins */.card { background: hotpink; }
If the framework has --color-primary, use it. Hardcoding breaks theme inheritance.
/* Wrong — breaks when themes change */.button { background: #e63312; } /* Right — responds to the active theme */.button { background: var(--color-primary); }
When you need to deviate from defaults, pick the right level:
| Situation | Approach |
|---|---|
| Entire section needs a different visual identity | data-theme attribute on the section |
| Same theme but the accent colour differs here | Token override: --color-primary: ... |
| A specific component behaviour needs to change | Unlayered modifier class |
| One-off layout or component not in the framework | Unlayered CSS class with your own rules |
| Repeated one-liner visual adjustment | Utility class from @layer utilities |
You think you need !important |
Something is wrong — check layer architecture first |
Tailwind starts with a blank slate and builds up every visual property through classes in markup. Vanilla Breeze starts with sensible defaults and steps aside when you need to change something.
| Tailwind | Vanilla Breeze |
|---|---|
| No styles by default | Styled by default |
| Add classes to build up | Override tokens or write CSS to change |
| Style lives in HTML | Style lives in CSS |
| Utility values baked at build time | Utility values connected to live tokens |
!important for overrides |
Unlayered CSS wins automatically |
| Purge/JIT build step required | No build step |
This is not anti-utility. It’s proportional. Most elements on a page should look like the theme. Classes are for exceptions, not construction.
Browse available themes and learn the token API.
The philosophy behind the framework.
The full list of design tokens you can override.
Why each structural choice was made.