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
Tuesday
Today
- 🔥 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.
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
| label | Visitors |
|---|---|
| W1 | 2.4K |
| W2 | 2.9K |
| W3 | 3.7K |
| W4 | 4.6K |
| W5 | 5.4K |
| W6 | 5.8K |
| W7 | 5.7K |
| W8 | 5.2K |
| W9 | 4.6K |
| W10 | 3.9K |
| W11 | 3.6K |
| W12 | 3.8K |
Signups vs paid
- Signups
- Paid
| label | Signups | Paid |
|---|---|---|
| W1 | 445 | 102 |
| W2 | 562 | 83 |
| W3 | 607 | 74 |
| W4 | 562 | 80 |
| W5 | 462 | 100 |
| W6 | 369 | 130 |
| W7 | 344 | 166 |
| W8 | 410 | 199 |
| W9 | 536 | 223 |
| W10 | 660 | 234 |
| W11 | 720 | 230 |
| W12 | 690 | 213 |
Plan mix
- Free4.5K71%
- Pro1.5K23%
- Studio3676%
| Slice | Value | Share |
|---|---|---|
| Free | 4.5K | 71% |
| Pro | 1.5K | 23% |
| Studio | 367 | 6% |
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
Pro
For teams shipping interfaces with weight.
$0/ month
- Everything in Starter
- Premium blocks & templates
- Figma kit
- Priority support
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>
);
}