Skip to content

The UI kit

@localixai/kit is the one set of controls, overlays, rows, states, class recipes and design tokens that the Localix workbench and every mini app view are built from. Use this page to find the module for a job, learn the shared size and tone vocabulary, and add to the kit without forking it.

Terminology

TermMeaningBoundary
KitThe source package packages/kit, imported as @localixai/kit/<id>.Presentation only: no network, no app state, no feature names.
DoorOne public module, packages/kit/src/entries/<id>.ts: an explicit re-export list.Anything not re-exported by a door is private to the kit.
Lent moduleA module the app serves under the same namespace, apps/ui/src/app/kit/<id>.ts.Bound to a host feature (the code editor, Markdown, tool cards).
CatalogEvery module with the job it is for, in packages/kit/src/catalog/.Tests pin it to the doors and lent modules in both directions.
KIT.mdThe catalog rendered as Markdown, written into a user mini app by every build.Generated; an edit is overwritten on the next build.
Control ladderThe heights xs 20, sm 24, md 28, lg 32, xl 36 px.A size name means the same height on every primitive.

What you can do

TaskDo thisBoundary
Find the module for a jobLook the job up in the catalog (below, or KIT.md in a user mini app's folder)Build nothing the kit has; the house-style check reports drift.
Import a primitive in the appimport { Button } from '@localixai/kit/button'Never @/shared/ui/… for a primitive: it lives in the kit now.
Import a primitive in a mini appThe same specifier; the build takes the module from the running hostOne instance: a mini app never bundles its own copy of the kit.
Size a controlsize="sm" — a rung nameNever a height in className.
Mark an action destructivetone="danger" (IconButton), variant="danger" (Button)danger is for what a click does, never for a state.
Suggest the next steprecommended on the one Button that is the safe, expected next actionNever on a permission grant, a destructive act, or a busy run.
Show a stateA status tone: neutral, info, success, warning, errorerror is for what went wrong, never for a button.
Colour anythingA token utility (text-text-soft, bg-surface-hover, --chart-N)No palette class, no hex; see Visual system.
Add a primitiveFollow packages/kit/AGENTS.md: file, door, catalog entry, testA door without a catalog entry fails the kit's tests.
Use a host feature in a mini app viewA lent module: @localixai/kit/code-editor, markdown, diff, tool-cardLent modules keep the host feature's behaviour and state.
Check a view like the build doesbun test scripts/ui-token-drift-guard.test.ts in the repositoryUser mini apps get the same rules from mini-app check's style step.

Choose by job

The full catalog has about a hundred modules; these are the jobs that are most often hand-rolled.

JobModule
A command with a label / with an iconbutton / icon-button
A text or number fieldinput inside field; textarea; search-input for filtering
Structured prose with formattingrich-text: RichTextEditor inside field; rich-text-view for viewing
PDF pages matching a downloadpdf-pages: PdfPages with a PDF Blob; fit-to-pane pages and selectable text
Visual template choicesselection-tile with an actual document-thumbnail
A value in a form saved with othersselect (a few) / combobox (many, searchable)
A pick in chrome (a mode, an agent)menu-entries rows with selected in a dropdown-menu
Who plays a game's other sideopponent-menu: OpponentMenu, the chat agent or a decision model
A game's status row and what stopped itgame-status: GameControlRow, GameStatus, GameProblem over the board
Switching what a pane shows (2–4 views)@localixai/mini-app-sdk/ui/segmented-control
A status tone on a menu row's icontonedIcon from @localixai/mini-app-sdk/ui
An agent or person in a menu rowmonogramIcon from @localixai/kit/avatar
An on/off setting / a filter chipswitch / toggle-pill; checkbox inside forms
A page or section heading with actionssection-heading: SectionHeading (level page or section, as h1–h3)
A tag or category that opens somethingchip: ChipButton, the pill of Chip as a button
A label that heads a groupsection-label: SectionLabel with as="h3"
A pane header, toolbar, status rowpanel-header, workbench-toolbar, workbench-statusbar
Tabs inside a panetabs (TabBar, TabStrip)
A long navigation pathworkbench-breadcrumb with scrollable and scrollToEndKey
A navigable row, a treeworkbench-list-row with tree-navigation
Metadata that gives way to row actionsWorkbenchListRowActions with visibility="hover" and fallback
A table of recordsTableGrid from @localixai/mini-app-sdk/ui/components
Nothing to show yet / a failureempty-hint (one line), state-notice (centred, with actions)
A welcome screen that stands for Localixempty-hint: LocalixBird with the host's bird artwork as src
Progress, a live dot, a count badgeprogress-bar, pulsing-dot, chip
"Copied", "Nothing to undo" at a controlaction-feedback (transient, never the journal)
A dialog, a confirmationdialog (its size is the width), confirm-dialog
A menu under a button and on right-clickmenu-entries over dropdown-menu and context-menu
A menu of choices that need explainingmenu-entries rows with description (a line under the label)
One choice among several in a menumenu-entries rows with selected (icon kept, check at the end)
Several on/off choices in a menumenu-entries rows with checked (drawn like selected)
A pick that should not close the menumenu-entries rows with keepOpen (a submenu hint shows it)
An inline action beside a menu choicemenu-entries rows with trailingAction (independent, stays open)
A resizable splitpane-resize with WorkbenchSash from button
A chart@localixai/mini-app-sdk/ui/charts
A graph or a boardcanvas

How it works

Use startResizeSession from @localixai/kit/pane-resize for custom resize handles. It owns the pointer or mouse listeners, a temporary shield above embedded content, and one movement update per animation frame. Supply onMove, onEnd and onCancel; call the returned cleanup when the handle unmounts. Cancellation restores the caller's original size, while release commits the final movement.

Use ClaudeMark, CodexMark, GeminiMark, CopilotMark, OpenCodeMark and HermesMark from @localixai/kit/coding-agent-marks for coding agent identities. These monochrome SVGs inherit the surrounding text colour. Menus, session rows and resource references share the same marks.

One package, two consumers

The primitives live in one package rather than inside the app, because a facade of re-exports would make "the kit" a list the app happened to maintain: the app would import internals the facade did not list, and nothing would stop a primitive from reading app state. As a package, its boundary is checked like any package's — scripts/check-architecture.mjs refuses a kit file that imports the app — and the app imports the same doors a mini app does. What the app sees is what a mini app gets.

The package is source only. The app's Vite build compiles it with the app, React Compiler included, and a mini app's artifact build turns each @localixai/kit/<id> import into a load of the host's module, so the host, the app and every mini app share one instance of each primitive and of the libraries behind them (Radix, cmdk, the flow canvas). The registry of those modules, apps/ui/src/app/miniAppSharedModules.ts, is built from the doors and the lent modules; its test refuses an id served by both.

The kit owns the design system

The tokens and style layers are in packages/kit/styles/, in the cascade order packages/kit/styles/index.css states. The app's stylesheet imports it with one line, and the mini app kit stages it so a mini app's utilities are compiled against the same theme. The rules that keep a surface on that system — no palette colour, no literal duration, the radius scale, one focus recipe, no pane-sized ground — are the ones Visual system lists; each is a check that scans the kit as well as the app, and the authoring plugin's style check applies them to a mini app.

One vocabulary for every primitive

Three axes carry the same names everywhere, so a caller never has to look up which word a component chose:

AxisValuesMeaning
Sizexs, sm, md, lg, xlA rung of the control ladder; md is 28px everywhere.
Action tonedefault, dangerWhat a click does. Buttons, icon actions, menu items.
Status toneneutral, info, success, warning, errorWhat state a thing is in. Chips, notices, dots, badges.

One look for a menu row that can be chosen

A described menu has two kinds of choice row: selected for one choice among several (announced as menuitemradio) and checked for a real multi-select or an on/off toggle (announced as menuitemcheckbox). Both are drawn the same way — the row's icon in the leading slot, the check at the END of the row, and on an unchosen row that end slot kept empty so hints still line up.

A checkbox row that put its check in the leading slot would take the icon's place: a filter menu of status dots or severities would show bare names while a single-choice menu beside it kept its icons. Only the role tells the two kinds apart; pick by what the choice means, never by how it should look.

Headings and chips

SectionHeading is the one title block: a heading, an optional description and optional actions on the title's line. level is how large it is drawn (page is 1.25rem, section 1rem) and as is the element (h1, h2 or h3), so a pane title can be an h1 at section size. With actions it is its own container and reflows on its own width, so its parent is a block or a column flex; without actions it sits anywhere. The Store and Settings open every page with it.

ChipButton is the pill of Chip as a native button with the focus ring: a tag that opens what else carries it, a category to browse. It holds no pressed state (that is TogglePill) and is not the glass pill of the composer bar (ComposerChip). SectionLabel takes as="h2"–"h4" when the label is the heading of the group under it.

The catalog is the answer to "which module?"

A list of exports and props does not say which module a job needs. The mini app authoring agent was once given the props of sixteen controls and nothing else, and it hand-rolled tabs, a checkbox and a progress bar the kit already had. The catalog states each module's job and what not to confuse it with. The mini app kit renders it into KIT.md, every mini-app check writes that file into the mini app's folder the way it writes tsconfig.json, and the mini-app create instruction tells the agent to choose there before writing a control.

Configuration

WhereOwns
packages/kit/src/entries/The doors: what @localixai/kit/<id> exports.
packages/kit/src/catalog/The catalog and its completeness test.
packages/kit/styles/Tokens, themes, window material, motion, utilities, accessibility.
packages/kit/AGENTS.mdThe rules for changing the kit.
apps/ui/src/app/kit/The modules the host lends, and the recorded debt among them.
apps/ui/src/app/miniAppSharedModules.tsThe runtime registry mini apps load the kit from.
scripts/stage-mini-app-kit.mjsWhat each release publishes to build mini apps, KIT.md included.
packages/mini-app-sdk/mini-app.api.mdThe mini app contract: every door's declarations, checked in CI.

Limits

  • The kit's doors list only the names something imports today. A new use of an internal name is a door edit, which is the review point, not a defect.
  • Fourteen modules are still lent by the host because they carry host features: Markdown, the code editor, diffs, code blocks, comments, tool cards and a few others. They keep the host's behaviour; moving them into the kit needs a port for the host service each one reads.
  • The checks read class strings and source; a class assembled at runtime from variables passes unchecked.
  • Build workbench UI — the artifact, the authoring plugin's create command and mini-app check.
  • Visual system — the paint contract and the checks behind it.

Source of truth

Loading content can reserve its geometry with Skeleton from @localixai/kit/skeleton. The enclosing region owns its accessible status and aria-busy; individual shapes are decorative.