36 fast, private utilities in one app.
A static site that deploys anywhere, and a native desktop app from the same codebase.
Most single-purpose online tools are the same shape: two inputs, one number out, and several megabytes of ads around them. AppBox is the opposite trade — one app, no ads, no analytics, no account, and every calculation runs on your device.
That constraint is what makes the offline story real rather than marketing. 34 of the 36 tools never touch the network, because there is nothing they need a server for. The two that do — weather and currency — use free, key-less APIs and cache their last result, so they still show you something useful on a train.
Every tool picks up its category's colour from a single attribute, so the app stays varied without becoming noisy. Light and dark are both first-class.
Press ⌘K (or Ctrl+K) anywhere to jump between tools:
It works properly on a phone too:
![]() |
![]() |
| Category | Tools |
|---|---|
| Calculators | Calculator (basic + scientific), Percentage, Tip & bill split |
| Converters | Unit converter (90+ units), Currency, Number base |
| Time | Clock/timer/stopwatch, World clock, Pomodoro, Date calculator, Age calculator |
| Developer | JSON formatter, Base64, JWT decoder, URL encoder, Hash generator, UUID generator, Regex tester, Diff checker, CSV ⇄ JSON, Cron parser |
| Text | Text tools & word counter, Markdown preview, Lorem ipsum |
| Productivity | Todo list, Notes, Password generator, QR codes |
| Design & media | Colour picker & contrast checker, Image compressor |
| Finance | Loan/EMI calculator, Compound interest |
| Health | BMI, Calories & macros, Water intake |
| Weather | 7-day forecast |
One UI codebase serves two targets:
src/ Next.js 16 App Router — the renderer
├─ app/
│ ├─ layout.tsx metadata, fonts, theme, service worker
│ ├─ page.tsx home (server component — no JS needed to read it)
│ ├─ <slug>/page.tsx one route per tool, 8 lines each
│ ├─ sitemap.ts generated from the tool registry
│ └─ globals.css Tailwind v4 tokens, light + dark
├─ components/
│ ├─ tools/ one component per tool
│ ├─ ui/ buttons, fields, tables, modals, toasts
│ ├─ charts/ hand-rolled inline SVG charts
│ └─ layout/ shell, sidebar, title bar, command palette
├─ lib/ domain logic, framework-free and unit-testable
│ ├─ tools.ts THE REGISTRY — single source of truth
│ ├─ expression.ts arithmetic evaluator (replaces mathjs)
│ ├─ finance.ts units.ts colors.ts cron.ts diff.ts csv.ts hash.ts …
└─ types/
└─ appbox-bridge.ts the IPC contract, shared by preload and renderer
electron/ compiled by Vite → dist-electron/
├─ main.ts window, app:// protocol, IPC, hardening
├─ preload.ts the only bridge into the renderer
└─ menu.ts native menu, generated from the registry
out/ next build → Vercel AND → Electron over app://
src/lib/tools.ts holds every tool's slug, name, description, category, keywords and
icon. The sidebar, home grid, command palette, native desktop menu, sitemap.xml, the
PWA shortcuts and each route's title, meta description and JSON-LD all read from it.
Adding a tool means one registry entry plus one 8-line route file — nothing else to
remember to update.
They do different jobs. Next.js output: 'export' produces real HTML per route, which
is what gives each tool its own title, description, canonical URL and structured data —
the thing the previous version, a single client-rendered page, could not have. Vite
compiles the Electron main and preload processes from TypeScript; it never touches the UI.
The renderer is sandboxed with contextIsolation: true, nodeIntegration: false and
sandbox: true. It reaches the host only through the narrow, typed surface in
electron/preload.ts. The packaged app is served from a registered app:// scheme rather
than file://, which gives it a stable origin so localStorage, history.pushState and
fetch behave exactly as they do on the web.
git clone https://github.com/shehari007/mini-react-electron-desktop-app.git
cd mini-react-electron-desktop-app
npm installnpm run dev # web, at http://localhost:3000
npm run electron:dev # desktop, with hot reload
npm run electron:preview # desktop, against a production buildnpm run typecheck # tsc --noEmit, strict
npm run lint # eslint
npm run build # static export into out/npm run package:win # NSIS installer (x64 + arm64)
npm run package:mac # DMG (x64 + arm64)
npm run package:linux # AppImage + .debInstallers land in release/.
Push it and Vercel handles the rest. Its Next.js preset understands output: 'export'
on its own, so vercel.json only adds what the framework does not: cache headers and a
Content-Security-Policy matching the desktop one.
Do not set outputDirectory to out there. It reads as "look for the Next build in
out/", and the deploy fails with a missing routes-manifest.json — that file lives in
.next/, which is exactly where the preset already looks.
Any static host works too: npm run build, then serve out/.
npx serve outSet NEXT_PUBLIC_SITE_URL if you deploy to a different domain, so canonical URLs and the
sitemap point at the right place.
| Web | Desktop | |
|---|---|---|
| 34 offline tools | Service worker caches all 36 routes after one visit | Files are on disk |
| Weather, Currency | Cached result, with its age shown | Same |
| Saved data | localStorage, on your device |
Same |
| Fonts | Self-hosted by next/font — no request to Google |
Same |
There is no analytics script, no cookie banner and no third-party embed on any page.
Todos, notes, history, saved cities and preferences are written to your browser's local
storage and never leave the device. The only outbound requests the app ever makes are the
weather and currency lookups, and only when you open those tools. /about has an export
button and a clear-everything button for the data it holds.
- No charting library. The four chart forms the app needs are ~500 lines of inline SVG in
components/charts/. Recharts would have added ~100KB to routes whose selling point is loading instantly. Chart colours are a fixed, colourblind-validated 3-slot palette, deliberately independent of the per-tool accent so a series colour always means the same thing. - No
mathjs.lib/expression.tsis a tokeniser plus shunting-yard evaluator with degree/radian trig, postfix factorial and implicit multiplication — a few hundred lines instead of a 500KB dependency, and it returns errors specific enough to show the user. - MD5 is hand-written. Web Crypto deliberately excludes it, and verifying a download
against a published checksum is exactly the legacy case people still need it for. It is
fuzz-checked against
node:cryptoacross 139 inputs. - Rendered Markdown is sanitised.
markedpasses raw HTML through, so both the Markdown tool and Notes run output through DOMPurify before it reaches the DOM. - Per-tool accent colours. Each category sets one
data-accentattribute and everything inside inherits that palette through CSS custom properties, so tool components never reference a colour directly. This needs@theme inlinein Tailwind v4 — a plain@themefreezes the substitution at:rootand every tool renders the same colour.
Issues and pull requests are welcome. To add a tool:
- Add an entry to
TOOLSinsrc/lib/tools.ts. - Create
src/components/tools/YourTool.tsx. - Create
src/app/your-slug/page.tsx(copy any existing one — they are identical bar two names).
Navigation, search, the desktop menu, the sitemap and metadata all pick it up automatically.
Built and maintained by Muhammad Sheharyar Butt.
- GitHub: @shehari007
- Email: shehariyar@gmail.com
- Support: Buy me a coffee
MIT — see LICENSE.













