@cueplusplus/ui
v0.17.0
Published
CUE++ design system components: Tailwind v4 styled wrappers over Base UI, driven by @cueplusplus/tokens.
Readme
@cueplusplus/ui
Components for console software: dense, keyboard-first interfaces where a screen is mostly data. Base UI underneath for behaviour and accessibility, Tailwind v4 on top for the paint.
Install
The @cueplusplus scope is public on npm and resolves there by default, so there is nothing
to configure and no credential to supply — it installs like any other package.
pnpm add @cueplusplus/ui @cueplusplus/tokens @cueplusplus/theme-cue @base-ui/reactThree peers are required and declared: react ^19, react-dom ^19 and @base-ui/react ^1.7.
tailwindcss ^4 is a declared peer too, but an optional one — needed for the default styling
lane, and without it you own the CSS. Every other peer is optional and scoped to a subpath.
@cueplusplus/tokens arrives as a dependency; installing it by name just makes the token
constants importable in your own code. The theme package is not a peer at all — see below.
CI, Vercel and the token's expiry are in
docs/CONSUMING.md §1.
Quick start
/* app/globals.css */
@import "tailwindcss";
@import "@cueplusplus/ui/styles.css";
@import "@cueplusplus/theme-cue/theme.css";import cue from "@cueplusplus/theme-cue";
import { Button } from "@cueplusplus/ui";
import { ThemeProvider } from "@cueplusplus/ui/system";
export default function App() {
return (
<ThemeProvider themes={[cue]} theme="cue" density="compact" mode="system">
<Button variant="primary">Run cue</Button>
</ThemeProvider>
);
}The order of those three @imports is load-bearing, and the themes prop is not optional in
practice: this library contains no palette. Install and register a @cueplusplus/theme-* package,
or every surface paints @cueplusplus/theme-base's blank base — greys, one desaturated accent,
the platform monospace — with nothing erroring to tell you.
To stop the defaults flashing on first paint, add prepaintScript() beside the provider and hand
both the same array — §4 of CONSUMING.md
is the whole layout.
Beside theme and mode, the provider takes four axes, and each one changes a single thing:
density the geometry, font the typeface pairing, styleSet the page composition, and paint
the finish — rims, strokes, elevation, glow, glass, texture and edge fades. paint is off unless
you ask for it: the provider stamps no data-paint, so a page looks exactly as it did until you
pass one of none, halo, hatch, lift, frost, bevel or grain. Any subtree can take its
own with <Density paint="frost"> — the same island that re-scopes density and styleSet. The
font pairing is the one axis a subtree cannot change: it lives on <html>, so it is the root's.
import cue from "@cueplusplus/theme-cue";
import { ThemeProvider } from "@cueplusplus/ui/system";
export function App({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider themes={[cue]} theme="cue" density="compact" mode="system" paint="halo">
{children}
</ThemeProvider>
);
}The ten packs, where each was measured from and what each refuses:
https://ui.cueplusplus.com/docs/paint. Serving a nonce-based Content-Security-Policy? Read
§4.5 of CONSUMING.md
before you ship it — paint="grain" alone needs img-src data:.
What it ships
Common components come from the root entry. Groups that need an optional peer have a subpath of their own, so importing a button never pulls a charting library into your bundle.
| Import specifier | What is in it |
| --- | --- |
| @cueplusplus/ui | the root barrel: every component that costs no optional peer, the brand marks and the container icons included |
| @cueplusplus/ui/primitives | the leaves: buttons, chips, avatars, dots and the two loading shapes |
| @cueplusplus/ui/forms | everything a user types into, toggles, picks from or drags |
| @cueplusplus/ui/overlays | everything that floats: dialogs, sheets, menus, tooltips, the command palette |
| @cueplusplus/ui/chrome | console furniture: panels, rows, bars, tabs, the tree outline and the Inspector layout |
| @cueplusplus/ui/layout | page structure: cards, stacks, grids, Bento, the Preview frame, disclosure and the navigation shapes |
| @cueplusplus/ui/layout/carousel | needs embla-carousel-react |
| @cueplusplus/ui/layout/resizable | needs react-resizable-panels |
| @cueplusplus/ui/instruments | the readouts: meters, sparklines, tables, logs, CodeBlock and the two device frames |
| @cueplusplus/ui/instruments/data-table | needs @tanstack/react-table |
| @cueplusplus/ui/instruments/log-viewer | needs @tanstack/react-virtual, anser |
| @cueplusplus/ui/charts | Recharts in this system's tokens, plus the categorical palette and the ramps; needs recharts |
| @cueplusplus/ui/date | calendars, pickers and segmented date/time fields; needs react-day-picker, date-fns, react-aria-components, @internationalized/date |
| @cueplusplus/ui/color | the picker suite the configurator edits with, hex and oklch; needs react-aria-components |
| @cueplusplus/ui/configurator | the floating panel that edits the token layer live and exports what it edited; needs react-aria-components |
| @cueplusplus/ui/flow | React Flow wearing the tokens: node cards, handles, signal-carrying wires; needs @xyflow/react (+ ./flow.css) |
| @cueplusplus/ui/chat | the multi-agent transcript: messages, composer, HITL questions, agent colour |
| @cueplusplus/ui/agent-runtime | the streaming layer under Chat; needs @assistant-ui/react |
| @cueplusplus/ui/elements | the agent's own surface, vendored from assistant-ui and repainted onto the tokens; needs lucide-react and heat-graph (+ ./elements.css) |
| @cueplusplus/ui/elements/markdown | MarkdownText; needs react-markdown, rehype-sanitize |
| @cueplusplus/ui/elements/generative | the generative-UI renderer and its spec (+ ./elements/generative.css) |
| @cueplusplus/ui/elements/replay | the scripted session-replay frame |
| @cueplusplus/ui/dmx | 512 bytes at wire rate: the bar, the strip, the patch bar, the channel grid |
| @cueplusplus/ui/midi | sequencer instruments: keyboard, musical clock, ruler, analyser, threshold rail |
| @cueplusplus/ui/system | ThemeProvider, prepaintScript, the Density island, the portal frame, isSafeTokenValue |
| @cueplusplus/ui/theming | createTheme(), contrastReport() and the --cue-* vocabulary as constants |
Four stylesheet exports. @cueplusplus/ui/styles.css is always imported; the other three are
imported only if you use what they paint — @cueplusplus/ui/elements.css,
@cueplusplus/ui/flow.css and @cueplusplus/ui/elements/generative.css.
Showing a component: Preview, Inspector, CodeBlock
Three pieces for a page that shows components rather than only using them — a docs page, a design review, a settings screen with a live sample. Each is exported from the root barrel as well as from its group's subpath, and none needs an optional peer.
Preview — one component on a stage
From @cueplusplus/ui/layout. A frame with a stage for the specimen, a tools slot at the top
right and a footer. label is required, because the frame is a role="group" named by it. The
stage can carry its own theme, mode, density and font; with none of them set it carries no
attribute and inherits the page.
A stage theme needs that theme's stylesheet on the page. Naming a theme does not load it: the
stage is scoped with [data-theme="<name>"], so @cueplusplus/theme-<name>/theme.css has to be
imported like any other palette, or the stage paints the blank base with nothing erroring. The
example below shows terminal on a cue page, so both are imported and both are registered:
/* app/globals.css */
@import "tailwindcss";
@import "@cueplusplus/ui/styles.css";
@import "@cueplusplus/theme-cue/theme.css";
@import "@cueplusplus/theme-terminal/theme.css";import cue from "@cueplusplus/theme-cue";
import terminal from "@cueplusplus/theme-terminal";
import { Chip } from "@cueplusplus/ui";
import { Preview } from "@cueplusplus/ui/layout";
import { ThemeProvider } from "@cueplusplus/ui/system";
export default function ChipPage() {
return (
<ThemeProvider themes={[cue, terminal]} theme="cue" density="compact" mode="system">
<Preview label="Chip preview" theme="terminal" resizable footer="terminal · compact">
<Chip tone="accent">Standby</Chip>
</Preview>
</ThemeProvider>
);
}- Only the stage changes theme.
tools,footer, the resize grips and anything they open stay in the page's theme, so the controls that drive a specimen always look like the page around them. resizableadds a grip per axis, 160–1280 × 96–960 px by default, that works by pointer and by keyboard (arrows step 8 px, Shift+arrow 64 px). Pass{ axis, minWidth, maxWidth, minHeight, maxHeight }to narrow it. A resizable Preview fills its container's width.- It is a client component (
"use client").
Inspector — the layout of a settings panel
From @cueplusplus/ui/chrome. Titled sections of label-and-control rows, then a footer — the shape
of a properties sidebar or a wrench menu. The controls are the library's own.
import { Button, Input, SegmentedControl } from "@cueplusplus/ui";
import { Inspector } from "@cueplusplus/ui/chrome";
const sizes = [
{ value: "sm", label: "Small" },
{ value: "md", label: "Medium" },
{ value: "lg", label: "Large" },
];
export function ButtonInspector() {
return (
<Inspector.Root>
<Inspector.Section label="Variants">
<Inspector.Row label="Size">
<SegmentedControl items={sizes} defaultValue="md" />
</Inspector.Row>
<Inspector.Row label="Label" htmlFor="button-label">
<Input id="button-label" defaultValue="Run cue" />
</Inspector.Row>
</Inspector.Section>
<Inspector.Footer note="preset · primary" action={<Button size="sm">Reset</Button>} />
</Inspector.Root>
);
}- A row names its control. With
htmlForthe label is a<label>for that id; without it the row layslabelon the control asaria-label, unless the control already names itself. - A row is one column until the Inspector is 18rem wide, then label beside control. It is a container query, so it follows the popover or sidebar the Inspector sits in, not the viewport.
Inspectoritself has no hooks and no"use client", so a server component can render it.
CodeBlock — code with a caption and a copy button
From @cueplusplus/ui/instruments.
import { CodeBlock } from "@cueplusplus/ui/instruments";
export function ButtonSource() {
return <CodeBlock label="button.tsx" language="jsx" code={'<Button variant="primary">Run cue</Button>'} />;
}codeis printed verbatim, and the copy button copies exactly that.labelis the caption and defaults to the language;languagedefaults to"tsx".- There is no syntax-highlighter palette.
language="jsx"picks out tag names, attribute names and punctuation in three inks the theme already has; every other language prints plain.
The machine-readable half
This package ships its own manifest: @cueplusplus/ui/manifest.json is an index of every
component with a SHA-256 for each companion document, and
@cueplusplus/ui/manifest/components/<name>.json is one component in full — props, variants, the
tokens it paints with, and the hand-written notes on when it is the wrong choice. Beside them,
@cueplusplus/ui/manifest/tokens.json is the token vocabulary and
@cueplusplus/ui/manifest/fixtures.json the playground fixture catalogue. An agent or a code
generator should read those rather than guess a prop name.
The same surfaces are published at https://ui.cueplusplus.com, with the docs site, the live kitchen sink and the CUE++ agent skills beside them.
Where the rest is
- Installing and using it from another project:
docs/CONSUMING.md - Every component, with props and a live example: https://ui.cueplusplus.com/docs/components
- Theming, density and the
--cue-*contract: https://ui.cueplusplus.com/docs/theming - The whole surface on one page: https://ui.cueplusplus.com/kitchen-sink
- Every machine-readable surface, with copy-paste snippets: https://ui.cueplusplus.com/ai
- What changed in the version you have:
node_modules/@cueplusplus/ui/CHANGELOG.md
