HextaUI

useHeldKeys

Las teclas que alguien mantiene pulsadas ahora mismo, compartidas por todos los suscriptores mediante un único conjunto de listeners en window.

Hold down any keys
[]
"use client"

import { Kbd, KbdGroup } from "@/components/ui/kbd"
import { useHeldKeys } from "@/hooks/use-held-keys"

export function UseHeldKeysDemo() {
  const held = useHeldKeys(true)

  return (
    <div className="flex flex-col items-center gap-3">
      <div className="flex h-8 items-center">
        {held.size > 0 ? (
          <KbdGroup>
            {[...held].map((key) => (
              <Kbd key={key} keys={key} size="lg" />
            ))}
          </KbdGroup>
        ) : (
          <span className="text-sm text-muted-foreground">
            Hold down any keys
          </span>
        )}
      </div>
      <code className="font-mono text-xs text-muted-foreground">
        {JSON.stringify([...held])}
      </code>
    </div>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/use-held-keys.json

Añade el hook y todo lo que necesita a tu proyecto.

import { useHeldKeys } from "@/hooks/use-held-keys"
const held = useHeldKeys(true)
const showShortcutHints = held.has("meta") || held.has("ctrl")

Úsalo para cualquier cosa que reaccione a teclas mantenidas en lugar de pulsadas: keycaps que se hunden, pistas de atajos que aparecen mientras mantienes ⌘, o un modificador que cambia una herramienta, como Alt para duplicar mientras arrastras.

Todos los componentes que llaman al hook comparten un único store. El primer suscriptor añade listeners pasivos de keydown y keyup a window, y el último en cancelar la suscripción los elimina. Una página con cincuenta keycaps escuchando sigue teniendo un solo par de listeners.

Los listeners solo leen eventos. Nunca llaman a preventDefault, y están en window, después de los manejadores propios de React, así que escribir en un campo nunca se retrasa ni cambia.

"meta" "ctrl" "alt" "shift"     modifiers, left and right alike
"a" … "z"  "0" … "9"            letters and digits, by physical key
"space" "enter" "escape" "tab"   named keys, lowercased
"arrowup" "arrowdown" "f1" …
  • Las letras y los dígitos vienen de event.code, la tecla física, así que mantener Option+K en un Mac sigue informando k y no ˚.
  • La repetición de teclas se ignora, y nada se vuelve a renderizar mientras se mantiene una tecla.
  • macOS no envía keyup de otras teclas mientras ⌘ está pulsada. Cuando se suelta ⌘, el store conserva solo los modificadores que siguen pulsados, así que las letras no pueden quedarse atascadas.
  • Todo se libera cuando la ventana pierde el foco o la pestaña se oculta. Un atajo que cambia de aplicación no deja nada retenido.

Keycaps que se hunden

<Kbd listen> está construido sobre este hook. Cada keycap se hunde mientras su tecla está pulsada.

"use client"

import { Kbd, KbdGroup } from "@/components/ui/kbd"
import { useHeldKeys } from "@/hooks/use-held-keys"
import { useIsApple } from "@/lib/hotkey"

export function UseHeldKeysShortcut() {
  const held = useHeldKeys(true)
  const apple = useIsApple()
  const modifier = apple ? "meta" : "ctrl"
  const ready = held.has(modifier) && held.has("shift")

  return (
    <div className="flex flex-col items-center gap-3 text-sm">
      <KbdGroup>
        <Kbd keys="mod" listen />
        <Kbd keys="shift" listen />
        <Kbd keys="p" listen />
      </KbdGroup>
      <p className="text-muted-foreground">
        {ready ? "Now press P" : "Hold the modifiers to see the hint"}
      </p>
    </div>
  )
}
  • Pasa false para dejar de escuchar. El hook devuelve entonces un conjunto vacío y no añade listeners, así que es barato llamarlo sin condiciones.
  • El conjunto solo se reemplaza cuando una tecla se pulsa o se suelta, así que su identidad sirve como dependencia de memo o de efecto.
  • Para atajos que disparan una acción, usa matchesHotkey de Hotkey en un manejador de keydown. Mantener teclas es para mostrar estado, no para ejecutar comandos.
  • En el servidor, y antes de la hidratación, el conjunto está vacío.
PropTipoPredeterminado
enabledSi se debe escuchar. Cuando es false, no se adjunta nada.
boolean–
DevuelveDescripción
ReadonlySet<string>Los nombres de las teclas que están pulsadas ahora mismo.

Kbd y KbdGroup mediante su prop listen.