Blocks

Whole sections, ready to paste.

Each block is one component file and one CSS file, built only from @vizzele/react. Copy both, swap the data, ship.

App showcase

An app landing hero with two live 3D phones: tick a habit on one and the score springs; drag either phone by its frame to spin it.

Habit, the app

Small streaks, big changes.

Track what matters in two taps. The phones here are live — tick a habit, or drag a phone by its frame to spin it.

This week

Focus

0 h+3.2 h vs last week

Tuesday

Today

0%of your habits done
  • 🔥 12
  • 🔥 31
  • 🔥 5
  • 🔥 18
View code
'use client';

import { useState } from 'react';
import { Button, Counter, PhoneMockup, Sparkline, Switch } from '@vizzele/react';

const habits = [
  { name: 'Morning run', streak: 12 },
  { name: 'Read 20 pages', streak: 31 },
  { name: 'No sugar', streak: 5 },
  { name: 'Meditate', streak: 18 },
];

/** Screen 1: a live habit tracker — tick a habit and the score springs. */
function HabitScreen() {
  const [done, setDone] = useState<Record<string, boolean>>({ 'Read 20 pages': true });
  const score = Object.values(done).filter(Boolean).length;
  return (
    <div className="blk-app-screen">
      <p className="blk-app-dim">Tuesday</p>
      <h3>Today</h3>
      <div className="blk-app-score">
        <Counter value={Math.round((score / habits.length) * 100)} suffix="%" />
        <span>of your habits done</span>
      </div>
      <ul>
        {habits.map((h) => (
          <li key={h.name}>
            <Switch
              label={h.name}
              checked={!!done[h.name]}
              onCheckedChange={(v) => setDone((d) => ({ ...d, [h.name]: v }))}
            />
            <small>🔥 {h.streak}</small>
          </li>
        ))}
      </ul>
    </div>
  );
}

/** Screen 2: weekly stats. */
function StatsScreen() {
  return (
    <div className="blk-app-screen">
      <p className="blk-app-dim">This week</p>
      <h3>Focus</h3>
      <div className="blk-app-score">
        <Counter value={21.5} decimals={1} suffix=" h" />
        <span>+3.2 h vs last week</span>
      </div>
      <Sparkline
        data={[2.1, 3.4, 2.8, 4.2, 3.9, 2.6, 2.5]}
        height={120}
        label="Focus hours per day"
      />
      <Button variant="liquid">Share your week</Button>
    </div>
  );
}

/** App landing hero: copy on the left, two live phones on the right. */
export function AppShowcase() {
  return (
    <div className="blk-app">
      <div className="blk-app-copy">
        <p className="blk-app-eyebrow">Habit, the app</p>
        <h3>Small streaks, big changes.</h3>
        <p>
          Track what matters in two taps. The phones here are live — tick a habit, or drag a phone
          by its frame to spin it.
        </p>
        <div className="blk-app-cta">
          <Button variant="liquid" size="lg" magnetic>
            Download for iOS
          </Button>
          <Button size="lg">Get it on Android</Button>
        </div>
      </div>
      <div className="blk-app-phones">
        <PhoneMockup model="galaxy" finish="violet" rotate={[-4, 24]} className="blk-app-back">
          <StatsScreen />
        </PhoneMockup>
        <PhoneMockup model="iphone" finish="natural" rotate={[-2, -16]} className="blk-app-front">
          <HabitScreen />
        </PhoneMockup>
      </div>
    </div>
  );
}

AI chat

An assistant that shows what it is doing: the agent thinks, works through visible steps, then answers word by word. Suggestions fuse with surface tension.

Vizzele AIReady
Hi! Ask me anything about your workspace.
View code
'use client';

import { useEffect, useRef, useState, type FormEvent } from 'react';
import {
  Agent,
  AgentSteps,
  Button,
  SurfaceTension,
  TextReveal,
  type AgentState,
  type AgentStep,
} from '@vizzele/react';

interface Message {
  id: number;
  role: 'user' | 'assistant';
  text: string;
}

const suggestions = ['Summarize last week', 'Why did signups jump?', 'Draft a release note'];

// Swap this for your model call; the UI only needs the steps and the final text.
const answers: Record<string, { steps: string[]; text: string }> = {
  'Summarize last week': {
    steps: ['Read 214 events', 'Group by feature'],
    text: 'Busy week: 3 releases, signups up 12%, and the new onboarding cut time-to-value from 4.1 to 3.2 days.',
  },
  'Why did signups jump?': {
    steps: ['Compare channels', 'Check campaigns'],
    text: 'Paid social doubled on March 3rd after the launch video. Organic held steady, so the jump is almost all campaign traffic.',
  },
  'Draft a release note': {
    steps: ['Collect merged PRs', 'Write the summary'],
    text: 'Charts that spring between datasets, an agent with seven states, and five effects you won’t find anywhere else.',
  },
};
const fallback = {
  steps: ['Search the workspace', 'Write an answer'],
  text: 'Here is what I found — this demo answers from a script, so plug in your own model to make it real.',
};

const wait = (ms: number) => new Promise((r) => setTimeout(r, ms));

/** A chat panel with an agent that shows what it is doing before it answers. */
export function AiChat() {
  const [messages, setMessages] = useState<Message[]>([
    { id: 0, role: 'assistant', text: 'Hi! Ask me anything about your workspace.' },
  ]);
  const [state, setState] = useState<AgentState>('idle');
  const [steps, setSteps] = useState<AgentStep[]>([]);
  const [draft, setDraft] = useState('');
  const busy = state !== 'idle' && state !== 'success' && state !== 'error';
  const log = useRef<HTMLDivElement>(null);

  useEffect(() => {
    log.current?.scrollTo({ top: log.current.scrollHeight, behavior: 'smooth' });
  }, [messages, steps]);

  async function ask(question: string) {
    if (!question.trim() || busy) return;
    const plan = answers[question] ?? fallback;
    setDraft('');
    setMessages((m) => [...m, { id: m.length, role: 'user', text: question }]);
    setState('thinking');
    await wait(900);
    setState('working');
    for (let i = 0; i <= plan.steps.length; i++) {
      setSteps(
        plan.steps.map((label, j) => ({
          label,
          status: j < i ? 'done' : j === i ? 'running' : 'pending',
        })),
      );
      await wait(800);
    }
    setSteps([]);
    setState('speaking');
    setMessages((m) => [...m, { id: m.length, role: 'assistant', text: plan.text }]);
    await wait(1800);
    setState('success');
    await wait(900);
    setState('idle');
  }

  const onSubmit = (e: FormEvent) => {
    e.preventDefault();
    void ask(draft);
  };

  return (
    <div className="blk-chat">
      <header className="blk-chat-head">
        <Agent state={state} name="Vizzele AI" size={36} />
      </header>

      <div className="blk-chat-log" ref={log} role="log" aria-label="Conversation">
        {messages.map((m) => (
          <div key={m.id} className="blk-chat-msg" data-role={m.role}>
            {m.role === 'assistant' && m.id > 0 ? (
              <TextReveal as="span" by="words" variant="blur" stagger={28}>
                {m.text}
              </TextReveal>
            ) : (
              m.text
            )}
          </div>
        ))}
        {steps.length > 0 && <AgentSteps className="blk-chat-steps" steps={steps} />}
      </div>

      <SurfaceTension className="blk-chat-suggest" role="group" aria-label="Suggestions">
        {suggestions.map((s) => (
          <button key={s} type="button" disabled={busy} onClick={() => void ask(s)}>
            {s}
          </button>
        ))}
      </SurfaceTension>

      <form className="blk-chat-form" onSubmit={onSubmit}>
        <input
          aria-label="Message"
          placeholder="Ask anything…"
          value={draft}
          onChange={(e) => setDraft(e.target.value)}
        />
        <Button type="submit" variant="liquid" disabled={busy || !draft.trim()}>
          Send
        </Button>
      </form>
    </div>
  );
}

Analytics dashboard

One filter row above everything it scopes; change the range and every stat, trend and chart springs to the new data.

Visitors
0
+8.2%vs last period
Signups
0
+12.4%vs last period
Paid conversions
0
-2.1%vs last period

Visitors

Visitors
labelVisitors
W12.4K
W22.9K
W33.7K
W44.6K
W55.4K
W65.8K
W75.7K
W85.2K
W94.6K
W103.9K
W113.6K
W123.8K

Signups vs paid

  • Signups
  • Paid
Signups vs paid
labelSignupsPaid
W1445102
W256283
W360774
W456280
W5462100
W6369130
W7344166
W8410199
W9536223
W10660234
W11720230
W12690213

Plan mix

  • Free4.5K71%
  • Pro1.5K23%
  • Studio3676%
Plan mix
SliceValueShare
Free4.5K71%
Pro1.5K23%
Studio3676%
View code
'use client';

import { useMemo, useState } from 'react';
import { AreaChart, BarChart, DonutChart, Select, StatCard } from '@vizzele/react';

const ranges = [
  { value: '7', label: 'Last 7 days' },
  { value: '30', label: 'Last 30 days' },
  { value: '90', label: 'Last 90 days' },
];

/** Replace with your data source; the charts morph whenever the rows change. */
function load(days: number) {
  const n = days === 7 ? 7 : 12;
  const scale = days / 30;
  return Array.from({ length: n }, (_, i) => ({
    label: days === 7 ? ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'][i]! : `W${i + 1}`,
    visitors: Math.round((3800 + 1400 * Math.sin(i / 1.7 + days) + i * 120) * scale),
    signups: Math.round((420 + 160 * Math.cos(i / 1.3 + days) + i * 14) * scale),
    paid: Math.round((120 + 60 * Math.sin(i / 2 + days * 2) + i * 6) * scale),
  }));
}

/** A dashboard slice: one filter row above, stats, a trend, a breakdown and a share. */
export function Analytics() {
  const [range, setRange] = useState('30');
  const rows = useMemo(() => load(Number(range)), [range]);
  const sum = (k: 'visitors' | 'signups' | 'paid') => rows.reduce((a, r) => a + r[k], 0);
  return (
    <div className="blk-analytics">
      <div className="blk-analytics-filters">
        <Select
          label="Date range"
          size="sm"
          options={ranges}
          value={range}
          onValueChange={setRange}
        />
      </div>
      <div className="blk-analytics-stats">
        <StatCard
          label="Visitors"
          value={sum('visitors')}
          delta={8.2}
          trend={rows.map((r) => r.visitors)}
        />
        <StatCard
          label="Signups"
          value={sum('signups')}
          delta={12.4}
          trend={rows.map((r) => r.signups)}
          trendColor="var(--vz-chart-2)"
        />
        <StatCard
          label="Paid conversions"
          value={sum('paid')}
          delta={-2.1}
          trend={rows.map((r) => r.paid)}
          trendColor="var(--vz-chart-3)"
        />
      </div>
      <div className="blk-analytics-card">
        <h3>Visitors</h3>
        <AreaChart
          title="Visitors"
          data={rows}
          index="label"
          series={[{ key: 'visitors', label: 'Visitors' }]}
          height={220}
        />
      </div>
      <div className="blk-analytics-row">
        <div className="blk-analytics-card">
          <h3>Signups vs paid</h3>
          <BarChart
            title="Signups vs paid"
            data={rows}
            index="label"
            series={[
              { key: 'signups', label: 'Signups' },
              { key: 'paid', label: 'Paid' },
            ]}
            height={200}
          />
        </div>
        <div className="blk-analytics-card">
          <h3>Plan mix</h3>
          <DonutChart
            title="Plan mix"
            size={170}
            thickness={16}
            data={[
              { label: 'Free', value: sum('signups') - sum('paid') },
              { label: 'Pro', value: Math.round(sum('paid') * 0.8) },
              { label: 'Studio', value: Math.round(sum('paid') * 0.2) },
            ]}
          />
        </div>
      </div>
    </div>
  );
}

Pricing

Prices count between monthly and yearly on a spring; the featured plan is a holographic card that tilts toward you.

Starter

Everything in the open-source library.

$0/ month

  • All components & effects
  • Community support
  • MIT license

Studio

For agencies building many products.

$0/ month

  • Everything in Pro
  • Unlimited projects
  • Custom themes
  • Onboarding call
View code
'use client';

import { useState } from 'react';
import { Button, Card, Counter, HoloCard, Tabs } from '@vizzele/react';

const plans = [
  {
    name: 'Starter',
    monthly: 0,
    blurb: 'Everything in the open-source library.',
    features: ['All components & effects', 'Community support', 'MIT license'],
    cta: 'Get started',
  },
  {
    name: 'Pro',
    monthly: 24,
    blurb: 'For teams shipping interfaces with weight.',
    features: [
      'Everything in Starter',
      'Premium blocks & templates',
      'Figma kit',
      'Priority support',
    ],
    cta: 'Start free trial',
    featured: true,
  },
  {
    name: 'Studio',
    monthly: 79,
    blurb: 'For agencies building many products.',
    features: ['Everything in Pro', 'Unlimited projects', 'Custom themes', 'Onboarding call'],
    cta: 'Talk to us',
  },
];

const Check = () => (
  <svg viewBox="0 0 24 24" width="16" height="16" fill="none" aria-hidden="true">
    <path
      d="M5 12.5l4.5 4.5L19 7.5"
      stroke="currentColor"
      strokeWidth="2.4"
      strokeLinecap="round"
    />
  </svg>
);

/** Three plans; prices spring between monthly and yearly, the featured plan is holographic. */
export function Pricing() {
  const [cycle, setCycle] = useState('monthly');
  const yearly = cycle === 'yearly';
  return (
    <div className="blk-pricing">
      <Tabs
        label="Billing cycle"
        value={cycle}
        onValueChange={setCycle}
        items={[
          { value: 'monthly', label: 'Monthly' },
          { value: 'yearly', label: 'Yearly · save 20%' },
        ]}
      />
      <div className="blk-pricing-grid">
        {plans.map((p) => {
          const price = yearly ? Math.round(p.monthly * 0.8) : p.monthly;
          const body = (
            <>
              <h3>{p.name}</h3>
              <p className="blk-pricing-blurb">{p.blurb}</p>
              <p className="blk-pricing-price">
                <Counter value={price} prefix="$" />
                <span>/ month{yearly && p.monthly > 0 ? ', billed yearly' : ''}</span>
              </p>
              <ul>
                {p.features.map((f) => (
                  <li key={f}>
                    <Check />
                    {f}
                  </li>
                ))}
              </ul>
              <Button variant={p.featured ? 'liquid' : 'default'} magnetic={p.featured}>
                {p.cta}
              </Button>
            </>
          );
          return p.featured ? (
            <HoloCard key={p.name} className="blk-pricing-card is-featured" max={8}>
              <span className="blk-pricing-badge">Most popular</span>
              {body}
            </HoloCard>
          ) : (
            <Card key={p.name} className="blk-pricing-card">
              {body}
            </Card>
          );
        })}
      </div>
    </div>
  );
}

Sign in

A liquid-glass card over a living mesh gradient, with inputs of light, a loading button and a toast.

View code
'use client';

import { useState, type FormEvent } from 'react';
import { Button, Input, LiquidGlass, Switch, toast, Toaster } from '@vizzele/react';
import { MeshGradient } from '@vizzele/react/gl';

/** A sign-in card of liquid glass floating over a living gradient. */
export function SignIn() {
  const [loading, setLoading] = useState(false);
  const onSubmit = (e: FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setTimeout(() => {
      setLoading(false);
      toast.success('Signed in — welcome back');
    }, 1200);
  };
  return (
    <div className="blk-signin">
      <MeshGradient
        className="blk-signin-bg"
        speed={0.2}
        colors={['#00e5ff', '#7c5cff', '#ff2bd6', '#3b2bff']}
      />
      <LiquidGlass className="blk-signin-card">
        <form onSubmit={onSubmit}>
          <h3>Welcome back</h3>
          <p>Sign in to keep building interfaces that feel alive.</p>
          <Input label="Email" type="email" autoComplete="email" required />
          <Input label="Password" type="password" autoComplete="current-password" required />
          <div className="blk-signin-row">
            <Switch label="Remember me" defaultChecked />
            <a href="#forgot">Forgot?</a>
          </div>
          <Button type="submit" variant="liquid" loading={loading}>
            Sign in
          </Button>
          <Button type="button" variant="ghost" onClick={() => toast('GitHub sign-in goes here')}>
            Continue with GitHub
          </Button>
        </form>
      </LiquidGlass>
      <Toaster />
    </div>
  );
}