Quickstart

Build a canvas where everyone in the room sees each other’s cursor. You’ll need Node.js 22 or later and two terminals.

1. Create a project

mkdir shared-cursors
cd shared-cursors
npm init -y
npx irtio init --tick
npm install
npm install --save-dev vite typescript vitest

The scaffold creates the shared schema, room code, and a room test in irtio/. The schema reads your project id from irtio.json. There is no id until your first irtio deploy, which gets one from irt.io and writes it there. The id identifies your project and is safe to include in the browser bundle.

2. Create the page

Create index.html at the project root:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Shared cursors</title>
  </head>
  <body>
    <p>Move your pointer. Copy this page's URL into a second tab.</p>
    <canvas id="stage" width="800" height="500" style="border: 1px solid #aaa"></canvas>
    <script type="module" src="/main.ts"></script>
  </body>
</html>

3. Connect the canvas

Create main.ts next to index.html:

import { joinRoom } from '@irtio/client';
import { schema } from './irtio/schema';

const canvas = document.querySelector<HTMLCanvasElement>('#stage')!;
const ctx = canvas.getContext('2d')!;
const room = await joinRoom(schema, { name: 'Player' });

canvas.addEventListener('pointermove', (event) => {
  const player = room.state.players[room.me];
  if (!player) return;
  const bounds = canvas.getBoundingClientRect();
  player.x = (event.clientX - bounds.left) * canvas.width / bounds.width;
  player.y = (event.clientY - bounds.top) * canvas.height / bounds.height;
});

function frame() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  for (const [, player] of room.render.players) {
    ctx.fillStyle = `hsl(${player.color * 1.4} 80% 45%)`;
    ctx.beginPath();
    ctx.arc(player.x, player.y, 8, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillText(player.name, player.x + 12, player.y);
  }
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

Write your own player’s values through room.state. Read room.render when drawing so remote movement is interpolated between updates.

4. Run it

Start the room in one terminal:

npx irtio dev

In a second terminal, from the same directory:

npx vite --host localhost

Open the localhost URL Vite prints. irtio adds ?room=CODE to the address. Copy that whole URL into a second tab and move the pointer in each canvas. Both cursors should appear in both tabs.

Keep both processes running. Vite serves the browser page. The room normally listens on port 7070, and falls back to 7171, 7272, 7373 or 7474 when it cannot bind that. The client probes those same ports, so a page with no url option finds the dev server either way. To pin the port, start the dev server with npx irtio dev --port 7070.

irtio dev keeps room state across restarts in .irtio/snapshots. Add --reset-state to delete those snapshots first so every room starts fresh. Useful when you’re changing the schema often.

5. Run the room test

npx vitest run irtio/room.test.ts

The generated test checks that a player’s write reaches the server and another client. Next, add a rule to your room or deploy it.