Instant Code Examples

Each example is a self-contained Instant app that you can copy and paste into your own projects.

Psst... this is a realtime page! 🔥

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!

URL
Loading...

Please note: this app will automatically expire and be deleted in 2 weeks.

Todos

2 previews
import { id, init, tx } from '@instantdb/react';

const db = init<{
  todos: {
    id: string;
    text: string;
    completed: boolean;
  };
}>({
  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([
            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([
                      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([tx.todos[todo.id].delete()]);
                }}
              >
                ×
              </button>
            </li>
          ))}
        </ul>
      ) : (
        <p className="italic text-gray-700">No todos!</p>
      )}
    </div>
  );
}

Auth

2 previews
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',
};

Cursors

2 previews
import { Cursors, init } from '@instantdb/react';

const db = init<
  {},
  {
    'cursors-example': {};
  }
>({
  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';

Custom cursors

2 previews
import { Cursors, init } from '@instantdb/react';

const db = init<
  {},
  {
    'cursors-example': {
      presence: {
        name: string;
      };
    };
  }
>({
  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';

Reactions

2 previews
import { init } from '@instantdb/react';
import { RefObject, createRef, useRef } from 'react';

const db = init<{}, RoomSchema>({
  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 }) => {
    if (!emoji[name]) return;

    animateEmoji(
      { emoji: emoji[name], 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;

type RoomSchema = {
  'topics-example': {
    topics: {
      emoji: {
        name: EmojiName;
        rotationAngle: number;
        directionAngle: number;
      };
    };
  };
};

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);
}

Typing indicator

2 previews
import { init } from '@instantdb/react';

const db = init<
  {},
  {
    'typing-indicator-example': {
      presence: {
        id: string;
        name: string;
        color: string;
      };
    };
  }
>({
  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) : <>&nbsp;</>}
        </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...`;
}

Avatar stack

2 previews
import { init } from '@instantdb/react';

const db = init<
  {},
  {
    'avatars-example': {
      presence: {
        name: string;
        color: string;
      };
    };
  }
>({
  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';

Merge tile game

2 previews
/*
 * 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, tx, id } from '@instantdb/react';
import { useEffect, useState } from 'react';

const db = init<DBSchema, RoomSchema>({
  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([
        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([
                      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([
              tx.boards[boardId].update({
                state: makeEmptyBoard(),
              }),
            ]);
          }}
        >
          Reset
        </button>
      </div>
    </div>
  );
}

type DBSchema = {
  boards: {
    state: Record<string, string>;
  };
};

type RoomSchema = {
  main: {
    presence: { color: string };
  };
};

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;
}