Skip to content

Hooks

Silvery provides hooks for input handling, layout queries, terminal access, and interaction features.

Interaction Feature Hooks

These hooks read state from the interactions runtime (features registered in the CapabilityRegistry). They require no provider wrappers — just use the corresponding with* provider in your app's pipe() chain.

useSelection

Reads the current text selection state from the SelectionFeature. Available when withDomEvents() is in the provider chain.

tsx
import { useSelection } from "silvery"

function SelectionIndicator() {
  const selection = useSelection()
  if (!selection?.active) return null
  return <Text>Selected: {selection.text}</Text>
}

Returns TerminalSelectionState | undefinedundefined when no selection feature is registered.

Legacy hooks

The following hooks are superseded by the feature-based architecture but still exist for backwards compatibility: useTerminalSelection, usePointerState, useFind, useFindProvider, useCopyMode, useCopyProvider. New code should use useSelection() and the automatic feature activation via withDomEvents() / withFocus().

useBoxRect

Returns the computed dimensions of the component's content area — width, height, and position.

tsx
import { Box, Text, useBoxRect } from "silvery"

function SizedBox() {
  const { width, height, x, y } = useBoxRect()

  return (
    <Box borderStyle="single">
      <Text>
        Size: {width}x{height}
      </Text>
      <Text>
        Position: ({x}, {y})
      </Text>
    </Box>
  )
}

Note

useLayout is a deprecated alias for useBoxRect. Both work identically, but prefer useBoxRect for new code.

Return Value

PropertyTypeDescription
widthnumberComputed width in characters
heightnumberComputed height in lines
xnumberX position from left edge
ynumberY position from top edge

First Render Behavior

On the first render, dimensions are { width: 0, height: 0, x: 0, y: 0 }. This is because layout hasn't been computed yet. The component will immediately re-render with correct values.

tsx
function Header() {
  const { width } = useBoxRect()

  // Guard against first render if needed
  if (width === 0) return null

  return <Text>{"=".repeat(width)}</Text>
}

In practice, both renders happen before the first paint, so this is usually invisible.

useTerm

Access the Term instance for terminal capabilities and styling.

tsx
import { useTerm } from "silvery"

function ColoredOutput() {
  const term = useTerm()

  return (
    <Box>
      <Text>{term.green("Success!")} Operation completed.</Text>
      <Text>
        Terminal size: {term.columns}x{term.rows}
      </Text>
    </Box>
  )
}

Return Value

Returns the Term instance passed to render(). Provides:

Property/MethodDescription
columnsTerminal width
rowsTerminal height
hasColor()Check if terminal supports colors
Color methodsred(), green(), bold(), etc.

useInput

Handle keyboard input.

tsx
import { useInput } from "silvery"

function App() {
  const [count, setCount] = useState(0)

  useInput((input, key) => {
    if (key.upArrow) setCount((c) => c + 1)
    if (key.downArrow) setCount((c) => c - 1)
    if (input === "q") process.exit()
  })

  return <Text>Count: {count}</Text>
}

Parameters

tsx
useInput(
  (input: string, key: Key) => void,
  options?: { isActive?: boolean }
)

Key Object

PropertyTypeDescription
upArrowbooleanUp arrow pressed
downArrowbooleanDown arrow pressed
leftArrowbooleanLeft arrow pressed
rightArrowbooleanRight arrow pressed
returnbooleanEnter/Return pressed
escapebooleanEscape pressed
ctrlbooleanControl key held
shiftbooleanShift key held
metabooleanMeta/Command key held
tabbooleanTab pressed
backspacebooleanBackspace pressed
deletebooleanDelete pressed

useApp

Access app-level controls.

tsx
import { useApp } from "silvery"

function App() {
  const { exit } = useApp()

  useInput((input) => {
    if (input === "q") exit()
  })

  return <Text>Press q to quit</Text>
}

Return Value

PropertyTypeDescription
exit(error?: Error) => voidExit the app

useStdout

Access stdout stream and dimensions.

tsx
import { useStdout } from "silvery"

function App() {
  const { stdout, write } = useStdout()

  return (
    <Text>
      Terminal: {stdout.columns}x{stdout.rows}
    </Text>
  )
}

Return Value

PropertyTypeDescription
stdoutNodeJS.WriteStreamstdout stream
write(data: string) => voidWrite directly to stdout

useFocusable

Returns focus state for the nearest focusable ancestor. The component must be rendered inside a <Box focusable> with a testID.

tsx
import { useFocusable, Box, Text } from "silvery"

function FocusableItem({ label }: { label: string }) {
  const { focused } = useFocusable()

  return (
    <Box testID={label} focusable borderStyle={focused ? "double" : "single"}>
      <Text>{label}</Text>
    </Box>
  )
}

Return Value

PropertyTypeDescription
focusedbooleanWhether this component currently has focus
focus() => voidProgrammatically focus this component
blur() => voidProgrammatically blur this component
focusOrigin"keyboard" | "mouse" | "programmatic" | nullHow focus was acquired

Focus behavior is configured via Box props: focusable, autoFocus, focusScope, onFocus, onBlur, onKeyDown.

useFocusWithin

Returns whether any descendant of the specified Box (by testID) has focus.

tsx
import { useFocusWithin } from "silvery"

function Sidebar() {
  const hasFocus = useFocusWithin("sidebar")

  return (
    <Box testID="sidebar" borderColor={hasFocus ? "blue" : "gray"}>
      <FocusableItem testID="item1" />
      <FocusableItem testID="item2" />
    </Box>
  )
}

useFocusManager

Access the focus manager for programmatic focus control.

tsx
import { useFocusManager } from "silvery"

function App() {
  const { activeId, focusNext, focusPrev, focus, blur } = useFocusManager()

  return (
    <Box flexDirection="column">
      <Text>Active: {activeId ?? "none"}</Text>
      <FocusableItem label="First" />
      <FocusableItem label="Second" />
      <FocusableItem label="Third" />
    </Box>
  )
}

Return Value

PropertyTypeDescription
activeIdstring | nulltestID of the currently focused node
activeElementSilveryNode | nullThe currently focused node
focusedbooleanWhether any node has focus
focus(id: string) => voidFocus a specific component by testID
focusNext() => voidFocus next focusable component
focusPrev() => voidFocus previous focusable component
blur() => voidClear focus from all components