Share files and messages between devices on the same network — with no servers, no accounts and no uploads.
| 🎯 Zero-config discovery | Devices on your network find each other automatically — no room codes, no QR codes, no setup. |
| 🔒 Direct P2P transfers | Files move device-to-device over DTLS-encrypted WebRTC. Nothing ever touches a server. |
| 🧭 Always-on radar | A layered dial with bezel, sweep, ping and blips frames the devices around you. |
| 🪶 Zero install | Pure static HTML/CSS/JS. ES modules, no build step, no bundler. |
| 🌗 Dark / light themes | Synced to system preference, persisted per device, with no flash on reload. |
| 📱 PWA-ready | Installable on iOS, Android and desktop. Works offline after the first visit. |
| 🌍 i18n-ready | Every string lives in a single dictionary; add a language with one object. |
- Automatic device discovery — a lobby pattern on the public PeerJS broker groups devices by their public IP, mirroring Snapdrop's subnet heuristic without any local-network access.
- Full-mesh connections — once discovered, peers connect directly. File traffic never flows through the lobby.
- Drag & drop — drop files anywhere on the page and pick the recipient.
- File picker — click a device node to open a sheet with send-file / send-message / cancel options.
- Consent-based transfers — every file requires an explicit Accept on the receiving side.
- Chunked streaming — 256 KB chunks with read-ahead pipelining and backpressure on the send buffer keep transfers smooth on slow links.
- Live progress — per-device rings on the dial, plus a footer chip with percentage, speed and ETA.
- Text messages — short encrypted messages between any two devices.
- Host migration — if the lobby host leaves, another device re-claims the lobby id automatically.
- Sound effects — runtime-synthesised with the Web Audio API, zero audio assets shipped.
- Desktop notifications — opt-in via the settings panel.
- Reduced-motion support — every decorative animation respects
prefers-reduced-motion.
QuickDrop is fully static, but ES modules require http(s) — so open it through a local dev server rather than file://.
# Node.js
npx serve
# Python
python3 -m http.server 8080
# Deno
deno run --allow-net --allow-read https://deno.land/std/http/file_server.tsThen open http://localhost:8080. The first client to load the page becomes the lobby host; every other device on the same network joins automatically.
Browsers cannot scan the local network, and QuickDrop refuses to ship a backend. Discovery is solved with a lobby pattern on the public PeerJS broker:
┌──────────────────────────────┐
│ Public PeerJS broker │
│ (0.peerjs.com / CDN) │
└──────────────┬───────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│ Registry │◄─────►│ Peer │◄─────►│ Peer │
│ (host) │ │ │ │ │
└───────────┘ └───────────┘ └───────────┘
- The first client claims a well-known id and becomes the lobby registry.
- New clients register with it and receive a roster of peers that share their public IP — i.e. devices behind the same router.
- Peers then open direct WebRTC connections (full mesh). All file data flows device-to-device; the registry only exchanges presence.
- If the registry host leaves, a remaining client automatically re-claims the lobby id and takes over (host migration).
Files are streamed in 256 KB chunks with send-buffer backpressure, so even large transfers stay smooth and memory usage on the sender stays flat.
quickdrop/
├── index.html App shell
├── manifest.webmanifest PWA metadata
├── sw.js Offline shell caching
├── favicon.svg Inline SVG logo
├── LICENSE All Rights Reserved
├── README.md
│
├── css/ Design system
│ ├── tokens.css Variables, colors, radii, easings
│ ├── base.css Reset, typography, scrollbars
│ ├── layout.css Shell, stage, responsive grid
│ ├── components.css Nodes, radar, modals, forms, toasts
│ └── motion.css Animations, reduced-motion support
│
├── js/
│ ├── config.js Chunk sizes, timeouts, lobby id
│ ├── utils.js Tiny event emitter, formatters
│ ├── i18n.js Translation layer (English today, more languages trivial to add)
│ ├── icons.js Inline SVG icon set
│ ├── device.js Friendly names + platform detection
│ ├── theme.js Dark / light theme manager
│ ├── sound.js Web Audio synthesiser
│ ├── notify.js Notifications API wrapper
│ ├── toast.js Bottom-right toast stack
│ ├── modal.js Modal system
│ ├── network.js P2P lobby, mesh, host migration
│ ├── transfer.js Chunked file engine with backpressure
│ ├── ui.js Radar stage rendering
│ └── app.js Entry point, wiring, settings, drag & drop
│
└── docs/ Images for README and OG tags
| Layer | Tech |
|---|---|
| Signalling | PeerJS (public cloud, zero self-hosted backend) |
| Transport | WebRTC DataChannel (DTLS + SCTP) |
| Language | Vanilla ES modules, no build step |
| Styling | Plain CSS with design tokens, no preprocessor |
| Audio | Web Audio API (runtime synthesis, no assets) |
| Icons | Inline SVG (Feather-style) |
| Fonts | Inter + Space Grotesk via Google Fonts |
| Offline | Service worker with stale-while-revalidate |
QuickDrop is fully static — any host that serves files over HTTPS works out of the box.
- Push this folder to a repository.
- Settings → Pages → Source: deploy from the
mainbranch, root directory. - Add an empty
.nojekyllfile so underscored paths are served as-is. - Your site is live at
https://<user>.github.io/<repo>/.
Drag the folder into the Netlify dashboard, or point Netlify at the repo with:
- Build command: (leave empty)
- Publish directory:
.
vercel --prodNo build step is required.
Connect the repo, set the build command to empty and the output directory to ..
QuickDrop is designed around the principle that your files are yours.
- File contents travel exclusively between the two devices involved.
- Device names and the public IP (used only for same-network grouping) pass through whichever peer currently hosts the lobby.
- No analytics, no cookies, no accounts, no telemetry.
- No third-party scripts except the PeerJS signalling library and Google Fonts.
See LICENSE for the full legal terms.
The UI is English today. All strings live in js/i18n.js behind a t() helper, so adding a language is a one-object change:
const dictionaries = {
en: { /* existing strings */ },
tr: { 'status.boot': 'Başlatılıyor…', /* ... */ },
};
setLocale('tr');No UI code changes, no build step. Pull requests adding new languages are welcome.
Contributions, issues and feature requests are welcome. Before opening a PR:
- Fork the repo.
- Create a feature branch (
git checkout -b feat/my-change). - Keep the same style: ES modules, English comments, uncompressed code.
- Open a PR describing what changed and why.
QuickDrop is built with care by Qexorium.
Standing on the shoulders of:
- PeerJS — for the excellent WebRTC wrapper that makes serverless P2P trivial.
- Snapdrop — the original inspiration for zero-setup local sharing.
- Inter & Space Grotesk — for the typefaces.
- Feather Icons — inspiration for the inline SVG icon set.
- The open web — every pixel runs on standards built by thousands of volunteers.
Copyright © 2026 Qexorium. All Rights Reserved.
See LICENSE for the full terms. Redistribution, modification or commercial use requires explicit written permission from the author.
Made with ☕ and ⚡ by Qexorium