A keyboard where each key sinks and lights as a sentence is typed into the text field above it, shift and space included.
╭─ Keyboard ──────────────────────────────────────────────────────────────────── WPM 62 · 98% ───╮ │ │ │ ╭──────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ Sh │ │ │ │ │ │ │ ╰──────────────────────────────────────────────────────────────────────────────────────────╯ │ │ │ │ │ │ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭─────────╮ │ │ │ ` │ │ 1 │ │ 2 │ │ 3 │ │ 4 │ │ 5 │ │ 6 │ │ 7 │ │ 8 │ │ 9 │ │ 0 │ │ - │ │ = │ │Bksp │ │ │ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰─────────╯ │ │ ╭──────╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭──────╮ │ │ │Tab │ │ Q │ │ W │ │ E │ │ R │ │ T │ │ Y │ │ U │ │ I │ │ O │ │ P │ │ [ │ │ ] │ │ \ │ │ │ ╰──────╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰──────╯ │ │ ╭────────╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───────────╮ │ │ │Caps │ │ A │ │ S │ │ D │ │ F │ │ G │ │ H │ │ J │ │ K │ │ L │ │ ; │ │ ' │ │Enter │ │ │ ╰────────╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───────────╯ │ │ ╭───────────╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭───╮ ╭──────────────╮ │ │ │Shift │ │ Z │ │ X │ │ C │ │ V │ │ B │ │ N │ │ M │ │ , │ │ . │ │ / │ │Shift │ │ │ ╰───────────╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰───╯ ╰──────────────╯ │ │ ╭──────╮ ╭─────╮ ╭────────────────────────────────────────────╮ ╭─────╮ ╭─────╮ ╭──────╮ │ │ │Ctrl │ │Alt │ │ │ │Alt │ │Fn │ │Ctrl │ │ │ ╰──────╯ ╰─────╯ ╰────────────────────────────────────────────╯ ╰─────╯ ╰─────╯ ╰──────╯ │ │ │ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯
Paste two lines into any page, play it in a terminal, or put a GIF in a README. The code follows the look you pick above.
<script type="module" src="https://www.ascii-magic.com/ascii/ascii.js"></script>
<ascii-art piece="keyboard-keypress"></ascii-art>// copy keyboard-keypress.js and kit.js into your project, then:
import { useEffect, useRef } from "react";
import create from "./keyboard-keypress.js";
export function Piece() {
const ref = useRef(null);
useEffect(() => {
const draw = create(), t0 = performance.now();
let raf;
const tick = () => {
const f = draw((performance.now() - t0) / 1000);
ref.current.textContent = f.text ?? f;
raf = requestAnimationFrame(tick);
};
tick();
return () => cancelAnimationFrame(raf);
}, []);
return <pre ref={ref} style={{ lineHeight: 1, fontFamily: "Menlo, Consolas, monospace" }} />;
}npx --yes https://www.ascii-magic.com/ascii/cli.tgz keyboard-keypress<!-- Download GIF below, commit it, then: -->
<p align="center">
<img src="assets/keyboard-keypress.gif" alt="Keyboard Keypress, animated ASCII art" width="720">
</p>Pause on the frame you like, pick a look, then download it at an exact size.
MIT licensed. It imports one small helper file, kit.js.
/* keyboard-keypress: a full keyboard layout with keys depressing in time as a sentence is typed above it. */
import { Grid, palette } from "../lib/kit.js";
export const meta = {
slug: "keyboard-keypress",
name: "Keyboard Keypress",
category: "screens",
note: "A keyboard where each key sinks and lights as a sentence is typed into the text field above it, shift and space included.",
tags: ["keyboard", "typing", "keys", "input", "hero", "app"],
cols: 98, rows: 25, fps: 24,
palette: palette(["#e6edf3", "#8b949e", "#7ee787", "#79c0ff", "#ffa657", "#ff7b72", "#d2a8ff", "#484f58", "#30363d", "#f2cc60"]),
};
const ROWS = [
[["`", 1], ["1", 1], ["2", 1], ["3", 1], ["4", 1], ["5", 1], ["6", 1], ["7", 1], ["8", 1], ["9", 1], ["0", 1], ["-", 1], ["=", 1], ["Bksp", 2]],
[["Tab", 1.5], ["Q", 1], ["W", 1], ["E", 1], ["R", 1], ["T", 1], ["Y", 1], ["U", 1], ["I", 1], ["O", 1], ["P", 1], ["[", 1], ["]", 1], ["\\", 1.5]],
[["Caps", 1.75], ["A", 1], ["S", 1], ["D", 1], ["F", 1], ["G", 1], ["H", 1], ["J", 1], ["K", 1], ["L", 1], [";", 1], ["'", 1], ["Enter", 2.25]],
[["Shift", 2.25], ["Z", 1], ["X", 1], ["C", 1], ["V", 1], ["B", 1], ["N", 1], ["M", 1], [",", 1], [".", 1], ["/", 1], ["Shift", 2.75]],
[["Ctrl", 1.5], ["Alt", 1.25], ["", 7.75], ["Alt", 1.25], ["Fn", 1.25], ["Ctrl", 1.5]],
];
const LINES = ["Ship it on Friday, 4pm.", "Quick brown foxes jump at 9."];
const KT = 0.17;
export default function create() {
const g = new Grid(meta.cols, meta.rows);
const W = meta.cols, H = meta.rows;
const U = 6; // cells per key unit
const keys = []; // {label, x, y, w}
ROWS.forEach((r, ry) => {
let x = 3;
r.forEach(([l, w]) => { const cw = Math.round(w * U); keys.push({ l, x, y: 8 + ry * 3, w: cw - 1 }); x += cw; });
});
const find = (c) => {
if (c === " ") return keys.find((k) => k.l === "");
if (c === "\n") return keys.find((k) => k.l === "Enter");
return keys.find((k) => k.l === c.toUpperCase());
};
const lShift = keys.find((k) => k.l === "Shift");
const seqs = LINES.map((s) => s + "\n");
const segLen = seqs.map((s) => s.length * KT + 1.6);
const PERIOD = segLen[0] + segLen[1];
return (t) => {
g.clear();
let lt = t % PERIOD, si = 0;
if (lt >= segLen[0]) { lt -= segLen[0]; si = 1; }
const s = seqs[si];
const n = Math.min(s.length, Math.floor(lt / KT));
const curCh = n < s.length && lt - n * KT < 0.12 ? s[n] : null;
const down = new Set();
if (curCh) {
const k = find(curCh); if (k) down.add(k);
if (/[A-Z,]/.test(curCh) && curCh !== ",") down.add(lShift);
}
g.box(0, 0, W, H, "round", "7");
g.text(2, 0, " Keyboard ", "0");
g.text(W - 18, 0, " WPM " + (62 + si * 7) + " · 98% ", "1");
g.box(3, 2, W - 6, 4, "round", "3");
const typed = s.slice(0, n).replace("\n", "");
const prev = si === 1 ? LINES[0] : "";
if (prev) g.text(5, 3, prev, "1");
g.text(5, prev ? 4 : 3, typed, "0");
if (Math.floor(t * 3) % 2 === 0 && n < s.length) g.set(5 + typed.length, prev ? 4 : 3, "▏", "3");
for (const k of keys) {
const on = down.has(k);
const y = k.y + (on ? 0 : 0);
g.box(k.x, y, k.w, 3, on ? "heavy" : "round", on ? "3" : "8");
if (on) g.fill(k.x + 1, y + 1, k.w - 2, 1, "▒", "8");
const lab = k.l;
const lx = k.l.length > 1 ? k.x + 1 : k.x + Math.floor((k.w - lab.length) / 2);
g.text(lx, y + 1, lab, on ? "0" : k.l.length > 1 ? "7" : "1");
}
// home row nubs
["F", "J"].forEach((c) => { const k = keys.find((q) => q.l === c); g.set(k.x + 2, k.y + 2, "─", "1"); });
return g.frame();
};
}
/YOUR OWN
The pieces here are hand-built animations. To turn your own photo, logo or video into animated ASCII, use the ASCII Magic editor.