Vitra CSS
Premium glassmorphism, directional particles, ambient scenery and smooth motion — CSS-only core, optional JS, zero dependencies.
v1.11.0 CSS-only core 0 dependencies
Introduction
Vitra is a lightweight, zero-dependency CSS framework built around glassmorphism, directional particles, ambient scenery, and smooth motion. The core is pure CSS: themes, glass surfaces, layout, components, and utilities all work from a single stylesheet.
JavaScript is optional and progressive. A small script (~6 kB min) adds interactive behavior — theme persistence, modals, tooltips, toasts, dropdowns, particles — but nothing in the CSS depends on it. If the script never loads, every component still renders and the page stays usable.
This page is built with Vitra itself: the glass header and sidebar, the badges, the tabs below, the toast you'll see when you copy a snippet. Switch the theme from the header dropdown and the whole page re-colors. For a full visual tour, see the interactive demo.
Installation
Add the stylesheet to <head>:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/DesvoSoft/Vitra@v1.11.0/dist/vitra.min.css" crossorigin="anonymous">
Optionally add the script before </body> — it enables modals, tooltips, toasts, dropdowns, particles, and theme persistence:
<script src="https://cdn.jsdelivr.net/gh/DesvoSoft/Vitra@v1.11.0/dist/vitra.min.js" crossorigin="anonymous" defer></script>
Always pin a version tag (@v1.11.0) in production. Subresource-integrity hashes for each release are generated at build time and published in dist/SRI.txt.
Which file do I need?
| File | Use |
|---|---|
vitra.min.css | Everything visual. Required. |
vitra.min.js | Interactive components and theme persistence. Optional. |
vitra.esm.js | Same as above for bundlers, tree-shakeable imports. |
Theming
Themes are applied with a single attribute on the root element. Every --vitra-* token — accent hue, backgrounds, surfaces, borders, text — updates instantly.
<html data-theme="dark">
Built-in themes
Seven presets, each with its own accent hue and tinted neutrals so every surface carries a trace of the accent:
auto follows the operating system's prefers-color-scheme and re-resolves live when it changes.
Persistence
With the optional script, initialize once and the visitor's choice is stored in localStorage and restored on every page that calls init:
Vitra.theme.init({ defaultTheme: 'auto', persist: true });
This docs page and the demo share that stored choice — pick a theme here and the demo opens in it.
Creating a custom theme
A theme is a [data-theme] block that overrides tokens. Start from the accent and the base surfaces:
html[data-theme="sunset"] {
--vitra-color-accent-h: 18;
--vitra-color-accent-s: 85%;
--vitra-color-accent-l: 58%;
--vitra-color-bg: hsl(18deg 30% 8%);
--vitra-color-surface: hsl(18deg 40% 90% / 6%);
--vitra-color-surface-hover: hsl(18deg 40% 90% / 10%);
--vitra-color-border: hsl(18deg 40% 90% / 12%);
--vitra-color-text-primary: hsl(18deg 20% 95%);
--vitra-color-text-secondary: hsl(18deg 15% 72%);
}
Set it with data-theme="sunset" directly. Note that Vitra.theme.set() only accepts the built-in names — apply custom themes via the attribute, or keep managing them yourself.
Components
Every example below is live — rendered by the same stylesheet you install. Components marked CSS-only need no JavaScript; needs JS means the optional script drives the behavior.
Buttons CSS-only
<button class="vitra-btn vitra-btn-solid">Solid</button>
<button class="vitra-btn vitra-btn-glass">Glass</button>
<button class="vitra-btn vitra-btn-outline">Outline</button>
Cards & Glass CSS-only
Glass card
Backdrop blur with a solid-color fallback when backdrop-filter is unsupported.
<div class="vitra-card vitra-glass">Hello, Vitra!</div>
Forms CSS-only
<input type="text" class="vitra-input" placeholder="Your name">
Tabs
Tab styling is CSS; the few lines that switch panels are yours to write (this page's switcher is ~15 lines — see the install section above working).
<div class="vitra-tabs">
<div class="vitra-tabs-nav">
<button class="vitra-tabs-tab vitra-tabs-tab-active" data-tab="tab1">One</button>
<button class="vitra-tabs-tab" data-tab="tab2">Two</button>
</div>
<div class="vitra-tabs-content">
<div class="vitra-tabs-panel" id="tab1">First panel</div>
<div class="vitra-tabs-panel" id="tab2" style="display: none;">Second panel</div>
</div>
</div>
Modals needs JS
Vitra.modal.open('my-modal'); // focus trap, Esc + overlay close, scroll lock
Tooltips needs JS
<button data-vitra-tooltip="Click to save" data-vitra-tooltip-position="top">Save</button>
Toasts needs JS
Vitra.toast.show('Saved successfully!', { type: 'success', duration: 3000 });
Dropdowns needs JS
<div class="vitra-dropdown">
<button data-vitra-dropdown-toggle>Options</button>
<div class="vitra-dropdown-menu">
<button class="vitra-dropdown-item">Edit</button>
</div>
</div>
Badges CSS-only
<span class="vitra-badge vitra-badge-primary">Primary</span>
Progress ring CSS-only
<div class="vitra-progress-ring" style="--vitra-progress-ring-value: 75;">
<div class="vitra-progress-ring-inner">75%</div>
</div>
Avatars CSS-only
<div class="vitra-avatar">JD</div>
Particles needs JS
Vitra.particles.spawn(10, { direction: 'down' }); // capped at 40 desktop / 15 mobile
JavaScript API
The script exposes a single global, Vitra, with one submodule per feature. ES module builds export the same modules for tree-shaking. Every module is optional — use only what you call.
Vitra.theme
| Method | Does |
|---|---|
get() | Current theme name ('auto' if unset). |
set(name) | Applies a built-in theme; returns false and warns on unknown names. |
toggle() | Switches light ↔ dark (resolves auto first); returns the new theme. |
init(options) | Restores stored choice; { defaultTheme: 'auto', persist: true }. |
getEffective() | Resolves auto to 'light' or 'dark'. |
getValidThemes() | Array of the seven built-in names. |
clear() | Removes the stored preference. |
Vitra.particles
Vitra.particles.spawn(10, {
container: 'body',
size: 4,
direction: 'down' // 'down' | 'down-left' | 'down-right' | angle in degrees | omit for ambient bob
});
Vitra.particles.destroy(); // all, or destroy(n)
Vitra.particles.limits(); // { active, available, max }
Hard caps: 40 particles on desktop, 15 on mobile. spawn beyond the cap is ignored with a console warning.
Vitra.reveal
Vitra.reveal.init({
selector: '.vitra-reveal',
threshold: 0.1,
rootMargin: '0px 0px 10% 0px',
stagger: 100
});
Also count() (revealed so far) and reset() (re-arm everything). With prefers-reduced-motion, elements reveal immediately.
Vitra.modal
Vitra.modal.open('my-modal', { closeOnOverlay: true, closeOnEsc: true });
Vitra.modal.close();
open accepts an id, a selector, or an element. Any child with data-vitra-modal-close closes it.
Vitra.tooltip
Vitra.tooltip.show('#save-btn', 'Saves your draft', { position: 'top' });
Vitra.tooltip.hide();
Vitra.tooltip.init(); // activates all [data-vitra-tooltip] elements
Vitra.toast
Vitra.toast.show('Saved successfully!', { type: 'success', duration: 3000 });
Types: success, error, info, default. Markup is created for you; messages are rendered as text, never HTML.
Vitra.dropdown / Vitra.ripple / Vitra.spotlight
Behavior modules with init() / destroy(). They initialize automatically (see data-config): dropdowns from [data-vitra-dropdown-toggle], ripple from .vitra-ripple elements, spotlight cursor-tracking from .vitra-spotlight.
Vitra.destroyAll()
Tears down every module — removes all listeners and injected nodes. Useful in SPAs on route teardown.
data-config
Declarative initialization without writing a line of JS: put a JSON data-config attribute on any element (conventionally <body>) and Vitra parses it on DOMContentLoaded.
<body data-config='{
"theme": { "default": "auto", "persist": true },
"reveal": { "stagger": 100 },
"particles": { "count": 12, "direction": "down" },
"dropdown": false
}'>
Defaults
Since v1.10.3, ripple, tooltip, dropdown, and spotlight initialize by default — with or without a data-config element. Setting a module to false opts out. theme, particles, and reveal are opt-in: they only run when their key is present.
| Key | Default | Value |
|---|---|---|
theme | off | Theme name, or init() options object. |
particles | off | true, or spawn() options with count. |
reveal | off | true, or init() options object. |
ripple | on | false to disable. |
tooltip | on | false to disable. |
dropdown | on | false to disable. |
spotlight | on | false to disable. |
Malformed JSON logs a [Vitra] console warning and falls back to the defaults above — it never throws.
Accessibility & Browser Support
Reduced motion
Every animation layer — particles, scenery, reveals, cinematic effects — ships inside @media (prefers-reduced-motion: reduce) guards. When the OS setting is on, ambient motion stops and reveal elements appear immediately. No configuration needed.
Graceful degradation
backdrop-filter(glass) is wrapped in@supports— unsupported browsers get a solid translucent surface instead.- Modern features (
oklch(), Popover API,@starting-style, container queries) all carry fallbacks; nothing breaks below Tier 1. - Without JavaScript, all styling works; modals, toasts, dropdowns, and theme persistence simply don't activate.
Keyboard & screen readers
Modals trap focus and restore it on close; dropdowns close on Escape and refocus their toggle; theme changes are announced via an aria-live region; tooltips set aria-describedby.
Browser support
| Tier | Browsers | What you get |
|---|---|---|
| Tier 1 | Chrome/Edge 117+, Firefox 129+, Safari 17.4+ | Full feature set, including Popover API and @starting-style. |
| Tier 2 | Chrome/Edge 88+, Firefox 97+, Safari 15.4+ | Everything core; advanced extras fall back (JS dropdown toggle, HSL colors, transition-based entries). |
| Legacy | IE 11 and older | Not supported. |