· Abdul Hamid Achik

The Tuimark kit: components you copy and own

Twenty-one terminal UI components for Tuimark, from buttons and alerts to a data table, dialogs, and a command palette, as plain documents and two stylesheets you copy into your project. How the kit is built, and the host helpers that come with it.

Every terminal app ends up drawing the same things: a status bar, a table with a cursor, a confirm dialog, a toast. In Tuimark each of those is a few lines of markup, but writing them well every time (the focus states, the light theme, the narrow terminal) is still work. The kit is that work done once, in the open, for you to copy.

$ tuimark play examples/kit-gallery/app.tui --data examples/kit-gallery/sample.json --cols 110 --rows 30110×30
kit gallery: the basics tab with a breadcrumb, badges, buttons, alerts and stat cards in the kit components, under the tab strip and the key hintskit gallery: the basics tab with a breadcrumb, badges, buttons, alerts and stat cards in the kit components, under the tab strip and the key hints

examples/kit-gallery: every component in one app, on five tabs.

Copy it, own it

The kit follows the shadcn idea. It is not a package and there is nothing to import. You copy the files you need into your project, and from then on they are yours: if a card should look different, you edit your copy.

It lives in the repository's kit/ directory:

  • theme.tcss holds the design tokens, dark and light, on top of the ten tokens the runtime itself requires;
  • kit.tcss holds the component classes, all named k-something;
  • components/NAME.tui is one small, valid document per component, with a NAME.sample.json beside it when it reads data.

Load the two stylesheets first and your own last. TCSS has no @import, so a document lists the sheets it uses, and where two rules tie, the later one wins. That is how you rebrand the whole kit: every color is a token in :root, so your sheet only re-declares the ones you want to change.

<tui theme="auto">
  <style src="theme.tcss"/>
  <style src="kit.tcss"/>
  <style src="app.tcss"/>
  ...
</tui>

No new language

The kit adds no tag, no attribute, and no CSS property. A component is ordinary markup with class names, ids, and action names that follow three conventions:

  • classes are k-<component> and k-<component>-<part>, with short modifiers such as primary or danger used only next to them;
  • ids are instance__part, so deploy__go is the go button of the deploy card;
  • actions are component_verb, such as dialog_confirm or palette_run, so a host can route them by prefix.

Each component file starts with a contract, a comment that says what it reads from the data, which actions it fires, which keys it binds, and which other kit classes it needs. Everything a host must provide is in that one line.

$ tuimark play kit/components/table.tui --data kit/components/table.sample.json --cols 72 --rows 1072×10
a data table of services with a header, state colors, right-aligned numbers and the cursor row as an accent bara data table of services with a header, state colors, right-aligned numbers and the cursor row as an accent bar

Because a component is a document, it can be tried before it is copied. Each one validates and runs on its own:

tuimark validate kit/components/table.tui --data kit/components/table.sample.json
tuimark run kit/components/table.tui --data kit/components/table.sample.json

The kit components page shows all twenty-one this way, rendered by tuimark play in the dark and light themes, with the markup of each.

The parts markup cannot do

Markup has no expressions, so some components lean on the host. A toast has to disappear after a few seconds, the command palette has to filter its list as you type, a form has to validate its fields, and a dialog's answer has to reach the code that opened it. The kit ships that code too, as helpers in Go (kit/go) and in TypeScript (kit/ts):

  • toasts, a capped queue of notices that expire;
  • filter and sortpage, substring or fuzzy filtering, column sorting, and pages;
  • form, per-field rules that write each field's error and the form's validity;
  • dialog and route, which route a dialog's answer, or any event, by the instance that fired it;
  • palette, a command registry; async, loading and error state for slow work; format, bytes, durations, relative times, and percentages.

Like the components, they are files you copy and own, not a dependency. A Go helper imports only the Tuimark package, and a TypeScript helper imports only types from @thelacanians/tuimark. Each one has tests, and the Go tests of the toasts, forms, dialogs, palette, and sorting load the real component documents and drive them.

$ tuimark play kit/components/confirm.tui --data kit/components/confirm.sample.json --cols 72 --rows 1672×16
a confirm dialog over the page asking to delete a project, with Cancel and Delete buttonsa confirm dialog over the page asking to delete a project, with Cancel and Delete buttons

Two apps built on it

examples/kit-gallery puts every component in one app on five tabs, with a Go host that routes events by their prefix and tests that hold every document to the kit's conventions. examples/containers is a container-engine dashboard built only from kit components: a filterable, sortable table with multi-select, a detail pane with sparklines and a log, confirm dialogs, a command palette, a help overlay, and toasts, over a simulated engine.

Start from the kit components page, pick one, and copy it. The kit guide has the tokens, the conventions, and every helper in detail.