Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 73 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,53 @@
A dependency-free TypeScript terminal UI runtime. Screens are plain data; JSX is one way to write them.

```bash
npm install @textui/kit @textui/widgets
npm install @textui/kit
```

```tsx
import { Box, Text, render, useInput, useState } from '@textui/kit';

function Counter() {
const [count, setCount] = useState(0);
useInput((e) => {
if (e.name !== '+') return false;
setCount((c) => c + 1);
return true;
});

return (
<Box border="round" padding={1} direction="column">
<Text bold>Count: {count}</Text>
<Text dim>+ to increment, ctrl+c to quit</Text>
</Box>
);
}

const { waitUntilExit } = render(<Counter />);
await waitUntilExit();
```

> Status: pre-1.0. The surface is still moving.

## Installing

One package is enough to have something on screen. The rest are additive, and
nothing pulls in a third-party dependency.

```bash
npm install @textui/kit # pnpm add @textui/kit
```

`@textui/kit` is the runtime, a terminal to put it on, and `render`. `Box`,
`Text`, the hooks and `render` all come from here, and the example above needs
nothing else.

**For the component catalog** - `Panel`, `Table`, `Row`, `Column`, charts,
overlays, forms - add `@textui/widgets`. Its components are resolved by name at
mount time, so they have to be registered before they can render:

```bash
npm install @textui/kit @textui/widgets # pnpm add @textui/kit @textui/widgets
```

```tsx
Expand All @@ -14,7 +60,32 @@ const { waitUntilExit } = render(<Dashboard />, { onBoot: registerBuiltins });
await waitUntilExit();
```

> Status: pre-1.0. The surface is still moving.
Forget `onBoot` and the components are missing registrations rather than
missing imports - the screen renders, and says so where they should have been.

**For a project set up for you**, and for components copied into your source
rather than imported, use the CLI. It needs no install:

```bash
npx @textui/cli init # pnpm dlx @textui/cli init
npx @textui/cli doctor # what this terminal can actually do
```

`doctor` is the one to run first when something renders wrong - it reports the
unicode level, colour depth and keyboard protocol actually detected.

**Working against the pieces directly** - `@textui/core` and `@textui/terminal`
- is the same thing with a longer name. `@textui/kit` re-exports both and adds
`render`; there is nothing in them it hides. Take them separately when you want
the runtime without a terminal attached, which is what the test harness and the
static renderer do:

```bash
npm install @textui/core @textui/terminal # pnpm add @textui/core @textui/terminal
```

Node >= 22, and TypeScript wants `"jsx": "react-jsx"` with
`"jsxImportSource": "@textui/kit"` - see [getting started](docs/getting-started.md).

## The one idea

Expand Down
109 changes: 94 additions & 15 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,99 @@ type Service = { name: string; status: string; cpu: string };
# Getting started

```bash
pnpm add @textui/core @textui/terminal
npm install @textui/kit # pnpm add @textui/kit
```

Node ≥ 22. There are no other dependencies.
Node ≥ 22. There are no other dependencies, and this one package is enough to
put something on screen.

## The smallest thing that runs
## Hello, in full

`@textui/kit` is the runtime, a terminal to put it on, and `render`. Everything
this example uses comes from it.

```tsx
import { Box, Text, render, useInput, useState } from '@textui/kit';

function Counter() {
const [count, setCount] = useState(0);
useInput((e) => {
if (e.name !== '+') return false;
setCount((c) => c + 1);
return true;
});

return (
<Box border="round" padding={1} direction="column">
<Text bold>Count: {count}</Text>
<Text dim>+ to increment, ctrl+c to quit</Text>
</Box>
);
}

const { waitUntilExit } = render(<Counter />);
await waitUntilExit();
```

`render` mounts and keeps running - it returns a handle rather than a promise,
so the application is up before the next line. `waitUntilExit()` resolves when
it stops, however it stopped.

For JSX, point the compiler at the runtime:

```jsonc
// tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@textui/kit"
}
}
```

## Adding the catalog

`Box` and `Text` are host primitives and always available. `Panel`, `Table`,
`Row`, `Column`, the charts, forms and overlays live in `@textui/widgets`, and
are resolved **by name at mount time** - so they have to be registered before
they can render:

```bash
npm install @textui/widgets # pnpm add @textui/widgets
```

```tsx
import { Text, render } from '@textui/kit';
import { Panel, registerBuiltins } from '@textui/widgets';

function Dashboard() {
return (
<Panel title="Services">
<Text>nothing to report</Text>
</Panel>
);
}

const { waitUntilExit } = render(<Dashboard />, { onBoot: registerBuiltins });
await waitUntilExit();
```

Forget `onBoot` and nothing throws at compile time: a missing registration is a
runtime miss, rendered visibly where the component should have been.

## The pieces underneath

`@textui/kit` re-exports `@textui/core` and [`@textui/terminal`](terminal)
and adds `render`; it hides nothing. Take them
separately when you want the runtime without a terminal attached - which is
what the test harness and the static renderer do - or when you want to own the
wiring:

```bash
npm install @textui/core @textui/terminal # pnpm add @textui/core @textui/terminal
```

Wired by hand, that same hello is:

```tsx
import { createApp, WRITER_KEY } from '@textui/core';
Expand All @@ -38,19 +125,11 @@ await app.start();

## JSX

Point the compiler at the runtime:

```jsonc
// tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "@textui/core"
}
}
```
Working against the pieces directly, `jsxImportSource` is `@textui/core`
rather than `@textui/kit` - the runtime is where the factory lives, and the kit
re-exports it.

Then `<Row gap={1}/>` produces `{ component: 'Row', gap: 1 }`. Lowercase names are host primitives - `box`, `text`, `canvas`, `spacer` - and capitalised names are components you import, which is what gives them prop types.
`<Row gap={1}/>` produces `{ component: 'Row', gap: 1 }`. Lowercase names are host primitives - `box`, `text`, `canvas`, `spacer` - and capitalised names are components you import, which is what gives them prop types.

```tsx
import { useStoreValue } from '@textui/core';
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions scripts/docs/snippets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"dependencies": {
"@textui/cli": "workspace:*",
"@textui/core": "workspace:*",
"@textui/kit": "workspace:*",
"@textui/documents": "workspace:*",
"@textui/terminal": "workspace:*",
"@textui/textide": "workspace:*",
Expand Down
Loading