Skip to content

Phone remote

The phone remote turns a phone into a one-handed clicker for the presenter view. It shows a live copy of the current slide, drawn by the deck’s own components, so what you see is what the room sees. Everything it does runs as a command in the presenting window, the same commands the keyboard runs, so it can do exactly what the keys can and nothing more.

For a walkthrough, see Present from your phone, even on a static host.

Control What happens
Next and back step forward or back, as Space and K do; in Laser or Zoom mode, a swipe left on the slide is next and a swipe right is back
Laser touch the slide to point at that spot on every screen; lift your finger and the dot goes
Pen draw on the slide with your finger; the same shapes and eraser as the desk’s pen, with undo and clear
Zoom tap a point to zoom into it; tap again to zoom out
Air pointer hold the button and move the phone: the laser follows where you aim
Blackout black out the audience’s screens, and bring them back
Focus show the focus labels on links, terms and embedded pages, as F does
Slides a list of every slide; tap one to jump there
Notes the part of your notes for the current click step, with A− and A+ to resize them
Timer the presenter’s stopwatch or countdown and how far ahead or behind you are; tap it to pause

The settings button holds the rest:

  • Pointer: a laser dot (a red point) or a spotlight (dims the rest of the slide).
  • Air pointer reach: how far you turn the phone to reach the slide’s edge, from ±8° to ±45°.
  • Buzz at: minutes into the talk when the phone vibrates, for a quiet “ten minutes left”.
  • Tick on every press: a short vibration on each button.

The remote keeps the phone’s screen awake while it is paired, where the browser allows a wake lock. A paired phone cannot open windows on your computer, go full screen there, or pair other phones.

  1. In the presenter view (press S on the deck), click Phone remote in the toolbar.
  2. Scan the QR code with the phone’s camera. The phone opens the deck’s /remote page and connects.

The QR code holds a one-time code, not a password or a ticket:

  • A code works once, and for one minute. While the dialog is open it changes every 30 seconds, so a photo of your screen is soon useless.
  • One phone at a time. The newest phone to scan becomes the remote; any other is disconnected and says “Another phone is the remote”.
  • A paired phone keeps a lease for its browser tab. A reload or a dropped connection reconnects it without scanning again.
  • Disconnect in the dialog drops the phone, even while it is offline, voids its lease and changes the code.
  • Copy link copies the pairing address, for when the camera can’t read the screen.

The address in the QR is …/remote#pair=CODE on a deck with a live room (the fragment never reaches a server) and …/remote?pair=CODE on kadal-deck dev or a relay.

The phone and the presenting window need something in between to carry the remote’s messages. There are three, and the deck picks the right one by itself.

Where the deck runs What carries the remote What you do
Published on deck.kadal.cc, or any host with a live room the deck’s room nothing: the QR pairs through the room
kadal-deck dev on your computer the dev server run kadal-deck dev --host so the phone can reach it
A static build (GitHub Pages, Netlify, S3, any file server) a remote relay run kadal-deck relay and point the deck at it

A deck with a room always pairs through the room, and kadal-deck dev always pairs through the dev server, whatever remote.relay says. A build pairs through a relay only when you name one; it never reaches for one on its own.

Terminal window
npx kadal-deck dev --host # on your network: the phone opens http://<your computer>:5173/
npx kadal-deck dev --host --https # a self-signed certificate, for the air pointer
npx kadal-deck dev --tunnel # on the internet through a Cloudflare Quick Tunnel (needs cloudflared)
npx kadal-deck dev --remote <password> # on your network, with the presenter pages behind a password
Terminal window
pnpm exec kadal-deck dev --host # on your network: the phone opens http://<your computer>:5173/
pnpm exec kadal-deck dev --host --https # a self-signed certificate, for the air pointer
pnpm exec kadal-deck dev --tunnel # on the internet through a Cloudflare Quick Tunnel (needs cloudflared)
pnpm exec kadal-deck dev --remote <password> # on your network, with the presenter pages behind a password
Terminal window
yarn kadal-deck dev --host # on your network: the phone opens http://<your computer>:5173/
yarn kadal-deck dev --host --https # a self-signed certificate, for the air pointer
yarn kadal-deck dev --tunnel # on the internet through a Cloudflare Quick Tunnel (needs cloudflared)
yarn kadal-deck dev --remote <password> # on your network, with the presenter pages behind a password
Terminal window
bunx kadal-deck dev --host # on your network: the phone opens http://<your computer>:5173/
bunx kadal-deck dev --host --https # a self-signed certificate, for the air pointer
bunx kadal-deck dev --tunnel # on the internet through a Cloudflare Quick Tunnel (needs cloudflared)
bunx kadal-deck dev --remote <password> # on your network, with the presenter pages behind a password

Open the presenter view at the Network address Vite prints, not at localhost: the QR code carries the address of the page it is shown on, and a phone can’t open your computer’s localhost. The dialog says so when it sees one.

--tunnel always guards the presenter, notes, notes editor and overview pages with a password, because a tunnel address is public and the dev server can write your files. Without --remote <password>, one is made up and printed. The phone needs only the QR code.

A build has no server of its own, so the presenter view and the phone meet at a small relay you run. Name it in the headmatter:

---
title: My talk
remote:
relay: wss://relay.example.com/
---

or add ?relay=wss://relay.example.com/ to the presenter view’s address (kept for that tab, and carried to the phone by the QR). The first time, the Phone remote dialog asks for the relay’s password. See Remote relay.

Hold Hold to point and move the phone: turning it left and right moves the point across the slide, tipping it up and down moves it up and down. Where you aim when you press is the middle of the slide. The movement is smoothed (a One Euro filter), so a steady hand gives a steady dot.

  • It needs the phone’s motion sensors, and browsers give those only to a secure page (https). On plain http the button says “Air pointer needs HTTPS — touch the slide to point”, and the laser still works by touch. A published deck, dev --https, dev --tunnel and an https static host with a wss:// relay are all secure.
  • On an iPhone, iOS asks for permission to use motion. It only asks after a tap, so the first time the button says Tap to allow motion · then hold to point: tap it once, allow, then hold.
  • With dev --https, the phone warns about the self-signed certificate the first time. Accept it for that address to continue.

The starter deck passes the engine’s remote page to <Deck>:

src/main.tsx
import { RemotePage } from "@kadal/deck/remote";
<Deck
manifest={manifest}
config={config}
components={components}
pages={{
presenter: PresenterPage,
audience: AudiencePage,
notes: NotesPage,
stats: StatsPage,
remote: RemotePage,
}}
/>;

A deck made before the remote existed doesn’t list remote. Since 0.3.5 the engine serves its own remote page anyway, loaded only when a phone opens /remote, so older decks work without a change.

What you see Why, and what to do
The dialog says the page is open on localhost Open the presenter view at your computer’s network address (dev --host prints it), or at the published address.
The phone says “Not paired” The code was used or is more than a minute old. Scan the current QR.
The phone says “Another phone is the remote” A newer phone scanned the QR. Scan again to take over.
The phone says “Waiting for the presenter view” It is paired, but the presenting window is closed, reloading or asleep. Bring the presenter view back; the phone reconnects by itself.
Air pointer needs HTTPS Use the published deck, dev --https or dev --tunnel, or point by touch.
The dialog asks for a password and refuses it It is the relay’s password, printed where kadal-deck relay runs. Ten wrong tries a minute from one address are refused for a minute.
Nothing happens on a static build The deck names no relay. Add remote: { relay } or ?relay= (see above).

Send feedback