Keyboard Shortcuts Help

Displays available keyboard shortcuts to the user. Install Keyboard Shortcuts Help from the VLLNT UI registry with the shadcn CLI.

Report a bug

Displays available keyboard shortcuts to the user. Part of the learning family in VLLNT UI, it ships as a machine-readable registry entry — copy the source directly into your app with the shadcn CLI and own it, no runtime dependency on a component library.

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Installation

Add Keyboard Shortcuts Help to your project with the shadcn CLI. The source lands in your codebase, ready to adapt:

pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/keyboard-shortcuts-help.json

Source

"use client"; import { memo, useEffect, useRef } from "react"; import type { ReactNode } from "react"; import type { HeadingTag } from "../../lib/types"; import { useBodyScrollLock } from "../../lib/use-body-scroll-lock"; import { useEscapeKey } from "../../lib/use-escape-key"; import { cn } from "../../lib/utils"; export type KeyboardShortcut = { description: string; keys: readonly string[]; }; export type KeyboardShortcutsHelpProps = { /** Heading tag for the modal title. Defaults to `h2`. */ as?: HeadingTag; className?: string; closeIcon?: ReactNode; footer?: ReactNode; isOpen: boolean; onClose: () => void; shortcuts: readonly KeyboardShortcut[]; title?: string; }; // eslint-disable-next-line max-lines-per-function -- Modal with keyboard handling and focus trap function KeyboardShortcutsHelpImpl({ as: Heading = "h2", className, closeIcon, footer, isOpen, onClose, shortcuts, title = "Keyboard Shortcuts", }: KeyboardShortcutsHelpProps): React.ReactNode { const closeButtonRef = useRef<HTMLButtonElement>(null); // Move focus to the close button when the modal opens. useEffect(() => { if (isOpen) { closeButtonRef.current?.focus(); } }, [isOpen]); useEscapeKey(onClose, { enabled: isOpen, preventDefault: true }); useBodyScrollLock(isOpen); if (!isOpen) return null; return ( <div aria-labelledby="shortcuts-title" aria-modal="true" className="fixed inset-0 z-50 flex items-center justify-center" role="dialog" > {/* Backdrop */} <div aria-hidden="true" className="absolute inset-0 bg-black/50 backdrop-blur-sm" onClick={onClose} /> {/* Modal */} <div className={cn( "relative z-10 w-full max-w-sm rounded-lg bg-background p-6 shadow-xl mx-4", className, )} > {/* Header */} <div className="flex items-center justify-between mb-4"> <Heading className="text-lg font-semibold" id="shortcuts-title"> {title} </Heading> <button aria-label="Close shortcuts help" className="flex size-8 items-center justify-center rounded-md hover:bg-muted" onClick={onClose} ref={closeButtonRef} type="button" > {closeIcon ?? ( <svg className="size-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" > <path d="M6 18L18 6M6 6l12 12" strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} /> </svg> )} </button> </div> {/* Shortcuts List */} <div className="space-y-3"> {shortcuts.map((shortcut) => ( <div className="flex items-center justify-between text-sm" key={shortcut.description} > <span className="text-muted-foreground"> {shortcut.description} </span> <div className="flex gap-1"> {shortcut.keys.map((key) => ( <kbd className="inline-flex h-6 min-w-[24px] items-center justify-center rounded border border-border bg-muted px-1.5 font-mono text-xs" key={key} > {key} </kbd> ))} </div> </div> ))} </div> {/* Footer */} {footer ? ( <div className="mt-4 text-xs text-muted-foreground text-center"> {footer} </div> ) : null} </div> </div> ); } export const KeyboardShortcutsHelp = memo(KeyboardShortcutsHelpImpl); KeyboardShortcutsHelp.displayName = "KeyboardShortcutsHelp";

Stories

Explore every variant and state in the interactive Storybook:

Preview

Switch between light and dark to inspect the embedded Storybook preview.

Dependencies

  • @vllnt/ui@^0.3.0