--- url: /getting-started/quick-start.md --- # 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 ::: code-group ```bash [bun] bun add silvery ``` ```bash [npm] npm install silvery ``` ```bash [yarn] yarn add silvery ``` ```bash [pnpm] 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 ( Hello from Silvery! ) } await render().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](/guides/terminal-apps). ## 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 ( I am {width}x{height} ) } function App() { return ( ) } await render().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 ( Pick a framework: console.log(`Selected: ${item.value}`)} maxVisible={5} /> ) } await render().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 ( {item} } onSelect={(index) => console.log(`Selected: ${items[index]}`)} /> ) } await render().run() ``` ## Next Steps * [Components](/guides/components) -- Learn about Box, Text, and other components * [Terminal Apps](/guides/terminal-apps) -- Build a full terminal application step by step * [API Reference](/api/box) -- Complete API documentation --- --- url: /getting-started/migrate-from-ink.md description: >- Switch an Ink 7.0 app to Silvery — drop-in @silvery/ink compat layer, then opt into Silvery-native features at your own pace. --- # Migrate from Ink Silvery is a drop-in for Ink. Change one import line and your app keeps running — same `Box`, `Text`, `useInput`, `useApp`, `useFocus`, `usePaste`, `useAnimation`, `useWindowSize`, `useStdout`, `Static`. **918 of Ink 7.0's 931 tests (~98.6%) pass on the compat layer.** Then opt into Silvery-native features (responsive layout, scroll containers, mouse, find, clipboard, 45+ components) one at a time. ## The 60-second migration ::: code-group ```bash [bun] bun remove ink && bun add silvery react ``` ```bash [npm] npm uninstall ink && npm install silvery react ``` ```bash [pnpm] pnpm remove ink && pnpm add silvery react ``` ```bash [yarn] yarn remove ink && yarn add silvery react ``` ::: Change every `from "ink"` import to `from "silvery/ink"`: ```diff - import { Box, Text, render, useInput } from "ink" + import { Box, Text, render, useInput } from "silvery/ink" ``` That's it. Your app should compile and run. If you used Chalk, swap that too: ```diff - import chalk from "chalk" + import chalk from "silvery/chalk" ``` The Chalk compat layer passes **32 of 32 tests (100%)**. ## What changes (and what doesn't) **Nothing changes about your component code.** ``, ``, `useInput`, `useFocus`, `useFocusManager`, `useApp`, `useStdout`, `useStdin`, `useAnimation`, `useWindowSize`, `useCursor`, `usePaste`, `Static`, `Transform`, `render()` — all the same names, same props, same behavior. **What's different** — 13 intentional design choices in the compat layer: * **W3C flexbox spec over Yoga quirks** (4 tests) — Silvery's [Flexily](https://beorn.codes/flexily) layout engine follows the W3C spec strictly. Yoga has non-standard behaviors around `flex-wrap` and `aspect-ratio` that some Ink tests expect. If you need exact Yoga parity, [Silvery supports Yoga as a pluggable layout engine](/guide/layout-engine). * **Build artifact format** (2 tests) — Ink expects a `./build/` directory. Silvery ships TypeScript source (for Bun) plus pre-built `dist/` (for Node). * **Minor rendering edge cases** (~7 tests) — SGR attribute emission order (dim+bold), `measureElement` timing in synchronous render, `renderToString` effect ordering. Produces identical visual output. [Full compatibility table →](/guide/silvery-vs-ink#compatibility) ## What you gain (opt-in, when you're ready) Once the import swap works, you can adopt Silvery-native features incrementally. None of these break your existing Ink-style code. ### Switch one component to native imports ```diff - import { Box, Text } from "silvery/ink" + import { Box, Text } from "silvery" ``` The native `silvery` exports are the same `Box` and `Text` — the compat layer is a thin shim. Imports from `silvery` give you access to the broader API (`useBoxRectDangerously`, native focus, scroll containers, etc.) in the same file. ### Responsive layout — `useBoxRectDangerously()` returns real dimensions during render In Ink, `useBoxMetrics()` returns `{ width: 0, height: 0 }` on the first render and updates via `useEffect`. That means N nesting levels = N visible flickers. Silvery's `useBoxRectDangerously()` returns actual dimensions on the first render — no `width: 0` flash: ```tsx import { Box, Text, useBoxRectDangerously } from "silvery" function Card({ title }: { title: string }) { const { width } = useBoxRectDangerously() return ( {width >= 32 ? title : title.slice(0, width - 1) + "…"} ) } ``` [Responsive Layout guide →](/guide/responsive-layout) ### Scroll containers — `overflow="scroll"` with virtualization Ink core has `visible` / `hidden` only ([#222](https://github.com/vadimdemedes/ink/issues/222), open since 2019). Silvery has true scroll containers with virtualization, sticky positioning, and native arrow-key/wheel scrolling: ```tsx ``` [Scrolling guide →](/guide/scrolling) · [Sticky positioning →](/guide/layout-coordinates) ### Mouse, text selection, find, clipboard Ink has none of these in core. Silvery ships them all: * **Mouse events** — `onClick`, `onWheel`, drag-and-drop, hit testing. [Event Handling →](/guide/event-handling) * **Text selection** — mouse drag, word/line selection, `userSelect` boundaries. [Text Selection →](/guide/text-selection) * **Find** — `Ctrl+F` with match highlighting and `n`/`N` navigation. [Find guide →](/guide/find) * **Clipboard** — OSC 52 paste/copy, `Esc, v` copy-mode for vim-style yanking. [Clipboard guide →](/guide/clipboard) ### 45+ built-in components Ink core has 6 components. The [`@inkjs/ui`](https://github.com/vadimdemedes/ink-ui) package adds 13 more. Silvery ships 45+ in core — `SelectList`, `TextInput`, `TextArea`, `Table`, `TreeView`, `Tabs`, `CommandPalette`, `ModalDialog`, `PickerDialog`, `Toast`, `Spinner`, `ProgressBar`, `Form`, `SplitView`, and more. Every component participates in focus, mouse, and keybindings automatically. [Components overview →](/guides/components) ### Performance — 3–27× faster on mounted rerenders Cell-level dirty tracking, per-node skip for unchanged subtrees, cell-level buffer diff. Native scrollback preserved in inline mode. Typical interactive update on a 1000-node tree: ~169 microseconds. [Benchmarks →](/guide/silvery-vs-ink#performance) ## What about Ink plugins? Some Ink ecosystem packages (`ink-select-input`, `ink-text-input`, `ink-spinner`, etc.) work unchanged against `silvery/ink`. Others rely on Ink's private DOM-tree internals and may need a compat shim or replacement. When swapping an Ink ecosystem component, check if Silvery has a native equivalent first — it usually does, and the native version is faster and more featureful. | Ink ecosystem | Silvery native | Notes | | ---------------------- | ------------------- | -------------------------------------------------------------------------- | | `ink-select-input` | `SelectList` | Search, multi-select, virtualization, keyboard + mouse | | `ink-text-input` | `TextInput` | Readline binding, full Unicode, paste, IME | | `ink-spinner` | `Spinner` | Built-in; `Spinner.start("…")` CLI helper too | | `ink-table` | `Table` | Responsive columns, search/filter | | `ink-progress-bar` | `ProgressBar` | Full-featured, theme-aware | | `ink-syntax-highlight` | `SyntaxHighlighter` | Use `languageForPath` from `@silvery/syntax` when inferring from filenames | ## Want the full feature comparison? The [Silvery vs Ink](/guide/silvery-vs-ink) page has the full feature matrix, performance numbers, compatibility breakdown, and migration scenarios: * [Compatibility coverage](/guide/silvery-vs-ink#compatibility) — exactly which Ink tests pass, which 13 fail and why * [Performance & size](/guide/silvery-vs-ink#performance-size) — benchmarks for mounted rerender, cold render, bundle * [Compat Layer Architecture](/reference/compatibility#compat-layer-architecture) — how the adapters bridge Ink APIs to silvery-native systems ## Need help? * Open an [issue on GitHub](https://github.com/beorn/silvery/issues) — happy to look at your migration * See [Troubleshooting](/guide/troubleshooting) for common gotchas * Browse [Imports & Subpaths](/guide/imports) for the full mapping between `silvery`, `silvery/ink`, `silvery/chalk`, and `silvery/runtime` --- --- url: /guide/the-silvery-way.md --- # The Silvery Way *How to build the shiniest Silvery apps* Silver tarnishes when you don't take care of it. So does your code. These are the principles that keep Silvery apps **shiny** (best practices) — and the anti-patterns that let them go **tarnished** (common pitfalls). Each principle is something people get wrong. If you're coming from Ink, Blessed, or raw ANSI — we've all been there. These patterns are natural starting points, but Silvery gives you better tools. If you're already doing the shiny thing, skip ahead. ## Three foundations The ten principles below all flow from three broader convictions: 1. **Take the best from the web.** Flexbox, scroll containers, DOM-style events, focus scopes, Playwright-style testing, design tokens — thirty years of web UI produced ideas worth keeping. If you'd reach for it on the web, reach for it in Silvery. 2. **Stay true to the terminal.** Cells, screens, buffers, ANSI, scrollback. The terminal is the medium, and Silvery embraces it. When a feature maps onto a terminal protocol — Kitty keyboard, OSC 52 clipboard, DEC mode 2026 — we expose it honestly, not as a polyfill. 3. **Raise the bar.** For developer ergonomics, architecture composability, and performance. The ten principles below are what this looks like in practice — each one exists because a shiny path is worth building and maintaining. ## 1. Use the Built-in Components Silvery ships 45+ components — all available from `import { ... } from "silvery"`. They handle keyboard navigation, theming, mouse support, kill ring, word movement, scroll indicators, and dozens of edge cases you haven't thought of yet. When you reimplement them, you lose all of that. ::: tip ✨ Shiny ```tsx Are you sure? ``` All themed. All mouse-aware. All keyboard-navigable out of the box — saving you time and bugs. ::: ::: danger 🩶 Tarnished ```tsx // Manual cursor tracking — loses j/k wrapping, mouse, scroll, selection theming const [cursor, setCursor] = useState(0) useInput((input) => { if (input === "j") setCursor((c) => Math.min(c + 1, items.length - 1)) if (input === "k") setCursor((c) => Math.max(c - 1, 0)) }) // Manual text handling — no Ctrl+A/E/K/U/W, no Alt+B/F, no kill ring, no clipboard useInput((input, key) => { if (key.backspace) setText((t) => t.slice(0, -1)) else if (!key.ctrl) setText((t) => t + input) }) ``` You'll spend a week reimplementing what [`SelectList`](/guides/components#selectlist) and [`TextInput`](/guides/components#textinput) give you in one line. ::: → [Components guide](/guides/components) · [Components & hooks reference](/reference/components-hooks) ## 2. Think in Flexbox Silvery uses CSS flexbox via [Flexily](/guide/layout-engine) — same mental model as web development. Let the layout engine compute positions and sizes. For responsive layout, reach for [`useResponsiveBoxProps`](/api/use-responsive-box-props) (declarative, no measurement reads). When you genuinely need a component's measured size, [`useBoxRectDangerously()`](/api/use-box-rect) returns the deferred (committed) rect — invariant across every convergence pass within one event batch, so layout decisions can't form feedback loops. ::: tip ✨ Shiny ```tsx // Fill remaining space I expand // Spacing with gap, not margins on every child