Packs

A pack is a self-contained extension that adds capabilities or a visual identity to Vanilla Breeze. Packs come in three types:

Functional

Add JS behaviors and components. No theme tokens — they work with any theme. Examples: ui, effects, prototype.

Aesthetic

Pure visual themes — token overrides for colors, fonts, radii, easing. No JS, no new components. CSS-only.

Full

Theme + effects + components in one coherent package. A complete visual identity. Examples: retro, kawaii.

Mental Model

Vanilla Breeze core ships tokens, layouts, and web components. A pack extends this with:

Theme

Token overrides scoped to [data-theme~="name"]. Changes colors, fonts, radii, easing. Zero new CSS rules — just different values.

Effects

CSS and JS animations activated by data-* attributes. Add data-neon or data-flipboard to any element.

Components

Web components with pack-specific chrome. They consume core tokens automatically — switch theme and the component adapts.

All three layers are optional. A pack can ship just a theme, just effects, just components, or any combination.

Token Inheritance

This is the key insight: pack components consume the same tokens as core. When you switch from data-theme="retro" to data-theme="ocean", every pack component re-renders with ocean's colors, fonts, and spacing. No code changes needed.

Loading a Pack

CDN (static)

Load the full pack alongside core:

<!-- Load core Vanilla Breeze first --> <link rel="stylesheet" href="/cdn/vanilla-breeze.css"> <script type="module" src="/cdn/vanilla-breeze.js"></script> <!-- Load a pack (all-in-one) --> <link rel="stylesheet" href="/cdn/packs/retro.full.css"> <script type="module" src="/cdn/packs/retro.full.js"></script>

Granular Loading

Load only the parts you need:

<!-- Theme tokens only (smallest payload) --> <link rel="stylesheet" href="/cdn/packs/retro.theme.css"> <!-- Effects only (CSS data-* animations) --> <link rel="stylesheet" href="/cdn/packs/retro.effects.css"> <script type="module" src="/cdn/packs/retro.effects.js"></script> <!-- Components only (web components) --> <script type="module" src="/cdn/packs/retro.components.js"></script>

Runtime Activation

Load a pack dynamically from JavaScript:

import { activateBundle } from '/cdn/vanilla-breeze.js' // Load a pack at runtime (returns a Promise) await activateBundle('retro') // Custom CDN path await activateBundle('retro', { basePath: '/assets/packs' })

activateBundle injects a <link> for the CSS and import()s the JS. It returns a Promise that resolves when both are loaded.

Layer Architecture

Vanilla Breeze uses CSS @layer to manage cascade priority. Pack layers sit after core layers, ensuring pack styles always win over core defaults without specificity battles:

@layer tokens, reset, native-elements, custom-elements, web-components, utils, bundle-theme, bundle-effects, bundle-components;
Layer Purpose Owner
tokensutils Core Vanilla Breeze styles Core
bundle-theme Token overrides for [data-theme~="name"] Pack theme file
bundle-effects CSS effects activated by data-* attributes Pack effects file
bundle-components Web component styles (inside Shadow DOM) Pack component files

When no pack is loaded, the three bundle layers are empty declarations — zero cost.

Pack Anatomy

An aesthetic pack lives in src/packs/{name}/ and follows a strict file convention:

src/packs/retro/ retro.theme.css # Token overrides (@layer bundle-theme) retro.effects.css # CSS data-* effects (@layer bundle-effects) retro.effects.js # JS effects (registerEffect calls) retro.components.js # Component registration (registerComponent calls) retro.bundle.js # Pack manifest metadata fonts/ # Pack-specific assets Cartridge-Regular.woff2 Cartridge-Bold.woff2 components/ audio-player/ audio-player.js # Web component class audio-player.contract.md

The build pipeline produces granular and combined outputs:

dist/cdn/packs/ manifest.json # Registry of all built packs retro.theme.css # Theme tokens only retro.effects.css # Effects CSS only retro.effects.js # Effects JS only retro.components.js # Components JS only retro.full.css # Theme + effects CSS combined retro.full.js # Effects + components JS combined retro.bundle.js # Pack manifest retro/fonts/ # Copied assets

Theme File

The theme file overrides core tokens inside a @layer bundle-theme block, scoped by a [data-theme~="name"] selector:

/* Pack theme tokens scope to a data-theme selector */ @layer bundle-theme { [data-theme~="retro"] { --color-primary: oklch(70% 0.28 145); --radius-m: 2px; --font-mono: 'Cartridge', 'VT323', monospace; --ease-out: steps(4); } }

Because it uses the ~= (word match) selector, users can combine themes: data-theme="retro dark".

Writing Effects

CSS Effects

CSS effects are data-* attribute selectors in the bundle-effects layer. They must:

/* Effects use data-* attribute selectors in the effects layer */ @layer bundle-effects { [data-neon] { --vb-neon-color: var(--color-primary); color: var(--vb-neon-color); animation: vb-neon-pulse 2.5s ease-in-out infinite alternate; } [data-neon="pink"] { --vb-neon-color: oklch(68% 0.3 340); } [data-neon="cyan"] { --vb-neon-color: oklch(72% 0.2 200); } }

JS Effects

JS effects use the registerEffect API. The pack registry provides a shared MutationObserver that auto-initializes effects on dynamically added elements:

import { registerEffect } from '/cdn/vanilla-breeze.js' registerEffect('flipboard', { selector: '[data-flipboard]', init(el) { /* set up split-flap animation */ }, destroy(el) { /* clean up timers and DOM */ }, reducedMotionFallback(el) { /* static display */ }, })
Property Required Description
selector Yes CSS selector to match (e.g. [data-flipboard])
init(el) Yes Called when a matching element enters the DOM
destroy(el) No Called when a matching element is removed
reducedMotionFallback(el) No Called instead of init when motion is reduced

Writing Components

Registration

Components use registerComponent for priority-based conflict resolution. Since customElements.define() is permanent, the registry uses first-wins semantics with priority tiebreaking:

import { registerComponent } from '/cdn/vanilla-breeze.js' import { MyWidget } from './components/my-widget/my-widget.js' registerComponent('my-widget', MyWidget, { bundle: 'my-pack', contract: 'my-widget', priority: 10, })

Token Contract

Every pack component documents which tokens it consumes and which it exposes. This makes the dependency chain explicit and enables tooling:

/* Components consume system tokens and expose their own */ class MyWidget extends HTMLElement { static consumesTokens = ['--color-primary', '--color-surface-sunken'] static exposesTokens = ['--widget-accent', '--widget-bg'] }

Usage

<!-- Default tokens from active theme --> <audio-player src="track.mp3" data-title="Demo"></audio-player> <!-- Override tokens inline --> <audio-player src="track.mp3" style="--audioplayer-screen-color: oklch(72% 0.3 340);"> </audio-player> <!-- Mini form factor --> <audio-player src="track.mp3" data-size="mini" data-title="Compact"> </audio-player>

Component Rules

  1. Shadow DOM required — isolates component styles from the page
  2. ::part() API — expose named parts for external styling
  3. Consume tokens, not hard values — use var(--color-primary) not oklch(70% 0.28 145)
  4. Progressive enhancement — slot a native <audio> for JS-off fallback
  5. Static metadata — declare bundle, contract, version, token arrays

Available Packs

Try the packs in the interactive explorer, or load them individually in your projects.

Functional Packs

Add capabilities beyond core. Work with any theme.

UI Pack

Theme picker and environment manager for broad theme switching UIs.

Type: functional

Effects Pack

Text effects, animated images, ticker counters, and star ratings.

Type: functional

Prototype Pack

Placeholder content for rapid prototyping and wireframing.

Type: functional

Extras

Standalone modules loaded separately: data-emoji, <emoji-picker>, extended emoji set.

Type: functional

Font Packs

Opt-in variable font bundles. One file per typographic role — no multi-weight loading.

Foundation Fonts

Inter (sans/UI), Literata (serif/editorial), Recursive (mono/code). Variable fonts with axis-aware tokens.

Type: font pack (~660KB Latin subset)

Display Fonts

Fraunces (WONK/SOFT axes), Cormorant (Garamond-lineage), Bodoni Moda (dramatic contrast). For editorial and marketing.

Type: font pack (~430KB Latin subset)

Expressive Fonts

Nabla (COLR v1 3D depth), Honk (inflatable neon), Kablammo (comic impact). For hero sections and creative contexts.

Type: font pack (~610KB)

Material Icons

Material Symbols Outlined variable icon font with attractor animations. Context-aware weight and optical size.

Type: icon pack (theme + effects + JS)

Full Packs

Theme + effects + components — a complete visual identity in one package.

Retro / CRT

Phosphor green, sharp corners, Cartridge font, CRT effects, split-flap board, audio player with oscilloscope.

Type: full (theme + effects + components)

Kawaii / Cute

Pastel pink/mint/lavender palette, pill shapes, bouncy motion, Cherry Bomb One display font, sparkle particles.

Type: full (theme + effects + components)

Memphis

Bold flat colour, geometric surface patterns (stripes, dots, zigzag, squiggle, confetti), hard drop shadows, Boogaloo + Outfit + Space Mono fonts.

Type: full (theme + effects)

Try Them

Pack Explorer

Interactive demo page — load and switch between all available packs live.

Next Steps

Pack Explorer

Try packs live in the interactive demo.

Principles

Understand the progressive enhancement philosophy.

Themes

Learn about the core token system that packs extend.