voxal docs
SDK · CoreSDK · UI (React)

Components

Box, Text, Newline, Spacer, Static, and Transform, the building blocks of a voxal UI

The UI layer ships six components, all imported from @voxalsh/sdk/ui. <Box> lays out the screen with flexbox and <Text> styles inline text. The rest are small helpers for spacing, append-only output, and post-processing. They match Ink one to one.

import { Box, Text, Newline, Spacer, Static, Transform } from '@voxalsh/sdk/ui';

Box

<Box> is a flexbox container. It is the only layout primitive: you nest boxes to build rows, columns, and grids, and you size, space, and border them with props. A box never holds text directly; put text in a <Text> child.

<Box flexDirection="column" borderStyle="round" padding={1} gap={1}>
  <Text bold>Dashboard</Text>
  <Box gap={2}>
    <Text>left</Text>
    <Text>right</Text>
  </Box>
</Box>

Layout props

NameTypeDefaultDescription
flexDirection'row' | 'column' | 'row-reverse' | 'column-reverse''row'Main axis direction.
flexWrap'nowrap' | 'wrap' | 'wrap-reverse''nowrap'Whether children wrap onto new lines.
alignItemsflex valueCross axis alignment of children.
alignSelfflex valueOverride the parent's alignItems for this box.
alignContentflex valueAlignment of wrapped lines.
justifyContentflex valueMain axis distribution of children.
flexGrownumber0Share of free space this box takes.
flexShrinknumber1How much this box shrinks when space is tight.
flexBasisnumber | stringInitial main axis size before grow/shrink.

Size props

width, height, minWidth, minHeight, maxWidth, maxHeight. A number means cells, a string like '50%' means percent of the parent, and 'auto' is allowed. aspectRatio (a number) locks the width to height ratio.

<Box width="50%" height={10} minWidth={20}>
  <Text>half the parent, ten rows tall</Text>
</Box>

Spacing props

All spacing is in cells. Padding adds space inside the border, margin outside it.

  • Padding: padding, paddingX, paddingY, paddingTop, paddingBottom, paddingLeft, paddingRight.
  • Margin: margin, marginX, marginY, marginTop, marginBottom, marginLeft, marginRight.
  • Gap between children: gap, columnGap, rowGap.

Position props

position ('relative', 'absolute', or 'static') with top, bottom, left, right. Use 'absolute' to overlay a box on top of its siblings.

Display and overflow

display ('flex' or 'none'; 'none' removes the box from layout). overflow, overflowX, overflowY ('visible' or 'hidden'; clip children that exceed the box).

Borders

NameTypeDescription
borderStylename or objectA named style (single, double, round, ...) or a custom box-drawing object. See Styling.
borderColorcolorColor of all border edges.
borderTopColor borderRightColor borderBottomColor borderLeftColorcolorPer-edge border color.
borderTop borderRight borderBottom borderLeftbooleanToggle an edge. Default true; set false to drop that edge.
borderDimColorbooleanDim the border.
borderBackgroundColorcolorBackground color behind the border characters.

Background

backgroundColor fills the box. Descendant <Text> elements inherit it unless they set their own.

aria-* props are accepted but ignored. There is no screen reader over SSH.

Text

<Text> is styled inline text. It is the only component that renders characters. Nest <Text> inside <Text> to mix styles on one line.

<Text>
  status: <Text color="green" bold>ok</Text> <Text dimColor>(updated now)</Text>
</Text>
NameTypeDefaultDescription
colorcolorForeground color.
backgroundColorcolorBackground color. Inherited from an ancestor <Box backgroundColor>.
dimColorbooleanfalseRender at reduced intensity.
boldbooleanfalseBold weight.
italicbooleanfalseItalic.
underlinebooleanfalseUnderline.
strikethroughbooleanfalseStrikethrough.
inversebooleanfalseSwap foreground and background.
wrap'wrap' | 'truncate' | 'truncate-start' | 'truncate-middle' | 'truncate-end' | 'hard''wrap'How text that exceeds its width is handled.

If children is null or undefined, <Text> renders nothing, so {cond && <Text>...</Text>} is always safe.

See Styling for color formats.

Newline

A line break, for use inside <Text>.

<Text>
  line one<Newline />line two<Newline count={2} />and a gap above this
</Text>
NameTypeDefaultDescription
countnumber1Number of line breaks to insert.

<Newline> must be used inside <Text>.

Spacer

<Spacer /> takes no props and is equivalent to <Box flexGrow={1} />. It expands to fill free space, pushing its siblings apart along the parent's main axis.

<Box>
  <Text>left</Text>
  <Spacer />
  <Text>right</Text>
</Box>

In a column parent, <Spacer /> pushes content to the top and bottom, which is how you pin a footer.

Static

<Static> renders an array of items once, permanently, above the live region. The items scroll into the terminal's scrollback and are never repainted, which makes it ideal for append-only output: logs, chat history, completed tasks. Only ever add to the array; do not mutate items already rendered.

<Static items={messages}>
  {(message, index) => (
    <Box key={message.id}>
      <Text color="green" bold>{message.role}</Text>
      <Text>{'  '}{message.text}</Text>
    </Box>
  )}
</Static>
NameTypeDefaultDescription
itemsarrayThe list to render. New entries are appended; existing ones are never re-rendered.
children(item, index) => ReactElementRenders one item. Give each a stable key.
styleBox styleOptional style object applied to the wrapping box.

Static output is emitted in inline mode, not the fullscreen alternate screen. Pair it with an inline live region (the default), as in the chat app guide.

Transform

<Transform> post-processes the rendered output of its <Text> children, one line at a time. The transform function receives each rendered line and its index and returns the replacement string. Use it for effects that are easier to express on the final string than as components, such as gradients or line numbering.

<Transform transform={(line, index) => `${index + 1}  ${line}`}>
  <Text>first</Text>
  <Text>second</Text>
</Transform>
NameTypeDescription
transform(line: string, index: number) => stringMaps each rendered line to its output.

Like <Text>, <Transform> returns nothing if its children are null or undefined.

On this page