Each example is a self-contained Instant app that you can copy and paste into your own projects.
We created a full-fledged Instant app just for you. Share this page's unique URL with your friends, and you'll see them in the previews below!
Loading...
Please note: this app will automatically expire and be deleted in 2 weeks.
import { id, init } from '@instantdb/react';
const db = init({
appId: "__YOUR_APP_ID__",
});
export default function InstantTodos() {
const { data, isLoading, error } = db.useQuery({
todos: {},
});
if (error)
return <p className="p-4 flex items-center">Oops, something broke</p>;
return (
<div className="flex flex-col p-4 gap-2">
<h1 className="font-bold text-lg">InsTodo</h1>
<form
className="flex flex-col gap-2"
onSubmit={(e) => {
e.preventDefault();
const form = e.currentTarget;
const todoInput = form.elements.namedItem('todo') as HTMLInputElement;
const text = todoInput?.value;
if (!text) return;
form.reset();
db.transact([
db.tx.todos[id()].update({
text,
completed: false,
}),
]);
}}
>
<input className="py-1 border-gray-300" type="text" name="todo" />
<button type="submit" className="bg-blue-500 text-white p-1 font-bold">
Add todo
</button>
</form>
{isLoading ? (
<p className="italic text-gray-700">Loading...</p>
) : data?.todos.length ? (
<ul>
{data.todos.map((todo) => (
<li
key={todo.id}
className="flex items-center justify-between gap-2"
>
<label className="truncate">
<input
type="checkbox"
className="align-middle"
checked={todo.completed}
onChange={(e) => {
db.transact([
db.tx.todos[todo.id].update({
completed: e.currentTarget.checked,
}),
]);
}}
/>{' '}
<span
className={`align-middle ${
todo.completed ? 'line-through text-gray-400' : ''
}`}
>
{todo.text}
</span>
</label>
<button
onClick={(e) => {
db.transact([db.tx.todos[todo.id].delete()]);
}}
>
×
</button>
</li>
))}
</ul>
) : (
<p className="italic text-gray-700">No todos!</p>
)}
</div>
);
}
import { init } from '@instantdb/react';
import { useState } from 'react';
const db = init({
appId: "__YOUR_APP_ID__",
});
export default function InstantAuth() {
const { isLoading, user, error } = db.useAuth();
if (isLoading) {
return <div className={cls.root}>Loading...</div>;
}
if (error) {
return <div className={cls.root}>Uh oh! {error.message}</div>;
}
if (user) {
return <div className={cls.root}>Hello {user.email}!</div>;
}
return <Login />;
}
function Login() {
const [state, setState] = useState({
sentEmail: '',
email: '',
error: null,
code: '',
});
const { sentEmail, email, code, error } = state;
if (!sentEmail) {
return (
<form
className={cls.root}
onSubmit={async (e) => {
e.preventDefault();
if (!email) return;
setState({ ...state, sentEmail: email, error: null });
try {
await db.auth.sendMagicCode({ email });
} catch (error: any) {
setState({ ...state, error: error.body?.message });
}
}}
>
<h2 className={cls.heading}>Let's log you in!</h2>
<input
className={cls.input}
placeholder="Enter your email"
type="email"
value={email}
onChange={(e) =>
setState({ ...state, email: e.target.value, error: null })
}
/>
<button type="submit" className={cls.button}>
Send Code
</button>
{error ? <p className={cls.error}>{error}</p> : null}
</form>
);
}
return (
<form
className={cls.root}
onSubmit={async (e) => {
e.preventDefault();
if (!code) return;
try {
await db.auth.signInWithMagicCode({ email: sentEmail, code });
} catch (error: any) {
setState({ ...state, error: error.body?.message });
}
}}
>
<h2 className={cls.heading}>
Okay we sent you an email! What was the code?
</h2>
<input
className={cls.input}
type="text"
placeholder="Magic code"
value={code || ''}
onChange={(e) =>
setState({ ...state, code: e.target.value, error: null })
}
/>
<button className={cls.button}>Verify</button>
{error ? <p className={cls.error}>{error}</p> : null}
</form>
);
}
const cls = {
root: 'flex max-w-xs mx-auto flex-col gap-3 items-center h-screen px-2 pt-12',
heading: 'text-lg font-bold',
input: 'py-1 border-gray-300 rounded w-full',
button: 'bg-blue-500 text-white px-3 py-1 rounded w-full',
error: 'text-red-700 text-sm bg-red-50 border-red-500 border p-2',
};
import { Cursors, init } from '@instantdb/react';
const db = init({
appId: "__YOUR_APP_ID__",
});
const room = db.room('cursors-example', '123');
export default function InstantCursors() {
return (
<Cursors
room={room}
userCursorColor={randomDarkColor}
className={cursorsClassNames}
>
Move your cursor around! ✨
</Cursors>
);
}
const randomDarkColor = '#' + [0, 0, 0].map(() => Math.floor(Math.random() * 200).toString(16).padStart(2, '0')).join('');
const cursorsClassNames =
'flex h-screen w-screen items-center justify-center overflow-hidden font-mono text-sm text-gray-800 touch-none';
import { Cursors, init } from '@instantdb/react';
const db = init({
appId: "__YOUR_APP_ID__",
});
const room = db.room('cursors-example', '124');
function CustomCursor({ color, name }: { color?: string; name: string }) {
return (
<span
className="rounded-b-xl rounded-r-xl border-2 bg-white/30 px-3 text-xs shadow-lg backdrop-blur-md"
style={{
borderColor: color ?? 'gray',
}}
>
{name}
</span>
);
}
export default function InstantCursors() {
room.useSyncPresence({
name: userId,
});
return (
<Cursors
room={room}
renderCursor={(props) => (
<CustomCursor color={props.color} name={props.presence.name} />
)}
userCursorColor={randomDarkColor}
className={cursorsClassNames}
>
Move your cursor around! ✨
</Cursors>
);
}
const userId = Math.random().toString(36).slice(2, 6);
const randomDarkColor =
'#' +
[0, 0, 0]
.map(() =>
Math.floor(Math.random() * 200)
.toString(16)
.padStart(2, '0'),
)
.join('');
const cursorsClassNames =
'flex h-screen w-screen items-center justify-center overflow-hidden font-mono text-sm text-gray-800';
import { init } from '@instantdb/react';
import { RefObject, createRef, useRef } from 'react';
const db = init({
appId: "__YOUR_APP_ID__",
});
const { usePublishTopic, useTopicEffect } = db.room('topics-example', '123');
export default function InstantTopics() {
const publishEmoji = usePublishTopic('emoji');
useTopicEffect('emoji', ({ name, directionAngle, rotationAngle }) => {
const emojiName = name as EmojiName;
if (!emoji[emojiName]) return;
animateEmoji(
{ emoji: emoji[emojiName], directionAngle, rotationAngle },
elRefsRef.current[name].current
);
});
const elRefsRef = useRef<{
[k: string]: RefObject<HTMLDivElement>;
}>(refsInit);
return (
<div className={containerClassNames}>
<div className="flex gap-4">
{emojiNames.map((name) => (
<div className="relative" key={name} ref={elRefsRef.current[name]}>
<button
className={emojiButtonClassNames}
onClick={() => {
const params = {
name,
rotationAngle: Math.random() * 360,
directionAngle: Math.random() * 360,
};
animateEmoji(
{
emoji: emoji[name],
rotationAngle: params.rotationAngle,
directionAngle: params.directionAngle,
},
elRefsRef.current[name].current
);
publishEmoji(params);
}}
>
{emoji[name]}
</button>
</div>
))}
</div>
</div>
);
}
type EmojiName = keyof typeof emoji;
const emoji = {
fire: '🔥',
wave: '👋',
confetti: '🎉',
heart: '❤️',
} as const;
const emojiNames = Object.keys(emoji) as EmojiName[];
const refsInit = Object.fromEntries(
emojiNames.map((name) => [name, createRef<HTMLDivElement>()])
);
const containerClassNames =
'flex h-screen w-screen items-center justify-center overflow-hidden bg-gray-200 select-none';
const emojiButtonClassNames =
'rounded-lg bg-white p-3 text-3xl shadow-lg transition duration-200 ease-in-out hover:-translate-y-1 hover:shadow-xl';
function animateEmoji(
config: { emoji: string; directionAngle: number; rotationAngle: number },
target: HTMLDivElement | null
) {
if (!target) return;
const rootEl = document.createElement('div');
const directionEl = document.createElement('div');
const spinEl = document.createElement('div');
spinEl.innerText = config.emoji;
directionEl.appendChild(spinEl);
rootEl.appendChild(directionEl);
target.appendChild(rootEl);
style(rootEl, {
transform: `rotate(${config.directionAngle * 360}deg)`,
position: 'absolute',
top: '0',
left: '0',
right: '0',
bottom: '0',
margin: 'auto',
zIndex: '9999',
pointerEvents: 'none',
});
style(spinEl, {
transform: `rotateZ(${config.rotationAngle * 400}deg)`,
fontSize: `40px`,
});
setTimeout(() => {
style(directionEl, {
transform: `translateY(40vh) scale(2)`,
transition: 'all 400ms',
opacity: '0',
});
}, 20);
setTimeout(() => rootEl.remove(), 800);
}
function style(el: HTMLElement, styles: Partial<CSSStyleDeclaration>) {
Object.assign(el.style, styles);
}
import { init } from '@instantdb/react';
const db = init({
appId: "__YOUR_APP_ID__",
});
const userId = Math.random().toString(36).slice(2, 6);
const randomDarkColor =
'#' +
[0, 0, 0]
.map(() =>
Math.floor(Math.random() * 200)
.toString(16)
.padStart(2, '0'),
)
.join('');
const user = {
id: userId,
name: `${userId}`,
color: randomDarkColor,
};
const room = db.room('typing-indicator-example', '1234');
export default function InstantTypingIndicator() {
room.useSyncPresence(user);
const presence = room.usePresence();
const { active, inputProps } = room.useTypingIndicator('chat');
const peers = Object.values(presence.peers).filter((p) => p.id);
const activeMap = Object.fromEntries(
active.map((activePeer) => [activePeer.id, activePeer]),
);
return (
<div className="flex h-screen gap-3 p-2">
<div className="flex w-10 flex-col gap-2" key="side">
{peers.map((peer) => {
return (
<div
key={peer.id}
className="relative inset-0 flex h-10 w-10 items-center justify-center rounded-full border-4 bg-white"
style={{
borderColor: peer.color,
}}
>
{peer.name?.slice(0, 1)}
{activeMap[peer.id] ? (
<div className="absolute -right-1 bottom-0 rounded-sm bg-black px-1 leading-3 text-white shadow">
⋯
</div>
) : null}
</div>
);
})}
</div>
<div key="main" className="flex flex-1 flex-col justify-end">
<textarea
placeholder="Compose your message here..."
className="w-full rounded-md border-gray-300 p-2 text-sm"
onKeyDown={(e) => inputProps.onKeyDown(e)}
onBlur={() => inputProps.onBlur()}
/>
<div className="truncate text-xs text-gray-500">
{active.length ? typingInfo(active) : <> </>}
</div>
</div>
</div>
);
}
function typingInfo(typing: { name: string }[]) {
if (typing.length === 0) return null;
if (typing.length === 1) return `${typing[0].name} is typing...`;
if (typing.length === 2)
return `${typing[0].name} and ${typing[1].name} are typing...`;
return `${typing[0].name} and ${typing.length - 1} others are typing...`;
}
import { init } from '@instantdb/react';
const db = init({
appId: "__YOUR_APP_ID__",
});
const room = db.room('avatars-example', 'avatars-example-1234');
const userId = Math.random().toString(36).slice(2, 6);
const randomDarkColor =
'#' +
[0, 0, 0]
.map(() =>
Math.floor(Math.random() * 200)
.toString(16)
.padStart(2, '0'),
)
.join('');
export default function InstantAvatarStack() {
const presence = room.usePresence({
user: true,
});
room.useSyncPresence({
name: userId,
color: randomDarkColor,
});
return (
<div className="flex h-screen justify-center items-center">
{presence.user ? (
<Avatar
key={'user'}
name={presence.user.name}
color={presence.user.color}
/>
) : null}
{Object.entries(presence.peers).map(([id, peer]) => (
<Avatar key={id} name={peer.name} color={peer.color} />
))}
</div>
);
}
function Avatar({ name, color }: { name: string; color: string }) {
return (
<div
key={'user'}
className={avatarClassNames}
style={{
borderColor: color,
}}
>
{name?.slice(0, 1)}
<div className="hidden group-hover:flex absolute z-10 bottom-10 text-sm text-gray-800 bg-gray-200 rounded px-2">
{name}
</div>
</div>
);
}
const avatarClassNames =
'group relative select-none h-10 w-10 bg-gray-50 border border-4 border-black user-select rounded-full first:ml-0 flex justify-center items-center -ml-2 first:ml-0 relative';
/*
* Tile Game!
* This example is meant to mimic a simple collaborative game. We use a 4x4 grid
* that users can color. We use `merge` to update a slice of data without
* overwriting potential changes from other clients.
* */
import { init } from '@instantdb/react';
import { useEffect, useState } from 'react';
const db = init({
appId: "__YOUR_APP_ID__",
});
const { useQuery, transact } = db;
const room = db.room('main');
export default function App() {
const [hoveredSquare, setHoveredSquare] = useState(null as string | null);
const [myColor, setMyColor] = useState(null as string | null);
const { isLoading, error, data } = useQuery({ boards: {} });
const {
user: myPresence,
peers,
publishPresence,
isLoading: isPresenceLoading,
} = room.usePresence();
const boardState = data?.boards.find((b) => b.id === boardId)?.state;
useEffect(() => {
if (isLoading || isPresenceLoading) return;
if (error) return;
// If the board doesn't exist, create it
if (!boardState) {
transact([
db.tx.boards[boardId].update({
state: makeEmptyBoard(),
}),
]);
}
// If I don't have a color, generate one and publish it
// make sure to not choose a color that a peer has already chosen
if (!myColor) {
const takenColors = new Set(Object.values(peers).map((p) => p.color));
const availableColors = colors.filter((c) => !takenColors.has(c));
const color =
availableColors[Math.floor(Math.random() * availableColors.length)] ||
defaultColor;
setMyColor(color);
publishPresence({ color });
}
}, [isLoading, isPresenceLoading, error, myColor]);
if (!boardState || isLoading || isPresenceLoading)
return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div className="flex-none p-4">
<div className="flex flex-col items-center gap-2">
<div className="flex-col">
<div className="flex items-center gap-2">
Me:
<div
className="flex w-8 h-8 rounded-full border border-black"
style={{ backgroundColor: myPresence?.color }}
></div>
</div>
<div className="flex items-center gap-2">
Others:
{Object.entries(peers).map(([peerId, presence]) => (
<div
key={peerId}
className="flex w-8 h-8 rounded-full border border-black"
style={{ backgroundColor: presence.color }}
></div>
))}
</div>
</div>
<div className="board">
{Array.from({ length: boardSize }).map((row, r) => (
<div key={`row-${r}`} className="flex">
{Array.from({ length: boardSize }).map((sq, c) => (
<div
key={`idx-${r}-${c}`}
className={`flex justify-center w-12 h-12 text-lg hover:cursor-pointer hover:bg-gray-300 outline outline-black`}
style={{
backgroundColor:
hoveredSquare === `${r}-${c}`
? (myColor ?? undefined)
: boardState[`${r}-${c}`],
}}
onMouseEnter={() => setHoveredSquare(`${r}-${c}`)}
onMouseLeave={() => setHoveredSquare(null)}
onClick={() => {
transact([
db.tx.boards[boardId].merge({
state: {
[`${r}-${c}`]: myColor,
},
}),
]);
}}
></div>
))}
</div>
))}
</div>
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-4 rounded my-4"
onClick={() => {
transact([
db.tx.boards[boardId].update({
state: makeEmptyBoard(),
}),
]);
}}
>
Reset
</button>
</div>
</div>
);
}
const boardSize = 4;
const whiteColor = '#ffffff';
const defaultColor = whiteColor;
const colors = [
'#ff0000', // Red
'#00ff00', // Green
'#0000ff', // Blue
'#ffff00', // Yellow
'#ff00ff', // Purple
'#ffa500', // Orange
];
// singleton ID
const boardId = '83c059e2-ed47-42e5-bdd9-6de88d26c521';
function makeEmptyBoard() {
const emptyBoard: Record<string, string> = {};
for (let r = 0; r < boardSize; r++) {
for (let c = 0; c < boardSize; c++) {
emptyBoard[`${r}-${c}`] = whiteColor;
}
}
return emptyBoard;
}