DESIGN SYSTEM · v4.0 · MOTION

Design that
moves.

The complete Volt language for web, Android and iOS — tokens, components, and now a full motion system: load choreography, scroll reveals, physics hovers, and the exact recipes behind each one.

✓ Live
Dark mode
PY
Build passed
42s · production
72%
--volt rgb(161 255 20) --ease-spring .34,1.56,.64,1 4PT spacing grid SORA + JetBrains Mono 2 themes · 1 token set back.out(1.4) entrances elastic.out hover release WEB · ANDROID · iOS stagger 0.06–0.12s lerp 0.1 cursor & parallax
0
Button variants
0
Components documented
0
Motion recipes, live
0
Platforms mapped
00

Principles

Rules that keep every screen — web, Android, iOS — feeling like one product.

01
One screaming accentVolt green is the only saturated color on any screen. If two things are volt, one of them is wrong.
02
Ink on volt, alwaysText on the primary is always ink — never white. Both themes, every platform.
03
4pt everythingAll spacing, sizing and radii are multiples of 4. Off-scale values get rounded onto the scale.
04
Motion is meaningEvery animation answers a question: what loaded, what changed, what's interactive, where did it go. Motion that answers nothing gets cut.
05
Theme via tokens onlyComponents never hardcode colors, so dark/light is a variable swap, not a redesign.
06
Respect the hand & the setting44/48px touch targets, and prefers-reduced-motion stills every loop and reveal — interaction still works without them.
01

Colors

Brand, semantic and surface tokens. Surfaces step in three elevation levels; text in three emphasis levels.

Brand

volt
rgb(161 255 20)
volt-hover
rgb(192 255 20)
volt-press
rgb(140 224 12)
ink
rgb(19 19 19)

Semantic

success
#4ade80
warning
#fbbf24
danger
#f87171
info
#60a5fa

Surfaces & text (current theme)

bg
--bg
surface-1
--surface-1
surface-2
--surface-2
surface-3
--surface-3
text
primary
text-2
secondary
text-3
tertiary
Contrast ruleVolt as text passes contrast only on dark surfaces. In light mode, interactive text switches to ink and volt becomes a fill/accent — baked into the tokens, not per-component decisions.
02

Typography

Sora for UI and display (400–800). JetBrains Mono for data, labels, code. Scale ratio 1.25.

display / 54 / 800Ship faster.
h1 / 39 / 800Page heading
h2 / 31 / 700Section heading
h3 / 25 / 700Subsection heading
body-lg / 16 / 400Lead paragraphs and important body copy.
body / 14 / 400Default body text across all product surfaces.
small / 12.5 / 500Secondary text, captions, helper copy.
mono / 11 / 500LABELS · TIMESTAMPS · DATA_VALUES
// Platform mapping
Web      font-family: 'Sora', sans-serif / 'JetBrains Mono', monospace
Android  FontFamily(Font(R.font.sora)) via Compose Typography
iOS      Font.custom("Sora", size:) registered in Info.plist
03

Spacing & Radius

4pt base grid — the bars below animate in with scaleX, which is exactly how progress and dividers should enter too.

sp-1 · 4px
sp-2 · 8px
sp-3 · 12px
sp-4 · 16px
sp-6 · 24px
sp-8 · 32px
sp-12 · 48px
sp-16 · 64px
sp-20 · 80px

Radius scale

sm · 8
md · 12
lg · 16
xl · 22
full

Buttons md · inputs md · cards lg · modals & sheets xl · chips full. Nested radius = parent radius − padding.

04

Motion Language

Motion in Volt has a grammar. Three layers, four easings, five durations — and one hard rule: every animation must answer a question the user has.

The three layers

L1
Micro feedback — 100–250msPress scale, hover lift, focus ring, checkbox pop. Instant, springy, interruptible. This layer tells the hand "the UI heard you."
L2
Story points — 400–1200msPage-load choreography, section reveals, modal entrances, success moments. Choreographed once, ease-out or back-out, staggered. This layer narrates.
L3
Ambient life — 2–8s loopsGlow halos, float loops, pulse dots, marquee belts, shimmer. Low amplitude, sine, infinite. This layer says "the system is alive" — never louder than layers 1–2.

Easing tokens

TokenCurveGSAPUse for
ease-outcubic-bezier(.22,1,.36,1)power3.out / power4.outEverything entering the screen
ease-springcubic-bezier(.34,1.56,.64,1)back.out(1.4–1.7)Playful entrances, toggles, pops
ease-elasticelastic.out(1, .5–.6)Hover release, drag release only
ease-sineease-in-outsine.inOutAmbient loops (float, breathe)
lerp 0.05–0.2x += (target−x) · krAF loopCursor follow, mouse parallax, smooth-scroll

Duration & stagger tokens

TokenValueUse
dur-fast150mshover, press, focus
dur-base250mscolor/state changes, tooltips
dur-slow500msmodal, tab glider, layout shifts
dur-story800–1200msload reveals, hero choreography
stagger-tight40–60msletters, words, chips
stagger-base80–120mscards, list items, sections
stagger-from"center" / "start"center for hero clusters, start for lists

Hard rules

R1
Animate transform & opacity onlyNever top/left/width/height in loops — they trigger layout. translate, scale, rotate, opacity are GPU-composited. Add will-change: transform to anything a rAF loop touches.
R2
Enter fast-ish, exit fasterExits are ~60% of the enter duration and never springy. Nobody wants to watch something leave.
R3
One choreographed load per pageA single timeline with overlapping position offsets — not twenty independent delays. Everything after first-load animates on scroll trigger instead.
R4
Scroll reveals reversetoggleActions "play none none reverse" — scroll back up and the page un-tells its story. Counters are the exception: they run once.
R5
Reduced motion = still, not brokenLoops stop, reveals become instant, marquees park but stay draggable. Check prefers-reduced-motion before starting any rAF loop.
05

Recipe Lab

The exact recipes running on this page — each demo is live, replayable, and pasted below as code. This page's hero, belt, counters and reveals are these same recipes.

Reveal
Word-mask revealy:110% → 0 inside overflow:hidden · power4.out
Drop-in, stagger from centery:-90 rot:25 → 0 · back.out(1.4) · stagger center
0
Count-up statgsap.to(obj) + onUpdate · power2.out · once
3D tilt + elastic releasehover me — rotateX/Y from cursor · elastic.out(1,.6) leave
Ambient float loopy ±8 rot ±1.5° · sine.inOut · yoyo · 3–5s
Lerp mouse parallaxmove cursor here — depth per element · lerp 0.05
Success popscale 0 → 1 · elastic.out(1,.5) · confirmations only
Magnetic buttontranslate toward cursor ×0.3 · elastic release
VOLT_SYSTEM
Scramble textrandom chars settle left→right · portal moments

Recipe code — copy these

// 1 · WORD-MASK REVEAL — split, wrap, slide up inside a mask
function splitWords(el) {
  el.innerHTML = el.innerText.split(" ").map(w =>
    `<span style="display:inline-block;overflow:hidden;padding-bottom:.12em">
       <span class="w" style="display:inline-block;transform:translateY(110%)">${w}</span></span>`
  ).join(" ");
}
gsap.to(el.querySelectorAll(".w"), {
  y: 0, duration: 1, stagger: 0.05, ease: "power4.out",
  scrollTrigger: { trigger: el, start: "top 80%", toggleActions: "play none none reverse" }
});
// 2 · DROP-IN CLUSTER — hero cards falling into place
gsap.set(".fcard", { y: -700, rotation: 25, opacity: 0, scale: 0.7 });
tl.to(".fcard", {
  y: 0, opacity: 1, scale: 1, rotation: (i, el) => el.dataset.rot,
  duration: 1.1, ease: "back.out(1.4)",
  stagger: { each: 0.08, from: "center" }
}, 0.8);  // position offset — overlaps the previous tween
// 3 · COUNT-UP STAT — tween a plain object, render on update
ScrollTrigger.create({
  trigger: el, start: "top 85%", once: true,
  onEnter: () => {
    const o = { v: 0 };
    gsap.to(o, { v: target, duration: 2, ease: "power2.out",
      onUpdate: () => el.textContent = Math.floor(o.v) });
  }
});
// 4 · 3D TILT + ELASTIC RELEASE — cursor position → rotation
card.addEventListener("mousemove", (e) => {
  const r = card.getBoundingClientRect();
  const px = (e.clientX - r.left) / r.width - 0.5;
  const py = (e.clientY - r.top) / r.height - 0.5;
  gsap.to(card, { rotateY: px * 14, rotateX: -py * 14, scale: 1.05,
    transformPerspective: 800, duration: 0.4, ease: "power2.out" });
});
card.addEventListener("mouseleave", () =>
  gsap.to(card, { rotateX: 0, rotateY: 0, scale: 1,
    duration: 0.8, ease: "elastic.out(1, 0.6)" }));
// 5 · LERP LOOP — cursor follow / parallax; k = 0.05 heavy, 0.2 snappy
let x = 0, tx = 0;
addEventListener("mousemove", e => tx = e.clientX);
(function loop() {
  x += (tx - x) * 0.1;                    // the entire trick
  el.style.translate = `${x}px 0`;
  requestAnimationFrame(loop);
})();
// 6 · INFINITE BELT — clone once, drift, wrap by one copy's width
track.append(...[...track.children].map(c => c.cloneNode(true)));
const cycle = track.scrollWidth / 2;
let x = 0, paused = false;
(function frame(t) {
  if (!paused && !reducedMotion) x -= SPEED * dt(t);
  track.style.transform = `translate3d(${x % cycle}px,0,0)`;
  requestAnimationFrame(frame);
})();
// hover → paused = true · pointerdown/move → x += dx (drag scrub)
// 7 · MAGNETIC BUTTON — pull toward cursor, elastic snap home
btn.addEventListener("mousemove", (e) => {
  const r = btn.getBoundingClientRect();
  gsap.to(btn, { x: (e.clientX - r.left - r.width/2) * 0.3,
                 y: (e.clientY - r.top - r.height/2) * 0.3, duration: 0.3 });
});
btn.addEventListener("mouseleave", () =>
  gsap.to(btn, { x: 0, y: 0, duration: 0.7, ease: "elastic.out(1, 0.5)" }));
06

Choreography

Recipes are notes; choreography is the song. How this very page sequences its load, and the template to reuse.

TimeElementRecipe
0.10sSidebar + topbarfade + slide, power3.out
0.30sHero titleword-mask reveal, stagger 0.08
0.80sFloating cardsdrop-in from -700, back.out, stagger from center
1.60sSub + CTAsfade-up, power3.out
2.00sMarquee beltfade in, drift starts
2.2s+Ambient layerfloat loops, glow halos, pulse begin
scrollEverything below foldScrollTrigger reveals, play/reverse
// The template — one timeline, position offsets, ambient after
const tl = gsap.timeline({ defaults: { ease: "power3.out" } });
tl.to(".chrome",  { opacity: 1, duration: 0.8 }, 0.1)
  .to(".title .w", { y: 0, duration: 0.9, stagger: 0.08 }, 0.3)
  .to(".cluster", { ...dropIn }, 0.8)     // overlaps, never waits
  .to(".sub, .cta", { opacity: 1, y: 0, duration: 0.8 }, 1.6)
  .add(startAmbientLoops, 2.2);
// Below the fold: gsap.from(section, {...}) with scrollTrigger per section.
// Rule of thumb: the whole load story ≤ 2.5s; user can scroll immediately.

Platform mapping

ConceptWebAndroid (Compose)iOS (SwiftUI)
Spring pressscale .96, ease-springanimateFloatAsState + spring(0.6f).scaleEffect + .spring(response:.3)
Staggered listgsap stagger 0.08AnimatedVisibility + delayMillis = i*80.animation(.delay(Double(i)*0.08))
Word-mask revealoverflow:hidden + translateYgraphicsLayer clip + offset anim.mask + .offset transition
Count-upobject tween + onUpdateanimateIntAsState(tween(2000))TimelineView / contentTransition(.numericText())
Shared elementView Transitions APISharedTransitionLayoutmatchedGeometryEffect
Scroll revealScrollTriggerLazyColumn + animateItem()scrollTransition { }
Reduced motionprefers-reduced-motionSettings.Global ANIMATOR_DURATION_SCALEUIAccessibility.isReduceMotionEnabled
07

Buttons

Seven variants, five sizes. Solid is the single primary action per screen. Primary CTAs get the magnetic treatment; everything presses to scale .96.

Variants

Sizes & states

08

Inputs & Forms

Volt focus ring on every control. Errors shake once (400ms) then hold — motion marks the moment, color marks the state.

We'll never share it.
Minimum 8 characters.
09

Badges & Alerts

Chips for status, alerts for page-level messages. Live states pulse (L3 ambient); alerts slide-fade in from -8px.

Live ✓ Deployed ⚠ Degraded ✕ Failed v4.0.0
Build succeededAll 214 tests passed. Deployed to production in 42s.
Heads upScheduled maintenance Sunday 02:00–03:00 IST.
Payment failedYour card was declined. Update billing to keep services running.
10

Cards

Every card here has the 3D tilt recipe attached — hover them. Reveal is y:80 + fade with 0.12 stagger; release is elastic.

Instant deploys

Push to main and it's live in under a minute. Rollback with one click.

🛰

Edge network

Served from 40+ regions — sub-50ms responses everywhere.

🔒

Zero-config auth

Sessions, OAuth and magic links wired in from the first commit.

📈

Live analytics

Count-up stats and shimmer loaders straight from this system.

12

Overlays

Four overlay species, one rule: the overlay adapts to the viewport, not the other way round. The same "Delete project?" dialog below is a centered modal on desktop and morphs into a bottom sheet under 640px — resize or open on a phone to see it.

Live demos

⚡ Spring pop tooltip

Desktop → mobile mapping

A pattern designed for a pointer must translate to a thumb. This table is the law for every Volt product:

DesktopMobile (≤640px)Why
Centered modalBottom sheet + grabberThumb reach; feels native on both platforms
Dropdown menuAction sheet (bottom)Tap targets 48px, no tiny hover menus
Hover tooltipLong-press or inline hintNo hover on touch — never hide critical info in tooltips
Sidebar navDrawer (hamburger)Reclaims width; links stagger in for orientation
Popover / hover-cardBottom sheetSame content, anchored where the thumb is
Inline dialog buttons (row)Stacked full-width, primary on bottomBottom = closest to thumb = primary action
Esc / click-outside to closeDrag-down / swipe to dismissGesture parity with the platform

Motion spec per overlay

OverlayEnterExitDismiss gestures
Dialog (desktop)scale .9→1 + y:24→0 · ease-spring · 400msfade + scale .96 · 200msEsc, scrim tap, ✕
Dialog (mobile) / SheettranslateY 100%→0 · ease-spring · 450mstranslateY →100% · ease-out · 280msdrag down >25% or fast flick, scrim tap
DrawertranslateX 105%→0 · ease-out · 450ms + links stagger 60mstranslateX →105% · 300msEsc, scrim tap, ✕, swipe right
Toastspring slide-up from bottomplain slide-down · 60% of enter (rule R2)auto 3s, swipe
Tooltipfade + y:6 + scale .9→1 · spring · 300msfade · 150mspointer leaves

Recipe — adaptive dialog & drag-to-dismiss

/* One dialog, two behaviours — CSS does the morph, JS stays identical */
.dialog { /* desktop: centered card */
  border-radius: var(--r-xl);
  transform: scale(.9) translateY(24px);
}
.open .dialog { transform: scale(1) translateY(0); }

@media (max-width: 640px) {
  .dialog-scrim { place-items: end stretch; }   /* pin to bottom */
  .dialog {
    width: 100%;
    border-radius: var(--r-xl) var(--r-xl) 0 0;  /* sheet corners */
    padding-bottom: calc(var(--sp-8) + env(safe-area-inset-bottom));
    transform: translateY(100%);                /* slide, don't scale */
  }
  .open .dialog { transform: translateY(0); }
  .dialog .grabber { display: block; }          /* affordance appears */
  .dialog .actions { flex-direction: column-reverse; } /* primary → bottom */
}
// Drag-to-dismiss: follow the finger 1:1 down, resist up,
// release past 25% (or a fast flick) → dismiss, else spring back
sheet.addEventListener("pointerdown", e => {
  startY = e.clientY; dragging = true; vel = 0;
  scrim.classList.add("dragging");        // kills the CSS transition
});
sheet.addEventListener("pointermove", e => {
  if (!dragging) return;
  const dy = e.clientY - startY;
  vel = e.clientY - lastY; lastY = e.clientY;
  // downward 1:1, upward rubber-band at 12%
  sheet.style.transform = `translateY(${dy > 0 ? dy : dy * 0.12}px)`;
  scrim.style.opacity = 1 - Math.max(0, dy) / sheet.offsetHeight; // scrim tracks drag
});
sheet.addEventListener("pointerup", () => {
  dragging = false; scrim.classList.remove("dragging");
  const dy = lastY - startY;
  if (dy > sheet.offsetHeight * 0.25 || vel > 14) close();  // distance OR velocity
  else sheet.style.transform = "";       // spring back via CSS
});
A11y non-negotiablesFocus moves into the overlay on open and returns to the trigger on close. Esc always closes. Scrim tap closes anything non-destructive. Background gets inert. Sheets/drawers are role="dialog" aria-modal="true" with a labelled heading.
13

Data & Loading

Progress fills scale from the left with shimmer on top. Skeletons sweep. Table rows reveal with a tight stagger.

PY
PY
SB
VM
+4
ServiceStatusLatency
api-gateway✓ Healthy23ms
auth-service✓ Healthy41ms
media-worker⚠ Degraded310ms
search-index✕ Down
14

Empty States & Errors

The screens nobody designs are the ones users meet first. Anatomy is fixed: icon → title → one line of copy → one primary action. Never more than one CTA, never a paragraph, never a dead end.

The five species

🚀

No projects yet

Deploy your first project and it'll show up here with live status.

FIRST-USE · TEACH + INVITE

No results for "voltt"

Check the spelling, or broaden the search.

NO-RESULTS · BLAME THE QUERY, NOT THE USER

Couldn't load deployments

The request timed out. Your data is safe — this is on us.

ERROR · OWN IT + RETRY
📡

You're offline

Showing the last synced data from 4 minutes ago. We'll reconnect automatically.

⚠ Cached view OFFLINE · DEGRADE, DON'T BLOCK
🔒

Analytics is a Pro feature

Upgrade to see request volume, latency and error budgets per region.

PERMISSION / UPSELL · SHOW THE VALUE

All caught up

No pending reviews. New ones will appear here the moment they land.

ZERO-INBOX · CELEBRATE, QUIETLY

Which state when — the loading ladder

SituationShowNever
Fetching, layout knownSkeleton matching the real layoutSpinner in a void
Fetching, layout unknown / <300msNothing, then skeleton after 300ms delayA flash of skeleton for fast loads
Loaded, zero items, first ever visitFirst-use empty (teach + CTA)"No data found"
Loaded, zero items after filter/searchNo-results empty + clear-filter actionThe first-use illustration
Request failedError empty + retry, keep stale data visible if anyWiping data the user already had
Connection lostOffline banner + cached contentA full-screen block

Copy rules

C1
Title states the fact, body states the way out"No projects yet" + "Deploy your first…". The title is never a sad apology.
C2
Errors take blame, never assign it"This is on us" — and say whether the user's data is safe, because that's their first fear.
C3
No-results blames the queryEcho the search term in quotes so the user sees the typo themselves.

Motion spec

// Empty states enter gently — they're an anticlimax, not a hero moment.
// Icon → title → copy → CTA, tight stagger, small distance:
gsap.from(empty.children, {
  y: 16, opacity: 0, duration: 0.6, stagger: 0.08, ease: "power3.out"
});
// First-use icon gets an L3 ambient float (it's the only thing on screen):
gsap.to(".empty-ico", { y: -6, duration: 2.6, ease: "sine.inOut", yoyo: true, repeat: -1 });
// Retry: button → loading state in place; on success the real content
// replaces the empty with the normal list-stagger reveal. On another
// failure, shake the empty card once (the input error recipe, reused).
15

Code & Syntax

Code blocks are UI too. JetBrains Mono 12.5/1.75, theme-aware token palette (volt keywords), header with filename + language + working copy button, gutter line numbers, line highlights, diff rows, and a terminal variant.

Token palette (current theme)

keyword string function number comment punctuation

Full block — filename, copy, line numbers, highlighted lines

useVoltTheme.ts TypeScript
1
2
3
4
5
6
7
8
9
10
import { useState, useEffect } from "react";export function useVoltTheme() { const [theme, setTheme] = useState<"dark" | "light">("dark"); useEffect(() => { document.body.dataset.theme = theme; // one attribute, whole theme }, [theme]); return { theme, toggle: () => setTheme(t => t === "dark" ? "light" : "dark") };

Diff block — reviews & changelogs

Button.tsx diff
41
42
43
44
45
const Button = ({ variant = "solid", ...props }) => ( <button style={{ transition: "all .3s ease" }} <button style={{ transition: "transform .15s var(--ease-spring)" }} onPointerDown={haptic} {...props} />

Terminal block

zsh — volt-web shell
npm create volt@latest my-app Scaffolding with dark + light tokens… Installing gsap, scrolltrigger… ✓ Done in 4.2s — cd my-app && npm run dev

Rules

K1
Copy always works, and confirmsButton flips to "✓ Copied" in success color for 1.6s, then reverts. Copy strips line numbers and diff markers — users paste code, not decoration.
K2
Volt keywords, one accent familyKeywords take the brand color; strings/functions/numbers get muted companions. In light mode every token darkens to pass 4.5:1 — same variables, different values.
K3
Highlight lines, don't paragraph-explainThe volt left-edge highlight marks the lines that matter. Max ~2 highlighted runs per block.
K4
Scroll, never wrapCode scrolls horizontally with pre whitespace. Wrapping code lies about the code.

Recipe — the copy interaction & a 12-line highlighter

// Copy with confirmation state (and stripped gutters/markers)
async function copyCode(btn) {
  const lines = btn.closest(".code-block").querySelectorAll(".ln:not(.del)");
  await navigator.clipboard.writeText([...lines].map(l => l.innerText).join("\n"));
  btn.classList.add("done"); btn.textContent = "✓ Copied";
  setTimeout(() => { btn.classList.remove("done"); btn.textContent = "⧉ Copy"; }, 1600);
}

// Minimal regex highlighter — order matters: comments, strings first
function highlight(src) {
  return src
    .replace(/(\/\/.*)/g, '<i class="tk-c">$1</i>')
    .replace(/("[^"]*"|'[^']*'|`[^`]*`)/g, '<i class="tk-s">$1</i>')
    .replace(/\b(const|let|function|return|import|export|if|else|for|await|async)\b/g,
             '<i class="tk-k">$1</i>')
    .replace(/\b(\d+\.?\d*)\b/g, '<i class="tk-n">$1</i>')
    .replace(/(\w+)(?=\()/g, '<i class="tk-f">$1</i>');
}
// Production: use Shiki (build-time, theme-perfect) or highlight.js (runtime).
// Map --tok-* variables onto the Shiki theme so dark/light stays one token set.
17

Disclosure & Layout

Accordions animate grid-template-rows: 0fr → 1fr, so height animation needs no JS measurement and never jumps. Resizable panels, scroll areas and carousels all keep their own scroll/drag state.

Accordion · single-open

Every component reads CSS variables only. That is why one Remix click restyles the whole page — including everything in this section.

Triggers are real buttons, menus use role=menuitemcheckbox, overlays trap escape, and the focus ring is a 2px offset ring on every control.

Yes, and it respects prefers-reduced-motion: transitions collapse to instant state changes rather than being merely shortened.

Popover & hover card
Frame size

Applies to the selected artboard only.

W
H
@volt_design
VD
Volt Design

The token system behind this page. 2 themes, 1 source of truth.

Joined June 2026 · 12.4k followers
Scroll area
09:41 Deploy started — build 4821
09:41 Pulling dependencies
09:42 Type-check passed
09:42 214 tests green
09:43 Image pushed to registry
09:43 Rollout 1/3
09:44 Rollout 3/3 — healthy
09:44 Cache warmed
09:45 Done in 3m 12s
Resizable panels · drag the handle
EditorDrag the divider. The split is stored as a percentage, so it survives a window resize — and a Remix.
PreviewBoth panes have min-width:0 so long content can never force the container wider.
Aspect ratio & separator
16 / 9
1 / 1

Carousel

18

Advanced Forms

The composition primitives: a field owns label + description + validation, an input group owns addons, a button group owns segmented actions. Everything below is keyboard-complete.

Field · description & validation
Lowercase, numbers and dashes. Used in your URLs.
✕ Spaces and capitals aren't allowed.
✓ Verified 2 minutes ago.
Input group · addons, icon, action
https://.volt.dev
K
$USD
Button group & split button
Combobox · searchable, typeahead
Native select · when the OS picker is right
Input OTP · auto-advance & paste

Paste a 6-digit code anywhere in the group — it fills every slot.

Toggle group
Slider · single & range
Blur radius18px
Price range$40 – $180
Spinners
Date picker · calendar in a popover
Item · list rows with media
Design tokensSynced 4 minutes agoLive
Component registry128 components
Sonner · stacked toasts

They stack, spring in, and swipe out to the right.

19

Data Table & Charts

A real table: filter, sortable headers, header checkbox with indeterminate state, row menus, pagination. Charts are hand-built SVG on the live palette — thin marks, recessive grid, hover crosshair, and identity never carried by colour alone.

Volt/ Workspaces/ Production/ Deployments
Deployment Env Status Author Duration

Charts

Requests
Last 14 days · per day
▲ 18.2%
EdgeOrigin
Build minutes
By day of week
412
▼ 4.1%
Error budget
30-day window
78%
budget remaining
4.2 min of 19 min spent
Sparklines
Stat tiles, one series, end-labelled
p95 latency
128ms
Sign-ups
1,204
20

Blocks

Whole screens assembled from the components above — the shadcn login-04, dashboard-01 and sidebar-07 shapes, rebuilt on Volt tokens. Remix restyles the blocks too, which is the real test of a token system.

Login · split with art panel

app/login/page.tsxlogin-04

Dashboard · sidebar that collapses to icons

app/dashboard/page.tsxdashboard-01 · sidebar-07
Production/Overview
PY
Requests +18%
2.41M
Error rate −0.3%
0.12%
p95 +6ms
128ms
Cost on plan
$412
Traffic
Edge vs origin · 14 days
2.41M
EdgeOrigin
Recent activity
All environments
4821 deployedproduction · 3m 12s
4820 previewfeat/remix-engine
Quota 82%build minutes
Latest deployments
DeploymentStatusAuthorDuration

Pricing · with a billing-period toggle

app/pricing/page.tsxpricing

Simple, honest pricing

Every plan includes the whole component library.

Hobby
$0/mo
  • 1 workspace
  • Community support
  • Preview deploys
Most popular
Studio
$24/mo
  • Unlimited workspaces
  • Custom domains + TLS
  • Priority builds
  • Style library sync
Enterprise
$96/mo
  • SSO + audit log
  • Private registry
  • 99.99% SLA
21

Mobile Patterns

Two reference devices rendered at their actual logical resolutions — iPhone 16 Pro at 402×874pt and Pixel 9 Pro at 427×952dp — then scaled to fit. Every bar height, inset and control inside these frames is the real platform value, annotated on the right.

Points, dp & pixels — the only conversion you need

DeviceLogicalPhysicalScaleDensityDisplay
iPhone 16 Pro402 × 874 pt1206 × 2622 px@3x (1pt = 3px)460 ppi6.3″ OLED, Dynamic Island
iPhone 16 Pro Max440 × 956 pt1320 × 2868 px@3x460 ppi6.9″ OLED
iPhone SE 3375 × 667 pt750 × 1334 px@2x (1pt = 2px)326 ppi4.7″ LCD, home button
Pixel 9 Pro427 × 952 dp1280 × 2856 pxxxhdpi (1dp = 3px)~495 ppi6.3″ LTPO OLED, 20:9
Pixel 9a~412 × 916 dp1080 × 2424 px~2.6x~422 ppi6.3″ OLED

Design in pt/dp, never px. A 44pt button is 132 physical px on an iPhone 16 Pro but 88px on an SE — same finger, same logical size. Android density buckets: mdpi 1x · hdpi 1.5x · xhdpi 2x · xxhdpi 3x · xxxhdpi 4x.

The frames — real metrics, annotated

9:41
📶 🔋 59pt safe-area top · island 125×37
‹ Back 44pt nav bar
Deploys
⚡ Deploy #482 succeeded
2 min ago · production
🛰 New region: ap-south-1
latency −34% for India
📈 2.4M requests today
+18% vs yesterday
44pt hit target
minimum tappable size (HIG)
16pt margins
Home
Projects
Activity
Settings
49pt tab bar
34pt bottom inset · indicator 134×5
iPhone 16 Pro402 × 874 pt · @3x · 460ppi · SF Pro
9:41
▲ ▼ 🔋 52dp status (camera cutout)
Deploys
PY
64dp M3 top app bar
⚡ Deploy #482 succeeded
2 min ago · production
🛰 New region: ap-south-1
latency −34% for India
48dp hit target
minimum touch size (Material)
+56dp FAB · r16
12dp M3 card radius
Home
Projects
Activity
Settings
80dp M3 nav bar · pill 64×32
24dp gesture area
Pixel 9 Pro427 × 952 dp · xxhdpi @3x · ~495ppi · Roboto/Google Sans

Platform metric sheet

SpeciOS (HIG)Android (Material 3)
Min touch target44 × 44 pt48 × 48 dp
Status bar / top inset54pt bar · 59pt safe-area top~24dp standard · 52dp on Pixel 9 (cutout)
Nav / app bar44pt (compact) · 96pt large-title64dp small · 152dp medium flexible
Bottom navigation49pt tab bar + 34pt home inset80dp nav bar, active pill 64×32dp
Home / gesture134×5pt indicator, 34pt zone108×4dp handle, 24dp zone
FAB— (toolbar buttons)56dp, radius 16dp (small 40 · large 96)
Screen margins16pt (20pt on Max)16dp compact · 24dp medium
Base type17pt body (SF Pro), Dynamic Type16sp body (Roboto), sp scales with settings
Corner radius keyscontinuous curves, concentricM3: 4 · 8 · 12 · 16 · 28dp scale
Press hapticUIImpactFeedback .lightHapticFeedbackConstants.VIRTUAL_KEY
List entrancescrollTransition + springLazyColumn animateItem() spring
Screen transitionmatchedGeometryEffectSharedTransitionLayout
Volt on nativeVolt tokens ride on top of these platform metrics, never against them: accent + surfaces + type come from Volt; bar heights, insets, touch targets and gestures come from the OS. Fight the accent war, not the ergonomics war.
22

Token Export

Copy-paste starting points, motion included. Single source of truth: the CSS variables in this file.

Web — CSS + GSAP setup

:root {
  --volt: rgb(161 255 20);  --ink: rgb(19 19 19);
  --ease-out: cubic-bezier(.22,1,.36,1);
  --ease-spring: cubic-bezier(.34,1.56,.64,1);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
gsap.registerPlugin(ScrollTrigger);

Android — Compose

val Volt = Color(0xFFA1FF14); val Ink = Color(0xFF131313)
val DarkColors = darkColorScheme(primary = Volt, onPrimary = Ink,
  background = Color(0xFF0B0D08), surface = Color(0xFF121509))
val LightColors = lightColorScheme(primary = Volt, onPrimary = Ink,
  background = Color(0xFFF7F9F2), surface = Color.White)

// Motion tokens
val SpringPop = spring<Float>(dampingRatio = 0.6f, stiffness = Spring.StiffnessMedium)
val EaseOut = tween<Float>(250, easing = CubicBezierEasing(.22f, 1f, .36f, 1f))
// Press: scale animateFloatAsState(if (pressed) 0.96f else 1f, SpringPop)

iOS — SwiftUI

extension Color {
  static let volt = Color(red: 161/255, green: 1, blue: 20/255)
  static let ink = Color(red: 19/255, green: 19/255, blue: 19/255)
}
extension Animation {
  static let voltSpring = Animation.spring(response: 0.35, dampingFraction: 0.65)
  static let voltEaseOut = Animation.timingCurve(0.22, 1, 0.36, 1, duration: 0.25)
}
Button("Explore") { }.scaleEffect(pressed ? 0.96 : 1)
  .animation(.voltSpring, value: pressed)
Changes saved to production