Adaptive typing trainer that practices the keys you actually miss. Free to use at keydown.io.
KeyDown analyzes your mistakes in real time and generates text weighted toward your weak n-grams. Instead of typing random words, you practice the specific letter combinations that slow you down.
Practice text is generated by gentext from frequency-ranked English words, so you type real language instead of random letter soup.
- Adaptive engine — real-time n-gram analysis adjusts text generation on the fly
- Session & weekly stats — WPM, accuracy, error distribution per session and across your week
- Per-key heatmaps — see error density across the entire keyboard, for QWERTY, Colemak, Colemak-DH, Dvorak, and Workman
- Long-term trends — monthly/yearly progress charts, n-gram error trends, speed-vs-accuracy scatter plots
- Shareable sessions — publish a read-only link to any session
- Export & import — download your progress as CSV or JSON
- pnpm + Turborepo monorepo, Node ≥ 24
- apps/app — Next.js 16 (App Router) · React 19 · Tailwind 4 + DaisyUI 5 · Recharts
- apps/api — NestJS 11 · Firebase Admin SDK (Auth, Firestore, Storage) · Resend
- packages/schemas — shared Zod schemas + REST API contract
- infra — Terraform for the Google Cloud project behind keydown.io
The browser never talks to Firebase directly: the frontend calls the API through a same-origin /api/* rewrite, and auth is an httpOnly session cookie issued by the API.
You need Node 24+, pnpm 11 (corepack enable), and a Firebase project of your own with Authentication, Firestore, and Storage enabled.
git clone https://github.com/keydownio/keydown.git
cd keydown
pnpm install
cp apps/api/.env.example apps/api/.env.local
# fill in apps/api/.env.local — see the comments in the file
pnpm devOpen http://localhost:3000. The API listens on :8080 and is proxied under /api/*.
What the API needs at minimum:
SERVICE_ACCOUNT_JSON— a service account key for your Firebase projectFIREBASE_WEB_API_KEY— the project's Web API key (used for Identity Toolkit REST calls)OAUTH_STATE_SECRET— any random string (openssl rand -base64 48)
Google/GitHub sign-in (*_OAUTH_CLIENT_*) and e-mail (RESEND_API_KEY) are optional for local development; e-mail/password sign-up works without them.
Useful commands:
pnpm build # build everything
pnpm typecheck # type-check all packages
pnpm lint # lint all packages
pnpm test # run the API test suite (vitest)
pnpm --filter @keydown/<name> <script> # run a script in one packageapps/
app/ — Next.js frontend (zero client Firebase)
src/
api/ — apiFetch/serverApiFetch + react-query hooks per endpoint
app/ — App Router (server components by default)
components/ — reusable UI components
containers/ — feature-level composite components
contexts/ — React contexts (auth = GET /auth/me)
hooks/ — custom hooks
constants/ — shared constants
utils/ — helpers
api/ — NestJS backend (firebase-admin, Resend)
src/
auth/ — session cookie, password + server-side OAuth flows
sessions/ — typing sessions + stats transaction
... — stats, shared-sessions, data, avatar, feedback, support
packages/
schemas/ — @keydown/schemas: Zod schemas + API contract
*-config/ — shared eslint/prettier/typescript configs
infra/ — Terraform for the production deployment (see infra/README.md)
Code conventions live in AGENTS.md, apps/app/AGENTS.md, and apps/api/AGENTS.md.
Contributions are welcome — see CONTRIBUTING.md. Please read the Code of Conduct first, and report security issues privately as described in SECURITY.md.
KeyDown is licensed under the GNU Affero General Public License v3.0. If you run a modified version as a network service, the AGPL requires you to offer its source code to your users.
The KeyDown name and logo are trademarks of Eternity Cat, LLC and are not covered by the license. If you deploy your own instance, please use a different name and logo.