Skip to content

Components

Silvery provides layout, text, input, and display components for building terminal UIs.

Box

The primary layout component. Uses CSS flexbox for layout.

tsx
import { Box, Text } from "silvery"

function Layout() {
  return (
    <Box flexDirection="row" justifyContent="space-between">
      <Text>Left</Text>
      <Text>Right</Text>
    </Box>
  )
}

Scrolling

Use overflow="scroll" with scrollTo for automatic scrolling:

tsx
<Box flexDirection="column" overflow="scroll" scrollTo={selectedIndex}>
  {items.map((item, i) => (
    <Text key={i} inverse={i === selectedIndex}>
      {item}
    </Text>
  ))}
</Box>

See Scrolling Guide for details.

Props

PropTypeDefaultDescription
flexDirection"row" | "column" | "row-reverse" | "column-reverse""row"Main axis direction
flexGrownumber0Grow factor
flexShrinknumber1Shrink factor
flexBasisnumber | string-Initial size
justifyContent"flex-start" | "flex-end" | "center" | "space-between" | "space-around""flex-start"Main axis alignment
alignItems"flex-start" | "flex-end" | "center" | "stretch""stretch"Cross axis alignment
paddingnumber0Padding on all sides
paddingXnumber0Horizontal padding
paddingYnumber0Vertical padding
marginnumber0Margin on all sides
widthnumber | string-Fixed or percentage width
heightnumber | string-Fixed or percentage height
minWidthnumber-Minimum width
minHeightnumber-Minimum height
borderStyle"single" | "double" | "round" | "bold" | "classic"-Border style
borderColorstring-Border color
overflow"visible" | "hidden" | "scroll""visible"Overflow behavior
scrollTonumber-Child index to keep visible

Text

Renders text with styling. Supports Chalk strings.

tsx
import { Text } from "silvery"
import chalk from "chalk"

// Basic styling
<Text color="green" bold>Success!</Text>

// Chalk strings work too
<Text>{chalk.red.bold("Error!")}</Text>

Auto-Truncation

Text automatically truncates to fit available width:

tsx
<Box width={20}>
  <Text>This is a very long text that will be truncated</Text>
</Box>
// Output: "This is a very lon…"

Opt out with wrap={false} if you need overflow behavior.

Props

PropTypeDefaultDescription
colorstring-Text color
backgroundColorstring-Background color
boldbooleanfalseBold text
italicbooleanfalseItalic text
underlinebooleanfalseUnderlined text
strikethroughbooleanfalseStrikethrough text
dimColorbooleanfalseDimmed color
inversebooleanfalseSwap foreground/background
wrap"wrap" | "truncate" | "truncate-start" | "truncate-middle" | "truncate-end""truncate"Text wrapping behavior

Newline

Renders a newline character.

tsx
import { Newline, Text } from "silvery"

<Text>Line 1</Text>
<Newline />
<Text>Line 2</Text>

Spacer

Flexible space that expands to fill available room.

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

function Layout() {
  return (
    <Box>
      <Text>Left</Text>
      <Spacer />
      <Text>Right</Text>
    </Box>
  )
}

Static

Renders content that won't be updated. Useful for logs or output that scrolls up.

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

function App() {
  const [logs, setLogs] = useState<string[]>([])

  return (
    <Box flexDirection="column">
      <Static items={logs}>{(log, i) => <Text key={i}>{log}</Text>}</Static>
      <Text>Current status...</Text>
    </Box>
  )
}

Props

PropTypeDescription
itemsT[]Array of items to render
children(item: T, index: number) => ReactNodeRender function

Input Components

TextInput

Single-line text input with full readline shortcuts (Ctrl+A/E, Ctrl+K/U, Alt+B/F, Ctrl+Y with kill ring).

tsx
import { TextInput } from "silvery"

function MyInput() {
  return <TextInput value={text} onChange={setText} onSubmit={handleSubmit} placeholder="Type here..." prompt="> " />
}

TextArea

Multi-line text editing with cursor navigation, line wrapping, and text selection.

tsx
import { TextArea } from "silvery"

function MyEditor() {
  return (
    <TextArea
      value={text}
      onChange={setText}
      height={5}
      placeholder="Type here..."
      submitKey="ctrl+enter"
      onSubmit={handleSubmit}
      scrollMargin={1}
    />
  )
}
PropTypeDefaultDescription
valuestring-Current value (controlled)
defaultValuestring-Initial value (uncontrolled)
onChange(value: string) => void-Called when value changes
onSubmit(value: string) => void-Called on submit key press
submitKey"ctrl+enter" | "enter" | "meta+enter""ctrl+enter"Key combo to trigger submit
heightnumberrequiredVisible height in rows
placeholderstring-Placeholder text when empty
isActiveboolean-Override focus system
cursorStyle"block" | "underline""block"Unfocused cursor style
scrollMarginnumber1Context lines above/below cursor when scrolling
disabledbooleanfalseIgnore input and dim text
maxLengthnumber-Maximum character count
testIDstring-Test ID for focus system

Features: Shift+Arrow selection, Ctrl+A select all, Ctrl+Home/End document navigation, word-wise movement (Ctrl+Arrow), readline shortcuts (Ctrl+K/U/Y), column memory for vertical movement.

SelectList

Single-select list with keyboard navigation (arrow keys, j/k, Home/End), disabled item support, and maxVisible for scroll windowing.

tsx
import { SelectList } from "silvery"

function MyList() {
  return (
    <SelectList
      items={[
        { label: "React", value: "react" },
        { label: "Vue", value: "vue" },
        { label: "Svelte", value: "svelte" },
      ]}
      onSelect={(item) => console.log(item.value)}
    />
  )
}

Toggle, Button

Simple interactive primitives for boolean toggles and clickable buttons.

Display Components

ComponentDescription
SpinnerAnimated spinner with presets (dots, line, arc, bounce)
ProgressBarDeterminate and indeterminate progress with custom fill
TableColumn-aligned table with header, per-column alignment
BadgeStyled label/tag
DividerHorizontal rule
VirtualListO(1) scroll for thousands of items — see List Components
VirtualViewVirtualized arbitrary content
ConsoleCaptures console.log output via patchConsole()
TransformPer-line string transformation on children
ImageKitty graphics / Sixel with text fallback
LinkOSC 8 hyperlink

Shadcn-Style Components

Higher-level pre-styled components using $token semantic colors. Import from silvery:

ComponentDescription
Form / FormFieldForm layout with label, description, error message
Toast / useToast()Auto-dismiss notifications with severity levels
CommandPaletteFuzzy-search command palette (Ctrl+K pattern)
TreeViewExpandable/collapsible tree with keyboard navigation
BreadcrumbPath breadcrumb with separator customization
Tabs / TabList / Tab / TabPanelTabbed interface with keyboard navigation
TooltipContextual tooltip overlay
SkeletonLoading placeholder with configurable width/lines
ErrorBoundaryReact error boundary with resetKeys and fallback
ModalDialogModal overlay with focus trapping
PickerDialog / PickerListSelection dialog

These components use the theming system — wrap your app in ThemeProvider with defaultDarkTheme or defaultLightTheme and the components will use semantic colors automatically. Any color prop starting with $ is resolved against the active theme (e.g. color="$primary", backgroundColor="$surface-bg").

@silvery/ui — Progress & Input Package

@silvery/ui is a separate package with progress indicators and ergonomic async wrappers. It works both as standalone CLI output (no React) and as React components inside Silvery apps.

bash
npm install @silvery/ui
bash
bun add @silvery/ui
bash
pnpm add @silvery/ui
bash
yarn add @silvery/ui

CLI mode (direct stdout, no React):

ts
import { Spinner, ProgressBar, MultiProgress } from "@silvery/ui/cli"

const stop = Spinner.start("Loading...")
await doWork()
stop()

Wrappers (ergonomic async adapters):

ts
import { withSpinner, withProgress } from "@silvery/ui/wrappers"

const data = await withSpinner(fetchData(), "Loading data...")

Declarative steps:

ts
import { steps } from "@silvery/ui/progress"

const loader = steps({ loadModules, parseConfig, validate })
await loader.run({ clear: true })

React components (for Silvery/Ink apps):

tsx
import { Spinner, ProgressBar, Tasks, Task } from "@silvery/ui/react"
import { TextInput, Select } from "@silvery/ui/input"

See the @silvery/ui README for full documentation.

Released under the MIT License.