React Hooks

Keyboard shortcuts, renderer context and terminal dimensions.

Hooks

The React bindings expose hooks analogous to the Solid ones. Use useKeyboard to register a key handler, useRenderer to access the current renderer and useTerminalDimensions to react to resize events.

HookSignatureDescription
useRenderer() => CliRendererReturns the active renderer from context. Throws if used outside the Cascade React root.
useKeyboard(handler: (e: KeyEvent) => void, options?: { release?: boolean }) => voidSubscribes to keyboard events. By default receives press events (including repeats). Use release: true to also receive release events.
useTerminalDimensions() => { width: number; height: number }Returns terminal width/height and re-renders on resize.
useTimeline(options?: TimelineOptions) => TimelineCreates a timeline and registers it with the animation engine; automatically pauses/unregisters on unmount.
import { useKeyboard } from "@cascadetui/react"

function App() {
  useKeyboard((event) => {
    if (event.name === "q") process.exit(0)
  })
  return <text content="Press q to quit" />
}

Combine these hooks with standard React state and effect patterns to build interactive applications.

import { useTerminalDimensions } from "@cascadetui/react"

function App() {
  const dims = useTerminalDimensions()
  return <text content={"Size: " + dims.width + "x" + dims.height} />
}

Recipe: track pressed keys (press + release)

import { useKeyboard } from "@cascadetui/react"
import { useMemo, useState } from "react"

function App() {
  const [pressed, setPressed] = useState<Set<string>>(() => new Set())

  useKeyboard(
    (e) => {
      setPressed((prev) => {
        const next = new Set(prev)
        if (e.eventType === "release") next.delete(e.name)
        else next.add(e.name)
        return next
      })
    },
    { release: true },
  )

  const text = useMemo(() => Array.from(pressed).sort().join(", ") || "(none)", [pressed])
  return <text>{"Pressed: " + text}</text>
}

For animation, use useTimeline to register a timeline with the engine. A simple pattern is to update local state in onUpdate and let React re-render declaratively.

import { useTimeline } from "@cascadetui/react"
import { useEffect, useState } from "react"

function App() {
  const [progress, setProgress] = useState(0)
  const timeline = useTimeline({ duration: 1200, loop: true })

  useEffect(() => {
    timeline.add(
      { t: 0 },
      {
        t: 1,
        duration: 1200,
        ease: "linear",
        onUpdate: (a) => setProgress(a.targets[0].t),
      },
    )
  }, [])

  return (
    <box border padding={1} width={40}>
      <text>{"Loading: " + Math.round(progress * 100) + "%"}</text>
      <box style={{ width: Math.max(1, Math.round(progress * 36)), height: 1, backgroundColor: "#7aa2f7" }} />
    </box>
  )
}