Layout & Styling#

The terminal renderer walks your component tree into an array of ANSI-styled lines and paints them every frame. This guide covers the three intrinsic elements, the line layout model, and how colors and borders work.

The intrinsic elements#

@sigx/terminal provides three host elements you can use in TSX:

  • box — a block container that can draw a border, a centered label, a background and a drop shadow.
  • text — an inline run of text, optionally colored.
  • br — a hard line break that starts a new line.
TSX
/** @jsxImportSource @sigx/terminal */
import { component, defineApp } from '@sigx/terminal';

const App = component(() => {
    return () => (
        <box border="single" label="Demo">
            <text>First line of </text>
            <text color="cyan">inline text.</text>
            <br />
            <text>Second line after a break.</text>
        </box>
    );
});

defineApp(<App />).mount({ clearConsole: true });

How layout works#

The layout is a simple inline/block line flow — not a full flexbox engine. There are no row/column, justify or align props. The rules are:

  • A box (and any element whose immediate child is a box) is treated as a block — it gets its own lines.
  • text and other single-line children are appended inline to the current line.
  • A br forces a new line.
  • Multi-line non-box children are promoted to block so they don't break a surrounding border.

So to stack content vertically, wrap each row in its own box, or separate runs with br:

TSX
<box border="rounded" label="Stacked">
    <box><text>Row one</text></box>
    <box><text>Row two</text></box>
    <box><text>Row three</text></box>
</box>

Layout primitives#

For real horizontal and vertical layout, the foundation ships components that compose box for you:

  • Box — the themed container: border (single / double / rounded / thick / none), borderColor, backgroundColor, label, labelColor, padX, dropShadow.
  • Row — lays children out horizontally with a gap and align (start / center / end, plus top / bottom).
  • Col — stacks children vertically with a gap.
  • Spacer — inserts blank size lines (vertical spacing).
  • Divider — a horizontal rule with an optional centered label and color.
TSX
/** @jsxImportSource @sigx/terminal */
import { component, defineApp, Box, Row, Col, Divider } from '@sigx/terminal';

const App = component(() => {
    return () => (
        <Box border="rounded" label="Dashboard">
            <Row gap={2} align="center">
                <Box border="single" label="Left"><text>A</text></Box>
                <Box border="single" label="Right"><text>B</text></Box>
            </Row>
            <Divider label="logs" />
            <Col gap={1}>
                <text>line one</text>
                <text>line two</text>
            </Col>
        </Box>
    );
});

defineApp(<App />).mount({ clearConsole: true });

The box/text/br intrinsics below are the lower-level host elements these primitives render to. Use the intrinsics for fine control or the primitives for ordinary layout.

Borders#

The border prop accepts single, double, rounded or thick. To draw no border, use none (the Box component) or omit the prop entirely (the box intrinsic):

TSX
<box border="single">...</box>    {/* ┌─┐ │ └─┘ */}
<box border="double">...</box>    {/* ╔═╗ ║ ╚═╝ */}
<box border="rounded">...</box>   {/* ╭─╮ │ ╰─╯ */}
<box border="thick">...</box>     {/* ┏━┓ ┃ ┗━┛ */}
<box>...</box>                     {/* no border */}

The renderer only draws a border when the border prop is truthy, and any unrecognized value falls back to the single-line style. So border="none" currently still renders a single border — leave the prop off to get no border at all.

Set borderColor to color the border with a named color, and label to render a centered caption in the top border, like a fieldset legend:

TSX
<box border="rounded" borderColor="cyan" label="Settings">
    <text>Configuration goes here.</text>
</box>

Colors#

Colors use a fixed named palette mapped to ANSI codes:

red, green, blue, yellow, cyan, white, black.

TSX
<box backgroundColor="blue" borderColor="white">
    <text color="yellow">Warning</text>
    <br />
    <text color="green">OK</text>
</box>
  • color styles text foreground.
  • backgroundColor fills the box padding and border area.
  • borderColor colors the border characters.

Only the named palette resolves to an ANSI code. Arbitrary strings or hex values such as #666666 produce no color, so stick to the named set.

Drop shadow#

dropShadow adds a gray shadow on the right and bottom edges of a box:

TSX
<box border="single" label="Card" dropShadow={true}>
    <text>This box casts a shadow.</text>
</box>

Putting it together#

TSX
/** @jsxImportSource @sigx/terminal */
import { component, defineApp, signal } from '@sigx/terminal';

const App = component(() => {
    const status = signal<'ok' | 'warn'>('ok');

    return () => (
        <box border="rounded" label="Dashboard" borderColor="cyan" dropShadow={true}>
            <box>
                <text>Status: </text>
                <text color={status.value === 'ok' ? 'green' : 'yellow'}>
                    {status.value === 'ok' ? 'Healthy' : 'Degraded'}
                </text>
            </box>
            <box backgroundColor="blue">
                <text color="white">Background-filled row</text>
            </box>
        </box>
    );
});

defineApp(<App />).mount({ clearConsole: true });

Tokens vs named colors#

The named palette above maps to fixed ANSI codes. The themed components and the Box/Text primitives instead accept theme tokens (accent, fg, line, …) that resolve against the active theme and adapt to the terminal's color depth — see Theming. Use named colors for quick scripts; use tokens for themed apps.

Next steps#