A real-time collaborative cursors and reactions demo built with beryl. Move your mouse to share your cursor, then select a reaction and click the canvas to send it to everyone in the room.
cd examples/cursors
gleam runThen open http://localhost:8000 in multiple browser tabs to see cursors move in real-time.
| Var | Default | Purpose |
|---|---|---|
PORT |
8000 |
TCP port the HTTP/WebSocket server binds to |
BIND_ADDRESS |
localhost |
Interface to bind. Set to 0.0.0.0 when running in a container or behind a proxy. |
The Dockerfile depends on the local beryl package via path = "../..", so the
build context must be the repository root, not examples/cursors:
# From the repository root:
docker build -f examples/cursors/Dockerfile -t beryl-cursors .
docker run --rm -p 8000:8000 beryl-cursorsRunning docker build . from inside examples/cursors/ will fail because the
parent beryl source is outside the build context.
The example ships with a Dockerfile and railway.toml.
- Create a new Railway service from this repository.
- In the service settings, set Root Directory to the repository root (not
examples/cursors) so the Docker build context can see the localberylpackage referenced viapath = "../..". - Set Config Path to
examples/cursors/railway.toml. - Deploy. Railway injects
PORT; the Dockerfile setsBIND_ADDRESS=0.0.0.0.
The Phoenix JS client in priv/static/app.js uses a relative /socket URL, so it automatically negotiates wss:// over Railway's TLS-terminated proxy.
| beryl Feature | How It's Used |
|---|---|
| App dispatch | cursor:* topics handle join/leave and cursor events |
| Topic patterns | Wildcard cursor:* routing matches any cursor room |
| Session presence (ETS) | The example-local session_presence tracker stores connected users and their username + color metadata in ETS |
| PubSub | broadcast_from fans out cursor moves and reactions to all other clients |
| Wire protocol | Phoenix-compatible format — works with the official Phoenix JS client |
| WebSocket transport | mist_transport.upgrade() handles Phoenix-compatible WebSocket requests |
| Rate limiting | Throttles high-frequency cursor movement messages |
Browser (vanilla JS + Phoenix client)
├── cursor movement
└── selectable, animated reactions
Server (Gleam)
├── Mist HTTP routing ── serves HTML + static files
├── beryl app-side dispatch ── cursor:* topics (cursor/app)
├── example session_presence ── ETS-backed user tracking
└── beryl pubsub ── broadcast cursor positions and reactions
- Backend: Gleam, beryl, mist
- Frontend: Vanilla JS, CSS animations, Phoenix JS client (CDN)
- No build step — just
gleam run