Keystroke

Physical keycaps that press, hold and spring back in real order, a shortcut overlay for keyboard-first product demos

Installation
$ pnpm dlx shadcn@latest add @remocn/keystroke

Usage

Keystroke is the keyboard counterpart of simulated-cursor. For each shortcut, physical keycaps rise in near the bottom of the frame and press in real order. The modifiers go down first and stay down while the letter taps, then come up after it. Each keytop sinks into its base, so the side wall shortens and the contact shadow tightens. On release it springs back with a small overshoot, and the legend of a key that is down turns the accent color. The caps leave together after an idle hold. When the next shortcut arrives, the previous one slides back and dims before it leaves.

tsx
import { Backdrop } from "@/components/remocn/backdrop";
import { Keystroke } from "@/components/remocn/keystroke";
 
export const ShortcutOverlay = () => (
  <Backdrop
    fill={{ type: "color", value: "#e9e9e5" }}
    padding={0}
    radius={0}
    shadow=""
  >
    <Keystroke />
  </Backdrop>
);

The component paints no background, so it composites over any scene. theme="light" draws off-white caps on a light-gray wall, and theme="dark" draws graphite caps. platform="mac" draws the glyphs ⌘ ⌥ ⇧ ⌃ ↩ ⌫ ⎋ ⇥, and platform="windows" writes Ctrl, Alt, Shift and Enter on wider caps. x and y place the center of the row as fractions of the frame. size is the keycap height in reference pixels at 720p and scales with the composition height, so the caps look the same at 1080p and 4K. In a vertical video, pass a smaller size.

Your own shortcuts

Each step is { at, keys, hold }. at is the frame the shortcut fires: the frame its last key bottoms out. It uses the same Sequence-local frames as the at of a remocn-ui step, so a keystroke and the UI it opens can share one number. Everything before the click is scheduled backward from at.

tsx
import { Backdrop } from "@/components/remocn/backdrop";
import { CommandMenu } from "@/components/remocn/command-menu";
import { Keystroke } from "@/components/remocn/keystroke";
import { useCurrentState } from "@/lib/remocn-ui";
 
const OPEN = 24;
const CLOSE = 96;
 
export const PaletteDemo = () => {
  const state = useCurrentState(
    [
      { at: OPEN, state: "opened" },
      { at: CLOSE, state: "closed" },
    ],
    "closed",
  );
 
  return (
    <Backdrop
      fill={{ type: "color", value: "#f4f4f2" }}
      padding={0}
      radius={0}
      shadow=""
    >
      <CommandMenu state={state} />
      <Keystroke
        steps={[
          { at: OPEN, keys: "mod+k" },
          { at: CLOSE, keys: "esc" },
        ]}
      />
    </Backdrop>
  );
};

keys is a combo joined with + ("mod+k", "shift+mod+p", "enter") or a sequence of combos separated by spaces ("g i" presses G, then I). A sequence fires on its last key, and a subtle "then" joins its chords. mod is ⌘ on mac and Ctrl on windows. The other modifiers are shift, alt/option, ctrl/control, cmd/meta (Win on windows) and fn. They are always drawn and pressed in the platform's standard order. Named keys include enter, esc, tab, space, backspace, delete, the arrows (up, down, left, right), f1 to f24, and plus for a plus key. Any other single character is drawn as its uppercase letter.

Timing

At 30 fps with speed={1} and the default steps, mod+k on 20, g i on 78 and shift+mod+p on 114:

FramesMotion
0–7Empty frame
7–18⌘ and K rise in and settle
14–20⌘ goes down, then K; ⌘K fires on 20
28–41K springs up on 28, ⌘ on 31
48–58G rises in while ⌘K slides back and dims; G fires on 58
61–69⌘K leaves
66–76G springs up
68–78"then" and I rise in, the row re-centers and G dims; G then I fires on 78
86–96I springs up
98–114⇧, ⌘ and P rise in as G then I slides back, then go down 3 frames apart; ⇧⌘P fires on 114
116–124G then I leaves
122–138P, ⌘ and ⇧ spring up in reverse order
158–166⇧⌘P leaves after its linger
166–180Empty hold

Each key takes 3 frames to go down. Keys in a combo start 3 frames apart and come up 3 frames apart, last down first up. The chord stays down for its hold (8 frames by default). The release lands exactly on rest 10 frames later. The caps start rising before at: 10 frames for a single key, 13 for a two-key combo, 16 for three, and 30 for g i. getKeystrokeLeadIn(keys, hold) returns that lead-in, so leave at least that much before the first at in a Sequence. After the last key comes up, a group waits linger frames (30 by default), then leaves over 8 frames. A group pushed back by a newer one leaves sooner, 18 frames after the push. If its keys are still down at the push, the 18 frames count from its last key-up instead, so its release still plays. At most two groups hold the screen: a third arrival sends the oldest off at once.

keystrokeLength is 166, the frame the default content has left. getKeystrokeDuration({ steps, linger, speed, fps }) returns the frames your own steps need, plus a 14-frame tail. at, hold and linger are frames of your composition, exactly like remocn-ui at. The press and release motion keeps the same timing in seconds at any frame rate.

Props10

PropTypeDefault
steps
KeystrokeStep[]three-step flow

Shortcuts in order, each { at, keys, hold? }; the default is ⌘K, G then I, ⇧⌘P

platform
"mac" | "windows""mac"

Resolves mod to ⌘ or Ctrl and draws glyph or word legends

theme
"light" | "dark""light"

Off-white caps on a light-gray wall, or graphite caps

accent
string"#0a84ff"

Legend color of a key while it is down

x
number0.5

Row center as a fraction of the frame width

y
number0.86

Row center as a fraction of the frame height

size
number64

Keycap height and 1u key width in reference px at 720p, clamped to 8–400

linger
number30

Frames the caps stay after the last key comes up, before they leave

speed
number1

Playback multiplier applied to the frame, like remocn-ui; 0 never fires

className
string—

Optional class name on the full-frame root

  • stepsKeystrokeStep[]

    Shortcuts in order, each { at, keys, hold? }; the default is ⌘K, G then I, ⇧⌘P

    Defaultthree-step flow

  • platform"mac" | "windows"

    Resolves mod to ⌘ or Ctrl and draws glyph or word legends

    Default"mac"

  • theme"light" | "dark"

    Off-white caps on a light-gray wall, or graphite caps

    Default"light"

  • accentstring

    Legend color of a key while it is down

    Default"#0a84ff"

  • xnumber

    Row center as a fraction of the frame width

    Default0.5

  • ynumber

    Row center as a fraction of the frame height

    Default0.86

  • sizenumber

    Keycap height and 1u key width in reference px at 720p, clamped to 8–400

    Default64

  • lingernumber

    Frames the caps stay after the last key comes up, before they leave

    Default30

  • speednumber

    Playback multiplier applied to the frame, like remocn-ui; 0 never fires

    Default1

  • classNamestring

    Optional class name on the full-frame root

KeystrokeStep

PropTypeDefault
atrequired
number—

Sequence-local frame the shortcut fires, when its last key bottoms out

keysrequired
string—

A combo such as "shift+mod+p", or chords separated by spaces such as "g i"

hold
number8

Frames the chord stays down after it fires

  • atrequirednumber

    Sequence-local frame the shortcut fires, when its last key bottoms out

  • keysrequiredstring

    A combo such as "shift+mod+p", or chords separated by spaces such as "g i"

  • holdnumber

    Frames the chord stays down after it fires

    Default8