Skip to content

Success-state Celebration

An account-created screen with a restrained burst: twelve particles, one shot per deliberate submit, nothing on load. Reduced motion drops the particles and leaves the check mark doing the work.

190

What you should hear: Tab reaches the control, Enter or Space activates it — and every state change is announced in the same words the page shows.

MIT licensev1.0.02.1 KB gzipzero dependenciesby Motif Studioadded 2026-09-10review due 2026-12-09
Live playground — drag, click, hoversandboxed previewsample copy

Success-state celebration

12 particles, one shot, 620 ms

Create a demo account

A success state is worth designing properly: it is the screen people screenshot. This one runs in the panel with no backend behind it.

Press Enter or the button — both paths do the same thing.

Waiting for a submit. The burst cannot fire on page load or on a loop.

Restraint is the design brief: one burst per deliberate action, no confetti behind the content, and the reduced-motion version drops the particles rather than shrinking them. The three next steps link to pages that exist.

Ambience
vibes

Ambience is a live light wash over the preview. Full palette re-theming of every demo arrives with Theme Studio (Pro).

deps: zeroJSX + TailwindMIT · original
success-burst.tsx
// Success-state Celebration — the demo's own source, extracted verbatim.
// From src/components/demos/scenes-17.tsx, export SuccessBurst.
// Needs React 18+ and Tailwind CSS v4. The classes below include this site's
// token utilities (bg-panel, text-ink-dim, chip …); their values are served as
// CSS custom properties at /api/exports/tokens.json. The reduced-motion branch
// is part of the source, not an afterthought.
// 3 helpers the demo imports are inlined above it, verbatim.
// Imports next/link — that dependency is part of the source.
// Catalog-dependent: the array below is a three-record sample of the site's own data.

import { type FormEvent, useEffect, useId, useRef, useState } from "react";

import Link from "next/link";

// The demo reads the site's own catalog: 362 catalogued assets. Three real
// records are inlined below — long fields trimmed — so this file runs as
// written. Replace the array with your own data, or read the full list from
// the site, where /api/search?q= returns the matching records as JSON.
const COMPONENTS = [
  {"slug":"prism-switch","kind":"element","title":"Prism Switch","description":"A theme-aware toggle that sweeps a six-stop prism gradient across the track on every flip. Pure…","tags":["switch","toggle"],"behaviors":["click","hover"],"stack":["React","HTML/CSS"],"deps":[],"bundleKb":1.2,"themeable":true,"a11yScore":98,"qualityScore":96,"status":"live","license":"MIT","version":"1.2.0","author":"Motif Studio","published":"2026-06-02","demo":"prism-switch","props":[{"name":"size","label":"Size","type":"range","min":20,"max":64,"step":2,"unit":"px","defaultValue":42},{"name":"hueSpeed","label":"Gradient speed","type":"range","min":0,"max":4,"step":0.1,"unit":"s","defaultValue":1.4}],"copies":4820,"views":21400,"aliases":[],"trend":[]},
  {"slug":"halo-button","kind":"element","title":"Halo Button","description":"Primary button with a mouse-tracking halo, press ripple and a springy scale. Ships as React + T…","tags":["button","magnetic"],"behaviors":["hover","click"],"stack":["React","HTML/CSS"],"deps":[],"bundleKb":2.1,"themeable":true,"a11yScore":96,"qualityScore":94,"status":"live","license":"MIT","version":"1.1.0","author":"Motif Studio","published":"2026-06-18","demo":"halo-button","props":[{"name":"magnet","label":"Magnet strength","type":"range","min":0,"max":60,"step":1,"defaultValue":24,"unit":""},{"name":"glow","label":"Halo glow","type":"range","min":0,"max":100,"step":1,"defaultValue":70,"unit":"%"}],"copies":3910,"views":17300,"aliases":["cta","call to action"],"trend":[]},
  {"slug":"pulse-loader","kind":"element","title":"Pulse Loader","description":"Three-note loader with staggered radial pulse and optional progress text. Reduced-motion safe a…","tags":["loader","loading"],"behaviors":[],"stack":["HTML/CSS","React"],"deps":[],"bundleKb":0.8,"themeable":true,"a11yScore":99,"qualityScore":93,"status":"live","license":"MIT","version":"1.0.3","author":"Motif Studio","published":"2026-05-28","demo":"pulse-loader","props":[{"name":"speed","label":"Pulse speed","type":"range","min":0.2,"max":2.4,"step":0.05,"unit":"s","defaultValue":1},{"name":"dots","label":"Dots","type":"range","min":1,"max":8,"step":1,"unit":"","defaultValue":3}],"copies":5220,"views":19800,"aliases":["spinner","loading"],"trend":[]},
];

// The demo reads the site's own catalog: 118 guides. Three real
// records are inlined below — long fields trimmed — so this file runs as
// written. Replace the array with your own data, or read the full list from
// the site, where /api/search?q= returns the matching records as JSON.
const LEARN_ARTICLES = [
  {"slug":"hero-that-breathes-in-20-min","kicker":"Build along","series":{"name":"The fundamentals","part":1,"of":4},"title":"Build a hero that breathes — in 20 minutes","deck":"Most hero sections die on the fold because nothing in them moves with intent. This walkthrough …","minutes":20,"level":"Beginner","tags":["hero","animation"],"updated":"2026-09-09","blocks":[{"h":"Why heroes feel dead","body":["A hero is one glance. If every pixel is still, the eye treats it like a magazine cover — read once, scroll on. You don't need autoplay video or a 3D library; you need three cheap layers of life that reinforce one message.","The recipe we use at Motif: a living backdrop (motion you can ignore), one typographic moment (motion you can't miss), and one honest action (motion under your finger)."],"links":[{"label":"Aurora Veil — living backdrop","href":"/components/aurora-veil"},{"label":"Wipe Reveal — typographic moment","href":"/components/wipe-reveal"}],"code":{},"callout":{}},{"h":"Step 1 — stack the veil","body":["Start with the backdrop so everything after it has something to sit on. Aurora Veil is three radial gradients drifting on a slow clock plus a grain pass — GPU-composited, zero JavaScript.","Resist cranking the speed. A drift that takes 16–20s per cycle reads as 'alive but expensive'. Faster reads as 'screensaver'."],"links":[],"code":{"title":"veil layer","lang":"tsx","text":"<section className=\"relative min-h-[90vh] overflow-hidden bg-[#07080d]\">\n  {/* layer 1: living backdrop */}\n  <AuroraVeil hueA={262} hueB={192} speed={18} grain\n              className=\"absolute inset-0\" />\n  {/* layer 2+3 arrive below */}\n</section>"},"callout":{}}],"takeaways":[],"revised":"","revisedNote":"","author":"","prereqs":[],"footnotes":[]},
  {"slug":"easing-is-a-language","kicker":"Field guide","series":{"name":"The fundamentals","part":2,"of":4},"title":"Easing is a language — a field guide to curves","deck":"The single highest-leverage skill in web animation is choosing the right curve. This guide maps…","minutes":12,"level":"Intermediate","tags":["easing","motion"],"updated":"2026-09-08","blocks":[{"h":"Start with the feeling, not the curve","body":["Every easing curve is a personality. Linear says 'machine'. ease-out says 'expensive'. back-out says 'confident'. elastic says 'celebration'. If you pick curves by copying presets, your page has the personality of a default theme."],"bullets":[],"code":{},"callout":{},"links":[]},{"h":"The six curves that cover 90% of UI","body":[],"bullets":["linear — progress bars, marquees, anything mechanical. Never for entrance motion.","ease-out-expo (0.16 1 0.3 1) — the workhorse entrance. Fast start, long settle = 'premium'. Use for cards, sections, images entering."],"code":{"title":"the workhorse","lang":"css","text":"/* the 'expensive' entrance: fast to 80%, then glide */\n.card-enter {\n  animation: rise .7s cubic-bezier(.16, 1, .3, 1) both;\n}\n@keyframes rise {\n  from { opacity: 0; transform: translateY(16px); }\n  to   { opacity: 1; transform: none; }\n}\n\n/* overshoot for ONE hero element */\n.spotlight {\n  transition: transform .5s cubic-bezier(.34, 1.56, .64, 1);\n}"},"callout":{},"links":[]}],"takeaways":[],"revised":"","revisedNote":"","author":"","prereqs":[],"footnotes":[]},
  {"slug":"prompt-that-reproduces","kicker":"AI workflows","series":{"name":"The fundamentals","part":3,"of":4},"title":"Write AI prompts that reproduce — not ones that 'inspire'","deck":"Most AI website prompts get you 60% fidelity because they describe vibes ('make it sleek') inst…","minutes":15,"level":"Intermediate","tags":["ai","prompts"],"updated":"2026-09-09","blocks":[{"h":"Constraints beat adjectives","body":["In every run log on this site you'll see the same pattern: prompts with concrete constraints score 10–15 points higher than adjective prompts. 'Sleek' is unmeasurable; '16px radius, near-black #07080B canvas, one violet→cyan accent used only for CTAs' is a spec."],"bullets":["Give the palette as hex/HSL, and say where each colour is *allowed* — restraint instructions land harder than colour names.","Give a component inventory (nav, bento features, pricing toggle) so the model doesn't invent sections."],"code":{},"callout":{},"links":[]},{"h":"The skeleton that scores 90+","body":[],"bullets":[],"code":{"title":"prompt skeleton","lang":"text","text":"Build a [page type] in [stack].\n\nPalette\n• canvas / ink / accent (hex) — and where each is allowed\n• accent reserved for [moments]\n\nAbove the fold\n• [section]: [2-3 concrete requirements, incl. typography scale]\n• [headline copy — write it for me, in this voice: ___]\n\nSections (in order)\n1. [name] — [what it must contain + interaction]\n2. [name] — ...\n3. [name] — ...\n\nMotion budget\n• entrances: [easing/duration hint]\n• interactive: [hover/click rules]\n• prohibited: [list]\n\nTone & traps\n• write copy like [persona]\n• avoid [stock clichés]\n• accessibility: [WCAG notes]"},"callout":{},"links":[]}],"takeaways":[],"revised":"","revisedNote":"","author":"","prereqs":[],"footnotes":[]},
];

function useReducedMotion() {
  const [reduced, setReduced] = useState<boolean>(() =>
    typeof window !== "undefined" ? window.matchMedia("(prefers-reduced-motion: reduce)").matches : false
  );
  useEffect(() => {
    const mq = window.matchMedia("(prefers-reduced-motion: reduce)");
    const on = (e: MediaQueryListEvent) => setReduced(e.matches);
    mq.addEventListener("change", on);
    return () => mq.removeEventListener("change", on);
  }, []);
  return reduced;
}

const BURST_PARTICLES = 12;

const NEXT_STEPS = [
  { href: "/components", label: "Open the catalog", meta: `${COMPONENTS.length} components` },
  { href: "/learn", label: "Read a guide", meta: `${LEARN_ARTICLES.length} essays` },
  { href: "/lab", label: "Try the Lab", meta: "free tools, no sign-in" },
];

export function SuccessBurst() {
  const uid = useId();
  const [email, setEmail] = useState("");
  const [created, setCreated] = useState(false);
  const [error, setError] = useState("");
  const [burst, setBurst] = useState(0);
  const reduced = useReducedMotion();
  const hostRef = useRef<HTMLDivElement>(null);

  // One shot per submit, twelve particles, and nothing at all under reduced
  // motion — the check mark itself is the whole celebration there.
  useEffect(() => {
    if (!burst || reduced) return;
    const host = hostRef.current;
    if (!host) return;
    const particles = host.querySelectorAll<HTMLElement>("[data-particle]");
    const anims: Animation[] = [];
    particles.forEach((el, i) => {
      const angle = (i / Math.max(1, particles.length)) * Math.PI * 2;
      const distance = 46 + (i % 3) * 10;
      anims.push(
        el.animate(
          [
            { transform: "translate(0px, 0px) scale(1)", opacity: 1 },
            {
              transform: `translate(${Math.round(Math.cos(angle) * distance)}px, ${Math.round(Math.sin(angle) * distance)}px) scale(.35)`,
              opacity: 0,
            },
          ],
          { duration: 620, easing: "cubic-bezier(.22,.61,.36,1)" },
        ),
      );
    });
    return () => anims.forEach((a) => a.cancel());
  }, [burst, reduced]);

  const submit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    if (!email.trim()) {
      setError("Type an address first — nothing is sent either way.");
      return;
    }
    setError("");
    setCreated(true);
    setBurst((b) => b + 1);
  };

  return (
    <div className="flex h-full w-full flex-col items-center justify-center bg-[radial-gradient(70%_90%_at_50%_100%,rgba(52,211,153,0.10),transparent_60%),#08090f] px-6 py-6">
      <div className="w-full max-w-md">
        <div className="mb-2 flex items-baseline justify-between gap-3">
          <p className="text-[10px] font-bold uppercase tracking-[0.24em] text-emerald-300/80">Success-state celebration</p>
          <p className="text-[10px] text-ink-faint">
            {reduced ? "reduced motion: no burst, check only" : `${BURST_PARTICLES} particles, one shot, 620 ms`}
          </p>
        </div>

        <div ref={hostRef} className="relative overflow-hidden rounded-2xl border border-white/10 bg-white/[.02] p-4">
          {created ? (
            <div className="relative">
              <div className="relative mx-auto grid h-10 w-10 place-items-center" aria-hidden>
                <span className="absolute inset-0 rounded-full bg-emerald-400/15" />
                <svg viewBox="0 0 24 24" className="h-5 w-5">
                  <path d="M5 12.5 10 17.5 19 7" fill="none" stroke="rgba(110,231,183,.95)" strokeWidth="2" strokeLinecap="round" />
                </svg>
                {!reduced &&
                  Array.from({ length: BURST_PARTICLES }).map((_, i) => (
                    <span
                      key={i}
                      data-particle
                      className="absolute h-1 w-1 rounded-full bg-emerald-300/80"
                      style={{ top: "50%", left: "50%" }}
                    />
                  ))}
              </div>
              <p className="mt-2 text-center text-[12px] font-bold text-ink">Account created — in this panel only</p>
              <p className="mt-1 text-center text-[11px] leading-relaxed text-ink-dim">
                No request left the page and nothing was written to storage: reload and the form is empty again. The celebration is a picture of
                the moment, not a receipt for one.
              </p>
              <ul className="mt-3 space-y-1.5">
                {NEXT_STEPS.map((step) => (
                  <li key={step.href} className="flex items-center justify-between rounded-xl border border-white/10 bg-white/[.03] px-3 py-1.5">
                    <Link href={step.href} className="text-[11px] font-medium text-emerald-100 hover:underline">
                      {step.label}
                    </Link>
                    <span className="font-mono text-[10px] text-ink-faint">{step.meta}</span>
                  </li>
                ))}
              </ul>
            </div>
          ) : (
            <form onSubmit={submit} className="space-y-2">
              <p className="text-[12px] font-bold text-ink">Create a demo account</p>
              <p className="text-[11px] leading-relaxed text-ink-dim">
                A success state is worth designing properly: it is the screen people screenshot. This one runs in the panel with no backend
                behind it.
              </p>
              <label className="block text-[10px] uppercase tracking-widest text-ink-faint" htmlFor={`${uid}-success-email`}>
                Email
              </label>
              <input
                id={`${uid}-success-email`}
                type="email"
                value={email}
                onChange={(e) => setEmail(e.target.value)}
                placeholder="you@studio.example"
                className="w-full rounded-xl border border-white/10 bg-white/[.04] px-3 py-2 text-[11px] text-ink placeholder:text-ink-faint focus-visible:outline focus-visible:outline-2 focus-visible:outline-emerald-300"
              />
              {error ? (
                <p className="text-[10px] text-rose-300/90">{error}</p>
              ) : (
                <p className="text-[10px] text-ink-faint">Press Enter or the button — both paths do the same thing.</p>
              )}
              <button type="submit" className="btn btn-primary !px-4 !py-2 text-[11px]">
                Create account (demo)
              </button>
            </form>
          )}
        </div>

        <div className="mt-2 flex flex-wrap items-center gap-2">
          <button
            type="button"
            onClick={() => {
              setCreated(false);
              setEmail("");
              setError("");
            }}
            disabled={!created}
            className="btn btn-ghost !px-3 !py-1.5 text-[10px] disabled:opacity-40"
          >
            Start over
          </button>
          <button
            type="button"
            onClick={() => {
              setCreated(true);
              setBurst((b) => b + 1);
            }}
            className="btn btn-ghost !px-3 !py-1.5 text-[10px]"
          >
            Fire the burst again
          </button>
        </div>

        <p aria-live="polite" className="mt-2 min-h-[1rem] text-[10px] leading-relaxed text-emerald-200/80">
          {created
            ? reduced
              ? "Success state, no burst: with reduced motion the check mark is the entire celebration."
              : `Success state, burst fired ${burst} time${burst === 1 ? "" : "s"} — capped at ${BURST_PARTICLES} particles and never on load.`
            : "Waiting for a submit. The burst cannot fire on page load or on a loop."}
        </p>
        <p className="mt-1 text-[10px] leading-relaxed text-ink-faint">
          Restraint is the design brief: one burst per deliberate action, no confetti behind the content, and the reduced-motion version
          drops the particles rather than shrinking them. The three next steps link to pages that exist.
        </p>
      </div>
    </div>
  );
}

Snippet provenance

lines 1–217 · v1.0.0
v1.0.0Motif Studio2026-09-10the only version the catalog records for this asset

No changelog entry mentions “Success-state Celebration”, so the catalog records a single version for it.

Every line above is attributed to the single version this catalog records — we keep no per-line history, so no per-line attribution is invented. See the provenance page for the rule and the gaps.

Review notes · anchored to code lines

Threads attach to a line number in this asset's snippet, so a remark about the focus trap stays next to it.

demo · stored in this browser

Loading notes…

The snippet on this page is 217 lines long, so anchors run 1–217. Notes are per-asset and per-browser; a shared review thread is a server feature, and this panel is the local stand-in that proves the anchoring works.

Dependency-aware copy: zero packages to install — paste and run. All markup is original Motif UI content (MIT).

Course rail — understand it before you ship it

Theme Studio preview

Theming example — two token themes

Assets are token-driven (100% design-token driven). Here the same scene runs under two theme presets from the token sheet.

Midnight / violet

Success-state celebration

12 particles, one shot, 620 ms

Create a demo account

A success state is worth designing properly: it is the screen people screenshot. This one runs in the panel with no backend behind it.

Press Enter or the button — both paths do the same thing.

Waiting for a submit. The burst cannot fire on page load or on a loop.

Restraint is the design brief: one burst per deliberate action, no confetti behind the content, and the reduced-motion version drops the particles rather than shrinking them. The three next steps link to pages that exist.

--accent 262 · dark surfaces

Bone / amber

Success-state celebration

12 particles, one shot, 620 ms

Create a demo account

A success state is worth designing properly: it is the screen people screenshot. This one runs in the panel with no backend behind it.

Press Enter or the button — both paths do the same thing.

Waiting for a submit. The burst cannot fire on page load or on a loop.

Restraint is the design brief: one burst per deliberate action, no confetti behind the content, and the reduced-motion version drops the particles rather than shrinking them. The three next steps link to pages that exist.

--accent 40 · paper surfaces

90-second read

Usage recipe — where this fits

Use it as the signature of a single screen — a hero backdrop, an entrance, a transition. One signature per screen; everything else stays quiet.

Compose it with calm content around it: the motion earns attention only when the layout does not compete for it.

belongs in animatedclick behaviourkeyboard behaviour

Cost honesty

Performance note

2.1 KB gzipcompositor-bound — motion runs on transform/opacity layers; the cost scales with layer count and blur radius.

Cheaper alternative — the static preview variant removes the animation loop and keeps the layout — a quieter, cheaper sibling.

What it needs

Browser support strip

CSS transformsopacity compositingcustom propertiesprefers-reduced-motionwill-changeall evergreen browsers

No polyfills ship with the asset — if a listed feature is missing in a target browser, the graceful fallback is the static layout.

prefers-reduced-motion

Reduced-motion fallback

With prefers-reduced-motion: reduce, the animation loop and travel are removed; state changes keep working through opacity and colour (≤ 300ms). The demo you see above honours the switch — try it in your OS settings and the loop will quiet down.

Design rule: the reduced branch is a second design, not a stripped page — every state change stays visible.

Pressable step-through

Keyboard walk demo

Step through what a keyboard user experiences with this asset.

1

Tab to the scene — motion is paused or non-essential for keyboard users

Auto-suggested

Composition map — what pairs well

One original micro-case

Inspiration context

The hero that held attention for one beat longer

A marketing page's hero used a stock fade-in that finished in 200ms — invisible to the eye, useless to the story. Re-timed with a slow settle (0.6s, ease-out-quart) and one typographic wipe, the hero read as 'alive' without a single new asset. The change was purely a curve and a delay.

Written in-house — no screenshots, no borrowed imagery. The pattern, not the pixels.

Every version, why

Changelog — this asset

v1.0.02026-09-10

Current release — this page documents the asset as shipped.

v1.0.02026-08-18

Token pass: colours and radii moved to the design-token sheet; a11y score raised to the current bar.

v0.1.02026-07-02

Initial release to the library — original markup, MIT licensed.

30-day sparkline

Copy history

190copies this month

Sample series from this asset's usage fingerprint · last 12 days shown.

Future queue

Community remixes

Queue opens after launch. Remixes arrive as alternate versions of this asset — same job, different voice — submitted by users and audited by the studio before they appear beside the original.

0 remixes yetaudited by studiocredit + link back to the remixer

Star / favourite

Save this asset

Your collection is stored in this browser (localStorage) — no account needed.

Build a page around it

Pairs well with a prompt

This asset belongs inside a bigger build — here is the prompt that would generate a page containing it:

Motion-spec page — the animation before the layout

Spec sheet · Timing table · Easing curves

beta

Exact tree

Bundled dependencies — disclosed

success-burst.tsx16.4 KB raw · ~2 KB gzip
(no runtime dependencies)the CSS ships with the snippet

Full snippet ≈ 16.4 KB raw, ~2 KB gzip — before any framework you already load.

Download

Export as single file

Grab the asset as one file, ready to paste into your project.

Files are generated in your browser from the snippets on this page — nothing leaves the tab.

Scale, not clutter

Sizing system — three densities

Density is a token decision, not a per-page override — pick one density per product surface and hold it. Active: Default.

Runnable copy

Open in a sandbox

Opens this asset in a new tab with its stylesheet injected — a real, editable HTML document in your browser. No account, no upload.

If the tab is blocked, use the single-file export instead — same content, saved locally.

Written version first

Tutorial transcript

This asset does not have a video yet — so the written version stands in: the code line walk below narrates the snippet line by line, and the design rationale on this page explains the default decisions. When a video ships, this exact panel hosts its full transcript.

video: not yettranscript: line walk belowformat: markdown, searchable

Annotated snippet

Code line walk

The lines that matter, with the reason they matter. Click one:

Trade-off, visible

Size vs. quality slider

~2 KB~6 KB~12 KB

Standard · CSS + hover states

~6 KB

adds transitions, focus-visible and reduced-motion rules

The slider is honest: the slim tier has no JS, the pro tier pays for behaviour. Start slim, upgrade only the surfaces that need it.

HTML → React, priced

Stack-switch cost note

Taking the HTML/CSS version into a React component costs real work — and the cost is predictable:

Event wiring

Hover, focus and click become handlers — the CSS stays, the state appears.

Props for the knobs

Every inline value that should vary becomes a prop with a default — the defaults must match the CSS exactly.

The estimate

A clean port of this asset is 20–45 minutes for a React developer — the 0-character stylesheet is the easy half.

Find it the way you say it

Tag synonyms

form → input · field · controlsearch resolves all of these to this asset

When to pick which

Sibling comparison

Data table — data table
CompareSuccess-state CelebrationAurora VeilHalo Trail
KindAnimatedAnimatedAnimated
Bundle2.1 KB gzip2.6 KB gzip5.8 KB gzip
a11y96/100100/10093/100
Dependenciesnonenonenone
Copies / mo1908,9406,330
Interactionsclick, keyboardscrolldrag, hover

The honest verdict — Success-state Celebration is the lighter default here; reach for Aurora Veil when its extra weight buys an interaction this one lacks.

Read next — two guides and a prompt

picked by tag overlap, not by hand

How these three were chosen: essays scored on shared tags (0 and 0 matches), then prompt scored on block/vibe overlap (0 matches). The rule lives in src/lib/spine.ts, so the same asset always links to the same three pages and a second reader can reproduce the choice.

–remixes · this browser

The counter is a note to yourself — it adds one each time you tap, in motif:remix-count:v1, and goes nowhere. A real, public remix count would need a server and accounts; this site has neither, and it would rather say that than print a number it cannot own.

Was this helpful?