Skip to content

Quick Start ​

Polished terminal apps in React.

Silvery is a React renderer for terminal applications — use it as just a renderer, or add optional packages for a complete framework. The core API (Box, Text, useInput) works like other React terminal renderers. What's different: responsive layout, native scrolling, and per-node incremental rendering.

Installation ​

bash
bun add silvery
bash
npm install silvery
bash
yarn add silvery
bash
pnpm add silvery

One package is all you need. silvery includes everything to build terminal apps — components, hooks, and rendering. The @silvery/* scoped packages exist for advanced users who want finer-grained imports.

Your First App ​

Create a file called app.tsx:

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

function App() {
  return (
    <Box borderStyle="round" padding={1}>
      <Text>Hello from Silvery!</Text>
    </Box>
  )
}

await render(<App />).run()

Run it:

bash
bun app.tsx
# or: npx tsx app.tsx

You should see a rounded box with "Hello from Silvery!" inside.

Power users: render() and run() are sugar over composable plugins. For full control over terminal I/O, focus, events, and commands, use pipe() — see Building an App.

Responsive Layout ​

Components can query their own dimensions during render — no prop drilling needed:

tsx
import { render, Box, Text, useBoxRectDangerously } from "silvery"

function SizedBox() {
  const { width, height } = useBoxRectDangerously()
  return (
    <Box borderStyle="single" flexGrow={1}>
      <Text>
        I am {width}x{height}
      </Text>
    </Box>
  )
}

function App() {
  return (
    <Box flexDirection="row" width="100%">
      <SizedBox />
      <SizedBox />
      <SizedBox />
    </Box>
  )
}

await render(<App />).run()

Each SizedBox will display its actual computed dimensions. No prop threading needed!

Interactive Lists ​

SelectList gives you keyboard-navigable selection out of the box — arrow keys, j/k, Home/End, disabled items, and scroll windowing. No manual useInput() or cursor state needed:

tsx
import { render, Box, Text, SelectList } from "silvery"

function App() {
  return (
    <Box flexDirection="column" padding={1}>
      <Text bold>Pick a framework:</Text>
      <SelectList
        items={[
          { label: "React", value: "react" },
          { label: "Vue", value: "vue" },
          { label: "Svelte", value: "svelte" },
          { label: "Angular (coming soon)", value: "angular", disabled: true },
        ]}
        onSelect={(item) => console.log(`Selected: ${item.value}`)}
        maxVisible={5}
      />
    </Box>
  )
}

await render(<App />).run()

For large datasets, use VirtualList with interactive mode — it renders only visible items and handles keyboard navigation (j/k, Page Up/Down, Home/End):

tsx
import { render, Box, VirtualList, Text } from "silvery"

const items = Array.from({ length: 1000 }, (_, i) => `Item ${i + 1}`)

function App() {
  return (
    <VirtualList
      items={items}
      height={15}
      itemHeight={1}
      interactive
      renderItem={(item, index, meta) => <Text inverse={meta?.isSelected}> {item} </Text>}
      onSelect={(index) => console.log(`Selected: ${items[index]}`)}
    />
  )
}

await render(<App />).run()

Next Steps ​