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 Vanilla Breeze effects compose naturally via space-separated data-effect values, CSS property separation, and the VB trigger system.
Traditional effect systems use one attribute per effect: data-shimmer, data-neon, data-reveal. This creates two problems:
The data-effect system solves both. Effects are space-separated values that compose at the CSS selector level. Triggers are a separate attribute.
Most effects use different CSS properties and compose naturally — the browser applies both without conflict:
| Effect | Primary CSS Property |
|---|---|
shimmer | background (clip: text) |
neon | text-shadow, color |
outline | -webkit-text-stroke |
gradient-text | background (clip: text) |
rainbow | filter (hue-rotate) |
bounce | animation (transform) |
wiggle | animation (rotate) |
sparkle | ::before / ::after |
When two effects target different properties, they compose with zero extra CSS.
Neon uses text-shadow. Rainbow uses filter. Both apply simultaneously:
<h1 data-effect="neon rainbow">Neon Rainbow</h1><h2 data-effect="outline shimmer">Outlined Shimmer</h2><h2 data-effect="sparkle gradient-text" class="sunset">Sparkle Gradient</h2>
When two effects target the same CSS property (like text-shadow), VB ships a compound CSS rule that merges them:
[data-effect~="neon"][data-effect~="hard-shadow"] { text-shadow: /* neon glow shadows */ 0 0 0.04em color-mix(in oklch, var(--vb-neon-color), white 60%), 0 0 0.12em var(--vb-neon-color), /* hard shadow */ var(--vb-shadow-offset) var(--vb-shadow-offset) 0 var(--vb-shadow-color);}
Tune composed effects with the standard CSS cascade:
data-effect="fade-in"class="slow" sets --vb-duration: 900msstyle="--vb-duration: 2s"<h2 data-effect="fade-in slide-up" data-trigger="scroll">Default</h2><h2 data-effect="fade-in slide-up" data-trigger="scroll" class="slow">Slow</h2><h2 data-effect="fade-in slide-up" data-trigger="scroll" class="bouncy">Bouncy</h2><h2 data-effect="fade-in slide-up" data-trigger="scroll" style="--vb-duration: 3s">Custom</h2>
An effect doesn't know what triggered it. The same fade-in slide-up effect can be activated by scroll, hover, click, or a timer — the visual result is identical:
<h2 data-effect="fade-in slide-up" data-trigger="scroll">Scroll</h2><h2 data-effect="fade-in slide-up" data-trigger="hover">Hover</h2><button data-effect="fade-in slide-up" data-trigger="click">Click</button><h2 data-effect="fade-in slide-up" data-trigger="time:2000">2s delay</h2>
Parent stagger + child effects = choreographed sequences. The data-stagger attribute sets --vb-stagger-index on each child, cascading the --vb-delay:
<ul data-stagger="80ms"> <li data-effect="fade-in slide-up" data-trigger="scroll">First</li> <li data-effect="fade-in slide-up" data-trigger="scroll">Second</li> <li data-effect="fade-in slide-up" data-trigger="scroll">Third</li></ul>
Register custom effects using the VB API. Custom effects compose with built-in effects automatically:
VB.effect('count-up', (el) => { const target = Number(el.getAttribute('value') || '0'); return { activate() { // Animate from 0 to target const duration = 1500; let start = null; function tick(ts) { if (!start) start = ts; const progress = Math.min((ts - start) / duration, 1); el.textContent = Math.round(target * progress).toLocaleString(); if (progress < 1) requestAnimationFrame(tick); } requestAnimationFrame(tick); } };});
<data value="48200" data-effect="count-up" data-trigger="scroll">0</data>
Register custom triggers that work with any effect:
VB.trigger('visible', (el, activate, param) => { const threshold = parseInt(param || '50', 10) / 100; const io = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) { io.disconnect(); activate(); } }, { threshold }); io.observe(el); return () => io.disconnect();});
<h2 data-effect="fade-in" data-trigger="visible:50">50% visibility threshold</h2>