Skip to content
caliper ui docs

Components

Key echo

Shows the keys you press as caps that pop up, stack and fade, for screen recordings, tutorials and live demos. Pressing the same combination again counts it instead of adding another.

Free
pnpm dlx shadcn@latest add @caliper/key-echo

Keyboard tour

Click the area, then press keys or shortcuts.

Try ⌘K, Shift+Enter or the arrow keys
Installs components/ui/key-echo.tsxDepends on none beyond ReactMotion none in component source

When to use

Use it for

  • Product demos, onboarding videos and keyboard-shortcut tutorials.
  • Live presentations of a keyboard-driven app.

Skip it when

  • Everyday product use; it is for showing, not doing.
  • Pages with password or payment fields in view.

Decisions

Why it is built this way, so you know what to keep when you change your copy.

Behaviour

Combinations, not letters
Modifiers are shown with the key they modify; Shift shows only when it adds something, since a capital already says it.
Repeats count
Pressing the same combination again bumps a count on the last cap instead of filling the stack.

Usage

Visual only
It is hidden from assistive technology and skips password fields.

Installation

terminal
pnpm dlx shadcn@latest add @caliper/key-echo

Adds its 3 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage

This is the code behind the preview above. It is built from KeyEcho and comboOf.

example.tsx
import { type ReactNode } from "react"
import { KeyEcho } from "@/components/ui/key-echo"

function Panel({ title, context, className = "max-w-md", children }: { title: string; context: string; className?: string; children: ReactNode }) {
  return (
    <section className={`grid w-full gap-5 rounded-overlay border border-border bg-background p-6 shadow-resting max-sm:p-4 ${className}`}>
      <div className="grid gap-1">
        <p className="font-medium">{title}</p>
        <p className="text-sm text-muted-foreground">{context}</p>
      </div>
      {children}
    </section>
  );
}

export function KeyEchoDemo() {
  return (
    <Panel title="Keyboard tour" context="Click the area, then press keys or shortcuts.">
      <div tabIndex={0} className="relative grid h-56 justify-items-center rounded-control border border-dashed border-border-strong pt-5 text-sm text-muted-foreground outline-none focus-visible:border-foreground">
        Try ⌘K, Shift+Enter or the arrow keys
        <KeyEcho scope="parent" className="absolute inset-x-0 bottom-4" />
      </div>
    </Panel>
  );
}

API reference

KeyEcho

component

Also accepts the native props of its root element and a ref.

PropTypeDefaultDescription
scope"page" | "parent""page"Echo keys pressed anywhere on the page, or only inside the parent element.
maxnumber3How many combinations stay on screen at once.
lingernumber1600How long a combination stays, in ms.

comboOf

component

No props of its own; it accepts the native props of its root element.

Accessibility

It is built on native elements and keeps aria-hidden in step with state.

No custom key handling; Tab and the native activation keys do the work.

Motion

It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.

All Surfaces examples