TUI: cssints/tui

A terminal app writes its styles with cssints/tui, and the same Vite plugin compiles them to prop objects for terminal renderables, not to classes: there is no sheet and no cascade. @cssints/tui applies them (states, the mouse pointer, the caret, themes); its adapters are OpenTUI and @bomb.sh/tty. The design, the full property table and what is out of scope are in research/tui.md.

import * as import csscss from "cssints/tui" with { type: "cssints" };

const 
const t: Tokens<{
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}, undefined, {
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}>
t
= import csscss.
createTokens<{
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}>(tokens: {
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
} & ValidGroup<{
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}, undefined, {
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}>, name?: (path: readonly string[]) => string | TokenName): Tokens<...>
export createTokens
Tokens from an inline DTCG-shaped `const` object: `$type` is inherited through groups, a leaf is `{ $value }`, a value is a DTCG value or a CSS string of the type's grammar, an alias is `"{group.token}"`. The variable is the kebab-case path (`--color-bg`), or what the mapper returns for the path.
createTokens
({
color: {
    readonly $type: "color";
    readonly bg: {
        readonly $value: "#1e1e2e";
    };
    readonly accent: {
        readonly $value: "#89b4fa";
    };
} & ValidGroup<{
    readonly $type: "color";
    readonly bg: {
        readonly $value: "#1e1e2e";
    };
    readonly accent: {
        readonly $value: "#89b4fa";
    };
}, "color", {
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}>
color
: { $type: "color"$type: "color",
bg: {
    readonly $value: "#1e1e2e";
}
bg
: { $value: "#1e1e2e"$value: "#1e1e2e" },
accent: {
    readonly $value: "#89b4fa";
}
accent
: { $value: "#89b4fa"$value: "#89b4fa" } } });
import csscss.
createGlobalTheme<Tokens<{
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}, undefined, {
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}>, {
    readonly color: {
        readonly bg: "#eff1f5";
        readonly accent: "#1e66f5";
    };
}, string>(tokens: Tokens<{
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}, undefined, {
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}>, condition: QueryWrap | string, overrides: {
    readonly color: {
        readonly bg: "#eff1f5";
        readonly accent: "#1e66f5";
    };
} & CheckOverrides<...>, options?: {
    ...;
} | undefined): void
export createGlobalTheme
Re-declares the overridden tokens under a media or container condition (on `:root`), or under a selector, and the aliases that depend on them. An unknown path and a value that is not of the token's type are type errors. `layer` puts the rule in a top-level layer that `layers()` declares (the build checks that it does); `scope` (with a selector) confines it to a subtree: `@scope (<scope>) { ... }`.
createGlobalTheme
(
const t: Tokens<{
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}, undefined, {
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}>
t
, "light", {
color: {
    readonly bg: "#eff1f5";
    readonly accent: "#1e66f5";
} & CheckOverrides<Tokens<{
    readonly $type: "color";
    readonly bg: {
        readonly $value: "#1e1e2e";
    };
    readonly accent: {
        readonly $value: "#89b4fa";
    };
}, "color", {
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}>, {
    readonly bg: "#eff1f5";
    readonly accent: "#1e66f5";
}, Tokens<{
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}, undefined, {
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}>>
color
: { bg: "#eff1f5"bg: "#eff1f5", accent: "#1e66f5"accent: "#1e66f5" } });
export const const button: css.TuiStylebutton = import csscss.const cn: (...styles: css.TuiStyle[]) => css.TuiStyle
Merges styles in argument order: a later prop wins.
cn
(
import csscss.const border: <readonly ["#585b70"]>(style: Exclude<BorderWord, "none">, color_0: "#585b70") => css.TuiStyle (+1 overload)border("rounded", "#585b70"), import csscss.const p: (...values: [css.Cells] | [css.Cells, css.Cells] | [css.Cells, css.Cells, css.Cells] | [css.Cells, css.Cells, css.Cells, css.Cells]) => css.TuiStylep(0, 1), import csscss.const width: (value: css.Size) => css.TuiStylewidth(12), import csscss.const bg: <readonly [Token<"color", true, "#1e1e2e">]>(parts_0: Token<"color", true, "#1e1e2e">) => css.TuiStyle
`background-color`.
bg
(
const t: Tokens<{
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}, undefined, {
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}>
t
.
color: Tokens<{
    readonly $type: "color";
    readonly bg: {
        readonly $value: "#1e1e2e";
    };
    readonly accent: {
        readonly $value: "#89b4fa";
    };
}, "color", {
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}>
color
.bg: Token<"color", true, "#1e1e2e">bg),
import csscss.const cursor: (value: css.Cursor) => css.TuiStyle
The mouse pointer over the element: set on over, back to `default` on out.
cursor
("pointer"),
import csscss.const hover: (...styles: css.TuiStyle[]) => css.TuiStyle
The styles while the pointer is over the element (`onMouseOver` to `onMouseOut`).
hover
(import csscss.const borderColor: <readonly [Token<"color", true, "#89b4fa">]>(parts_0: Token<"color", true, "#89b4fa">) => css.TuiStyleborderColor(
const t: Tokens<{
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}, undefined, {
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}>
t
.
color: Tokens<{
    readonly $type: "color";
    readonly bg: {
        readonly $value: "#1e1e2e";
    };
    readonly accent: {
        readonly $value: "#89b4fa";
    };
}, "color", {
    readonly color: {
        readonly $type: "color";
        readonly bg: {
            readonly $value: "#1e1e2e";
        };
        readonly accent: {
            readonly $value: "#89b4fa";
        };
    };
}>
color
.accent: Token<"color", true, "#89b4fa">accent)),
import csscss.const active: (...styles: css.TuiStyle[]) => css.TuiStyle
The styles while a mouse button is down on the element.
active
(import csscss.const borderStyle: (value: BorderWord) => css.TuiStyleborderStyle("double")),
); import csscss.const width: (value: css.Size) => css.TuiStylewidth("10rem"); // error: a terminal counts cells
Argument of type '"10rem"' is not assignable to parameter of type 'Size'.
import csscss.boxShadow("0 0 1px red"); // error: no such member in a terminal
Property 'boxShadow' does not exist on type 'typeof import("packages/cssints/src/tui-types")'.
import csscss.const color: <readonly ["blu"]>(parts_0: "blue" | "blueviolet" | "color: unexpected \"blu\" in \"blu\", grammar <color>") => css.TuiStylecolor("blu"); // error: not a colour
Argument of type '"blu"' is not assignable to parameter of type '"blue" | "blueviolet" | "color: unexpected \"blu\" in \"blu\", grammar <color>"'.
export const button = { border: true, borderStyle: "rounded", borderColor: "#585b70", paddingTop: 0, paddingRight: 1, paddingBottom: 0, paddingLeft: 1, width: 12, backgroundColor: "#1e1e2e", cursor: "pointer", ":hover": { borderColor: "#89b4fa" }, ":active": { border: true, borderStyle: "double" }, themes: { light: { backgroundColor: "#eff1f5", ":hover": { borderColor: "#1e66f5" } } }, vars: { backgroundColor: "color-bg", ":hover": { borderColor: "color-accent" } } };
import { attach, setTheme, use } from "@cssints/tui/opentui";

<box {...use(button)}><text>Save</text></box>; // Solid or React: the spread is a ref that calls attach
<text {...use(() => (active() ? tabActive : tab))}>Home</text>; // a style that changes: an accessor
attach(new BoxRenderable(renderer, {}), button); // core
setTheme("light");
import { close, createTerm } from "@bomb.sh/tty";
import { createTracker } from "@cssints/tui/bomb";

const term = await createTerm({ width: 80, height: 24 });
const ui = createTracker();
const build = () => [ui.open("save", button), ui.text("Save", label, "save"), close()];
process.stdout.write(ui.render(term, build, { pointer: { x, y, down } }).output);
// theme.ts: run time, not a cssints module
import { createSystemTheme } from "@cssints/tui/system";
import { t } from "./styles.ts";

export const system = createSystemTheme(t, {
	color: {
		bg: { surface: true },
		card: { surface: true, on: [t.color.bg], contrast: 1.1 },
		text: { hue: "fg", on: [t.color.bg, t.color.card], contrast: 7 },
		accent: { hue: "blue", on: [t.color.bg, t.color.card], contrast: 3 },
	},
}, { themes: { light: "light" } });

// OpenTUI: renderer.getPalette() and its theme_mode event
await followSystemTheme(await createCliRenderer(), system);
// @bomb.sh/tty: the queries are written, the replies taken out of stdin before input.scan()
const follow = followSystemTheme(system, { write: (s) => process.stdout.write(s) });
process.stdin.on("data", (bytes) => dispatch(input.scan(follow.feed(bytes)).events));