Signature
An animated SVG signature effect that draws out text as if hand-written.
Live preview recorded from Mellow UI; component and demo published by Mellow UI. Preview: platform recorded · rights authorized.
Full Mac keyboard with tactile depth-button keycaps — lifts on hover, snaps on press, plays synthesised mechanical click sounds.
MSource LandMellow UIWhy it stands out
Full Mac keyboard with tactile depth-button keycaps — lifts on hover, snaps on press, plays synthesised mechanical click sounds.
You are given a task to integrate a React component into your codebase.
Please verify your project has the following setup:
- shadcn/ui project structure
- Tailwind CSS v4.0
- TypeScript
- motion/react v11
If any of these are missing, provide instructions on how to setup project via shadcn CLI, install Tailwind or TypeScript.
Determine the default path for components and styles. Mellow components live in `components/mellow/`. If that folder does not exist yet, create it.
Copy-paste these files:
File location: components/mechanical-keyboard-demo.tsx
File content: "use client";
import React, { useState } from "react";
import { MechanicalKeyboard } from "../mellow/mechanical-keyboard";
export default function MechanicalKeyboardDemo() {
const [sound, setSound] = useState(true);
const [preview, setPreview] = useState(false);
const [rgb, setRgb] = useState(false);
return (
<div className="flex w-full flex-col items-center gap-6 px-3 py-6 sm:px-8">
{/* Controls */}
<div className="flex flex-wrap items-center justify-center gap-3">
{(
[
{ label: "Sound", active: sound, toggle: () => setSound(v => !v) },
{ label: "Preview", active: preview, toggle: () => setPreview(v => !v) },
{ label: "RGB", active: rgb, toggle: () => setRgb(v => !v) },
] as const
).map(({ label, active, toggle }) => (
<button
key={label}
onClick={toggle}
style={{
fontFamily: "var(--font-mono)",
fontSize: "0.6875rem",
fontWeight: 500,
letterSpacing: "0.12em",
textTransform: "uppercase",
padding: "0.35rem 0.875rem",
borderRadius: 6,
border: `1px solid ${
active
? "rgba(var(--ink-rgb),0.3)"
: "rgba(var(--ink-rgb),0.1)"
}`,
background: active
? "rgba(var(--ink-rgb),0.1)"
: "transparent",
color: active ? "var(--ink)" : "rgba(var(--ink-rgb),0.45)",
cursor: "pointer",
transition: "all 150ms ease",
}}
>
{active ? "▸ " : ""}{label}
</button>
))}
</div>
{/* Keyboard */}
<MechanicalKeyboard
enableSound={sound}
showPreview={preview}
variant={rgb ? "rgb" : "default"}
/>
<p
style={{
fontFamily: "var(--font-mono)",
fontSize: "0.6875rem",
color: "rgba(var(--ink-rgb),0.3)",
letterSpacing: "0.08em",
textTransform: "uppercase",
}}
>
hover keyboard · then type
</p>
</div>
);
}
File location: components/mellow/mechanical-keyboard.tsx
File content: "use client";
import React, { useState, useRef, useEffect, useCallback } from "react";
type KeySoundType = "alpha" | "modifier" | "wide" | "fn";
type SoundPhase = "press" | "release";
type SoundRow = [number, number, number, number, number];
const SOUND_PARAMS: Record<KeySoundType, [SoundRow, SoundRow]> = {
alpha: [[0.040, 7, 0.72, 2800, 0.56], [0.026, 11, 0.48, 5500, 0.40]],
modifier: [[0.035, 8, 0.52, 2000, 0.44], [0.024, 12, 0.36, 4000, 0.30]],
wide: [[0.060, 5, 0.85, 1000, 0.65], [0.040, 8, 0.60, 2000, 0.45]],
fn: [[0.032, 8, 0.60, 3500, 0.48], [0.022, 12, 0.42, 6000, 0.32]],
};
function playKeySound(ctx: AudioContext, phase: SoundPhase, type: KeySoundType) {
const [dur, decay, amp, lpHz, gain] = SOUND_PARAMS[type][phase === "press" ? 0 : 1];
const len = Math.floor(ctx.sampleRate * dur);
const buf = ctx.createBuffer(1, len, ctx.sampleRate);
const data = buf.getChannelData(0);
for (let i = 0; i < len; i++) {
data[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / len, decay) * amp;
}
const src = ctx.createBufferSource();
src.buffer = buf;
const lp = ctx.createBiquadFilter();
lp.type = "lowpass";
lp.frequency.value = lpHz;
const g = ctx.createGain();
g.gain.value = gain;
src.connect(lp);
lp.connect(g);
g.connect(ctx.destination);
src.start();
}
interface KeyDef {
code: string;
label: string;
sub?: string;
w?: number;
sound?: KeySoundType;
}
const KB_W = 680;
const KEY_H = 42;
const FN_H = 30;
const GAP = 4;
const ARROW_H = Math.floor((KEY_H - GAP) / 2);
const FN_ROW: KeyDef[] = [
{ code: "Escape", label: "esc", w: 1.5, sound: "modifier" },
{ code: "F1", label: "F1", sound: "fn" },
{ code: "F2", label: "F2", sound: "fn" },
{ code: "F3", label: "F3", sound: "fn" },
{ code: "F4", label: "F4", sound: "fn" },
{ code: "F5", label: "F5", sound: "fn" },
{ code: "F6", label: "F6", sound: "fn" },
{ code: "F7", label: "F7", sound: "fn" },
{ code: "F8", label: "F8", sound: "fn" },
{ code: "F9", label: "F9", sound: "fn" },
{ code: "F10", label: "F10", sound: "fn" },
{ code: "F11", label: "F11", sound: "fn" },
{ code: "F12", label: "F12", sound: "fn" },
];
const MAIN_ROWS: KeyDef[][] = [
[
{ code: "Backquote", label: "`", sub: "~" },
{ code: "Digit1", label: "1", sub: "!" },
{ code: "Digit2", label: "2", sub: "@" },
{ code: "Digit3", label: "3", sub: "#" },
{ code: "Digit4", label: "4", sub: "$" },
{ code: "Digit5", label: "5", sub: "%" },
{ code: "Digit6", label: "6", sub: "^" },
{ code: "Digit7", label: "7", sub: "&" },
{ code: "Digit8", label: "8", sub: "*" },
{ code: "Digit9", label: "9", sub: "(" },
{ code: "Digit0", label: "0", sub: ")" },
{ code: "Minus", label: "-", sub: "_" },
{ code: "Equal", label: "=", sub: "+" },
{ code: "Backspace", label: "delete", w: 2, sound: "wide" },
],
[
{ code: "Tab", label: "tab", w: 1.5, sound: "modifier" },
{ code: "KeyQ", label: "Q" },
{ code: "KeyW", label: "W" },
{ code: "KeyE", label: "E" },
{ code: "KeyR", label: "R" },
{ code: "KeyT", label: "T" },
{ code: "KeyY", label: "Y" },
{ code: "KeyU", label: "U" },
{ code: "KeyI", label: "I" },
{ code: "KeyO", label: "O" },
{ code: "KeyP", label: "P" },
{ code: "BracketLeft", label: "[", sub: "{" },
{ code: "BracketRight", label: "]", sub: "}" },
{ code: "Backslash", label: "\\", sub: "|", w: 1.5 },
],
[
{ code: "CapsLock", label: "caps", w: 1.75, sound: "modifier" },
{ code: "KeyA", label: "A" },
{ code: "KeyS", label: "S" },
{ code: "KeyD", label: "D" },
{ code: "KeyF", label: "F" },
{ code: "KeyG", label: "G" },
{ code: "KeyH", label: "H" },
{ code: "KeyJ", label: "J" },
{ code: "KeyK", label: "K" },
{ code: "KeyL", label: "L" },
{ code: "Semicolon", label: ";", sub: ":" },
{ code: "Quote", label: "'", sub: '"' },
{ code: "Enter", label: "return", w: 2.25, sound: "wide" },
],
[
{ code: "ShiftLeft", label: "shift", w: 2.25, sound: "modifier" },
{ code: "KeyZ", label: "Z" },
{ code: "KeyX", label: "X" },
{ code: "KeyC", label: "C" },
{ code: "KeyV", label: "V" },
{ code: "KeyB", label: "B" },
{ code: "KeyN", label: "N" },
{ code: "KeyM", label: "M" },
{ code: "Comma", label: ",", sub: "<" },
{ code: "Period", label: ".", sub: ">" },
{ code: "Slash", label: "/", sub: "?" },
{ code: "ShiftRight", label: "shift", w: 2.75, sound: "modifier" },
],
];
const BOTTOM_ROW: KeyDef[] = [
{ code: "Fn", label: "fn", w: 1.25, sound: "modifier" },
{ code: "ControlLeft", label: "ctrl", w: 1.25, sound: "modifier" },
{ code: "AltLeft", label: "opt", w: 1.25, sound: "modifier" },
{ code: "MetaLeft", label: "cmd", w: 1.5, sound: "modifier" },
{ code: "Space", label: "", w: 6.25, sound: "wide" },
{ code: "MetaRight", label: "cmd", w: 1.5, sound: "modifier" },
{ code: "AltRight", label: "opt", w: 1.25, sound: "modifier" },
];
const ARROW_UP_DEF: KeyDef = { code: "ArrowUp", label: "↑", sound: "modifier" };
const ARROW_ROW_DEFS: KeyDef[] = [
{ code: "ArrowLeft", label: "←", sound: "modifier" },
{ code: "ArrowDown", label: "↓", sound: "modifier" },
{ code: "ArrowRight", label: "→", sound: "modifier" },
];
const ALL_DEFS: KeyDef[] = [
...FN_ROW,
...MAIN_ROWS.flat(),
...BOTTOM_ROW,
ARROW_UP_DEF,
...ARROW_ROW_DEFS,
];
const ALL_CODES = new Set(ALL_DEFS.map(d => d.code));
// Per-key backlight hue for the rgb variant — a rainbow swept left→right
// across each row, drifting +12° per row for a diagonal wave.
const KEY_HUE: Record<string, number> = (() => {
const hues: Record<string, number> = {};
const assign = (row: KeyDef[], rowIdx: number, extraUnits = 0) => {
const total = row.reduce((s, d) => s + (d.w ?? 1), 0) + extraUnits;
let acc = 0;
for (const d of row) {
const center = acc + (d.w ?? 1) / 2;
acc += d.w ?? 1;
hues[d.code] = ((center / total) * 300 + rowIdx * 12) % 360;
}
return { acc, total };
};
assign(FN_ROW, 0);
MAIN_ROWS.forEach((r, i) => assign(r, i + 1));
// Bottom row shares its flex track with the arrow cluster (flex 3).
const { acc, total } = assign(BOTTOM_ROW, 5, 3);
hues[ARROW_UP_DEF.code] = (((acc + 1.5) / total) * 300 + 5 * 12) % 360;
ARROW_ROW_DEFS.forEach((d, i) => {
hues[d.code] = (((acc + i + 0.5) / total) * 300 + 5 * 12) % 360;
});
return hues;
})();
// Theme detection for the rgb variant — bright ink means dark theme. Reads
// the design-token var (CSS vars can't be used inside the per-hue oklch
// palettes below) and re-reads when the theme class flips on <html>.
function useThemeIsDark(): boolean {
const [dark, setDark] = useState(true);
useEffect(() => {
const read = () => {
const raw = getComputedStyle(document.documentElement)
.getPropertyValue("--ink-rgb");
const [r, g, b] = raw.split(",").map(n => parseFloat(n));
if (Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b)) {
setDark((r + g + b) / 3 > 127);
}
};
read();
const mo = new MutationObserver(read);
mo.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class", "data-theme"],
});
return () => mo.disconnect();
}, []);
return dark;
}
interface SingleKeyProps {
def: KeyDef;
active: boolean;
depth: number;
rgb: boolean;
dark: boolean;
height: number;
onPress: () => void;
onRelease: () => void;
glowRef: (el: HTMLSpanElement | null) => void;
}
function SingleKey({
def,
active,
depth,
rgb,
dark,
height,
onPress,
onRelease,
glowRef,
}: SingleKeyProps) {
const [hovered, setHovered] = useState(false);
const [mouseDown, setMouseDown] = useState(false);
const pressed = active || mouseDown;
const isModLike = def.sound === "modifier" || def.sound === "fn";
const hasSub = Boolean(def.sub);
const isWide = (def.w ?? 1) > 1.2;
const labelSize = isModLike ? "8px" : hasSub ? "10px" : "11px";
const hue = KEY_HUE[def.code] ?? 250;
return (
<div
style={{
flex: `${def.w ?? 1} 1 0`,
minWidth: 0,
position: "relative",
height,
cursor: "pointer",
userSelect: "none",
}}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => { setHovered(false); setMouseDown(false); }}
onMouseDown={e => { e.preventDefault(); setMouseDown(true); onPress(); }}
onMouseUp={() => { setMouseDown(false); onRelease(); }}
>
<span
aria-hidden
style={{
position: "absolute",
inset: 0,
borderRadius: 5,
background: rgb
? dark
? `oklch(0.34 0.17 ${hue})`
: `oklch(0.78 0.13 ${hue})`
: "rgba(var(--ink-rgb),0.03)",
border: `1px solid ${
rgb
? dark
? `oklch(0.5 0.2 ${hue} / 0.5)`
: `oklch(0.65 0.18 ${hue} / 0.6)`
: "rgba(var(--ink-rgb),0.06)"
}`,
boxShadow: rgb
? dark
? `0 ${depth + 5}px 14px -2px oklch(0.62 0.25 ${hue} / 0.55)`
: `0 ${depth + 5}px 14px -2px oklch(0.55 0.22 ${hue} / 0.5)`
: undefined,
transform: `translateY(${depth}px)`,
}}
/>
<span
style={{
position: "absolute",
inset: 0,
borderRadius: 5,
display: "flex",
flexDirection: "column",
alignItems: hasSub || isWide ? "flex-start" : "center",
justifyContent: hasSub ? "flex-end" : "center",
padding: hasSub
? "2px 5px 3px"
: isWide
? "0 7px"
: undefined,
overflow: "hidden",
fontFamily: "var(--font-mono)",
fontSize: labelSize,
fontWeight: 500,
letterSpacing: "0.01em",
color: rgb
? dark
? `oklch(0.88 0.1 ${hue})`
: `oklch(0.45 0.18 ${hue})`
: "var(--ink)",
whiteSpace: "nowrap",
background: rgb
? dark
? "oklch(0.23 0.015 250)"
: "oklch(0.97 0.005 250)"
: "rgba(var(--ink-rgb),0.07)",
border: `1px solid ${
rgb
? dark
? `oklch(0.45 0.12 ${hue} / ${pressed ? 0.9 : 0.55})`
: `oklch(0.6 0.16 ${hue} / ${pressed ? 0.9 : 0.5})`
: pressed
? "rgba(var(--ink-rgb),0.18)"
: "rgba(var(--ink-rgb),0.12)"
}`,
transform: pressed
? `translateY(${depth}px)`
: hovered
? "translateY(-1px)"
: "translateY(0px)",
transition: pressed
? "transform 55ms ease, box-shadow 55ms ease"
: "transform 300ms cubic-bezier(0.34,1.4,0.64,1), box-shadow 200ms ease",
boxShadow: pressed
? "none"
: "inset 0 1px 0 rgba(255,255,255,0.08), inset 0 -1px 0 rgba(0,0,0,0.12)",
}}
>
{rgb && (
<span
aria-hidden
ref={glowRef}
style={{
position: "absolute",
inset: 0,
// screen brightens dark caps; multiply tints white caps —
// either way the ripple reads as the key's own hue lighting up.
background: dark
? `oklch(0.72 0.23 ${hue})`
: `oklch(0.82 0.17 ${hue})`,
mixBlendMode: dark ? "screen" : "multiply",
opacity: 0,
pointerEvents: "none",
}}
/>
)}
{hasSub && (
<span
style={{
position: "relative",
zIndex: 1,
fontSize: "8px",
lineHeight: 1,
color: rgb
? dark
? `oklch(0.75 0.06 ${hue} / 0.7)`
: `oklch(0.5 0.12 ${hue} / 0.75)`
: "rgba(var(--ink-rgb),0.38)",
marginBottom: 1,
}}
>
{def.sub}
</span>
)}
<span style={{ position: "relative", zIndex: 1, lineHeight: 1 }}>{def.label}</span>
</span>
</div>
);
}
export interface MechanicalKeyboardProps {
className?: string;
enableSound?: boolean;
showPreview?: boolean;
variant?: "default" | "rgb";
keyDepth?: number;
onKeyPress?: (code: string) => void;
}
const KB_H_BASE = 12 + FN_H + 5 * GAP + 5 * KEY_H + 16;
// Preview text has no forced line-break — a run typed without ever hitting
// Space is one unbreakable string that CSS can't wrap, so it would otherwise
// overflow the board's width. Cap it well inside the 680px board (mono
// glyphs at 17px run ~10.5px wide) so it always auto-clears before that.
const MAX_PREVIEW_CHARS = 48;
export function MechanicalKeyboard({
className,
enableSound = true,
showPreview = false,
variant = "default",
keyDepth = 3,
onKeyPress,
}: MechanicalKeyboardProps) {
const [active, setActive] = useState<Set<string>>(new Set());
const [word, setWord] = useState("");
const hoveredRef = useRef(false);
const audioCtxRef = useRef<AudioContext | null>(null);
const pendingRef = useRef(new Map<string, KeySoundType>());
const containerRef = useRef<HTMLDivElement>(null);
const outerRef = useRef<HTMLDivElement>(null);
const bodyRef = useRef<HTMLDivElement>(null);
const isRgb = variant === "rgb";
const isDark = useThemeIsDark();
// ── RGB ripple engine ────────────────────────────────────────────
// On press, an expanding color wavefront lights each key's glow overlay
// in its own hue. Driven imperatively (direct style.opacity writes) so
// ~90 keys never re-render per frame.
const glowEls = useRef(new Map<string, HTMLSpanElement>());
const keyCenters = useRef(new Map<string, { x: number; y: number }>());
const ripplesRef = useRef<{ cx: number; cy: number; t0: number }[]>([]);
const rippleRaf = useRef(0);
const rippleFrame = useCallback((now: number) => {
const RIPPLE_MS = 650;
const SPEED = 0.9; // wavefront px/ms (screen px, scales with board)
const SIGMA = 34; // wavefront thickness
ripplesRef.current = ripplesRef.current.filter(r => now - r.t0 < RIPPLE_MS);
glowEls.current.forEach((el, code) => {
const c = keyCenters.current.get(code);
if (!c) return;
let g = 0;
for (const r of ripplesRef.current) {
const t = now - r.t0;
const d = Math.hypot(c.x - r.cx, c.y - r.cy);
const front = t * SPEED;
g += Math.exp(-((d - front) ** 2) / (2 * SIGMA * SIGMA)) * (1 - t / RIPPLE_MS);
}
el.style.opacity = Math.min(1, g).toFixed(3);
});
rippleRaf.current = ripplesRef.current.length
? requestAnimationFrame(rippleFrame)
: 0;
}, []);
const startRipple = useCallback(
(code: string) => {
if (!isRgb) return;
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
// Re-measure at the start of a ripple burst — layout/scale may have
// changed since the last one, and this forces layout only once.
if (ripplesRef.current.length === 0) {
keyCenters.current.clear();
glowEls.current.forEach((el, c) => {
const r = el.getBoundingClientRect();
keyCenters.current.set(c, { x: r.left + r.width / 2, y: r.top + r.height / 2 });
});
}
const center = keyCenters.current.get(code);
if (!center) return;
ripplesRef.current.push({ cx: center.x, cy: center.y, t0: performance.now() });
if (!rippleRaf.current) rippleRaf.current = requestAnimationFrame(rippleFrame);
},
[isRgb, rippleFrame],
);
useEffect(() => () => cancelAnimationFrame(rippleRaf.current), []);
const [scale, setScale] = useState(1);
const [bodyH, setBodyH] = useState(KB_H_BASE);
useEffect(() => {
const outer = outerRef.current;
const body = bodyRef.current;
if (!outer || !body) return;
const ro = new ResizeObserver(() => {
const availW = outer.getBoundingClientRect().width;
const bH = body.offsetHeight;
if (availW > 0) setScale(Math.min(1, availW / KB_W));
if (bH > 0) setBodyH(bH);
});
ro.observe(outer);
ro.observe(body);
return () => ro.disconnect();
}, []);
const getCtx = useCallback((): AudioContext | null => {
if (!enableSound) return null;
try {
const AC =
window.AudioContext ||
(window as unknown as { webkitAudioContext: typeof AudioContext })
.webkitAudioContext;
if (!audioCtxRef.current || audioCtxRef.current.state === "closed") {
audioCtxRef.current = new AC();
}
return audioCtxRef.current;
} catch {
return null;
}
}, [enableSound]);
const sound = useCallback(
(phase: SoundPhase, type: KeySoundType = "alpha") => {
const ctx = getCtx();
if (!ctx) return;
const go = () => playKeySound(ctx, phase, type);
ctx.state === "suspended" ? ctx.resume().then(go) : go();
},
[getCtx],
);
const handlePreviewKey = useCallback(
(def: KeyDef, physicalKey?: string) => {
if (!showPreview) return;
if (def.code === "Space") { setWord(""); return; }
if (def.code === "Backspace") { setWord(w => w.slice(0, -1)); return; }
if (def.sound === "modifier" || def.sound === "fn") return;
if (def.code === "Enter") return;
let ch: string;
if (physicalKey && physicalKey.length === 1) {
ch = physicalKey;
} else if (def.label.length === 1) {
ch = def.label.toLowerCase();
} else {
return;
}
setWord(w => (w.length >= MAX_PREVIEW_CHARS ? ch : w + ch));
},
[showPreview],
);
const pressKey = useCallback(
(def: KeyDef, physicalKey?: string) => {
sound("press", def.sound ?? "alpha");
pendingRef.current.set(def.code, def.sound ?? "alpha");
startRipple(def.code);
handlePreviewKey(def, physicalKey);
onKeyPress?.(def.code);
},
[sound, startRipple, handlePreviewKey, onKeyPress],
);
const releaseKey = useCallback(
(def: KeyDef) => {
const type = pendingRef.current.get(def.code);
if (type !== undefined) {
sound("release", type);
pendingRef.current.delete(def.code);
}
},
[sound],
);
useEffect(() => {
const down = (e: KeyboardEvent) => {
if (!hoveredRef.current || e.repeat || !ALL_CODES.has(e.code)) return;
// Space/arrows/Tab scroll or shift focus by default — this keyboard
// owns the keystroke while hovered, so stop the browser from also
// acting on it (e.g. Space scrolling the docs page to the bottom).
if (!e.metaKey && !e.ctrlKey && !e.altKey) e.preventDefault();
setActive(prev => new Set([...prev, e.code]));
const def = ALL_DEFS.find(d => d.code === e.code);
if (def) pressKey(def, e.key);
};
const up = (e: KeyboardEvent) => {
if (!ALL_CODES.has(e.code)) return;
setActive(prev => {
const next = new Set(prev);
next.delete(e.code);
return next;
});
const def = ALL_DEFS.find(d => d.code === e.code);
if (def) releaseKey(def);
};
document.addEventListener("keydown", down);
document.addEventListener("keyup", up);
return () => {
document.removeEventListener("keydown", down);
document.removeEventListener("keyup", up);
};
}, [pressKey, releaseKey]);
const rowProps = (def: KeyDef) => ({
def,
active: active.has(def.code),
depth: keyDepth,
rgb: isRgb,
dark: isDark,
onPress: () => pressKey(def),
onRelease: () => releaseKey(def),
glowRef: (el: HTMLSpanElement | null) => {
if (el) glowEls.current.set(def.code, el);
else glowEls.current.delete(def.code);
},
});
return (
<div
ref={outerRef}
className={className}
style={{
width: "100%",
position: "relative",
overflow: "hidden",
height: bodyH * scale,
}}
onMouseEnter={() => { hoveredRef.current = true; }}
onMouseLeave={() => { hoveredRef.current = false; }}
>
<div
ref={containerRef}
style={{
position: "absolute",
top: 0,
left: "50%",
marginLeft: -(KB_W / 2),
transformOrigin: "top center",
transform: `scale(${scale})`,
width: KB_W,
}}
>
<div
ref={bodyRef}
style={{
display: "inline-flex",
flexDirection: "column",
gap: GAP,
padding: "12px 14px 16px",
borderRadius: 14,
background: isRgb
? isDark
? "oklch(0.16 0.005 250)"
: "oklch(0.92 0.005 250)"
: "rgba(var(--ink-rgb),0.04)",
border: `1px solid ${
isRgb
? isDark
? "oklch(0.32 0.03 250 / 0.7)"
: "oklch(0.78 0.02 250)"
: "rgba(var(--ink-rgb),0.08)"
}`,
width: KB_W,
}}
>
{showPreview && (
<div
style={{
height: 36,
marginBottom: 4,
display: "flex",
alignItems: "center",
justifyContent: "center",
overflow: "hidden",
}}
>
<span
style={{
fontFamily: "var(--font-mono)",
fontSize: "17px",
fontWeight: 500,
color: isRgb
? isDark
? "oklch(0.9 0.02 250)"
: "oklch(0.25 0.02 250)"
: "var(--ink)",
letterSpacing: "0.08em",
whiteSpace: "nowrap",
opacity: word ? 1 : 0,
transition: "opacity 120ms ease",
pointerEvents: "none",
userSelect: "none",
}}
>
{word || " "}
</span>
</div>
)}
<div style={{ display: "flex", gap: GAP, width: "100%" }}>
{FN_ROW.map(def => (
<SingleKey key={def.code} {...rowProps(def)} height={FN_H} />
))}
</div>
{MAIN_ROWS.map((row, ri) => (
<div key={ri} style={{ display: "flex", gap: GAP, width: "100%" }}>
{row.map(def => (
<SingleKey key={def.code} {...rowProps(def)} height={KEY_H} />
))}
</div>
))}
<div style={{ display: "flex", gap: GAP, width: "100%" }}>
{BOTTOM_ROW.map(def => (
<SingleKey key={def.code} {...rowProps(def)} height={KEY_H} />
))}
<div
style={{
flex: "3 1 0",
minWidth: 0,
display: "flex",
flexDirection: "column",
gap: GAP,
}}
>
<div style={{ display: "flex", gap: GAP }}>
<div style={{ flex: "1 1 0" }} />
<SingleKey {...rowProps(ARROW_UP_DEF)} height={ARROW_H} />
<div style={{ flex: "1 1 0" }} />
</div>
<div style={{ display: "flex", gap: GAP }}>
{ARROW_ROW_DEFS.map(def => (
<SingleKey key={def.code} {...rowProps(def)} height={ARROW_H} />
))}
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default MechanicalKeyboard;
Usage notes:
Add a MechanicalKeyboard component from the mellow library. It renders a full Mac keyboard where every key has a raised 3D face that lifts on hover and snaps down on press, with synthesised mechanical click sounds. Props: enableSound (bool, default true), showPreview (bool — floating keystroke labels above the board), variant ('default'|'rgb' — rgb is a backlit board with per-key rainbow underglow where each press fires a color ripple across neighbouring keys; theme-adaptive, dark caps in dark mode and white caps in light mode), keyDepth (px, default 3), onKeyPress (code: string) => void. Wrap in overflow-x:auto for mobile.Discovery vocabulary
Related by governed terms
An animated SVG signature effect that draws out text as if hand-written.
More from Mellow UI