The React library your AI already knows.Every CSS property is a typed prop.
The instructions an agent needs ship inside the package, generated from the engine itself. 235 props the compiler checks catch what it still gets wrong. 38 components arrive with the keyboard and the ARIA already in them. No stylesheet, no build step, no 'use client'.
<Flex d="column" gapp={4}padding: 1remspacing is ÷4fontSize={14}font-size: 0.875remtype is ÷16, so the number is the pixel sizeborderRadius={4}border-radius: 1remthe spacing scale againb={4}border-width: 4pxa border is measured in pixelslineHeight={24}line-height: 24pxso is a line boxbgColor="sky-500"background-color: var(--sky-500,oklch(68.5% .169 237.3))a token, so a theme can move it4 is a rem here and a pixel there, because a padding and a border are not measured the same way. The editor knows which is which, and so does anything reading the same types.An agent writes it correctly
And the compiler catches the rest
A model can compose the UI at runtime
What it writes is finished
01The instructions are inside the package
A model writing against a library it has not seen guesses, and a guess that compiles is the expensive kind. So the answers travel with the code: installing @box-kite/react installs the rules, the whole prop reference and a docs folder beside them. Every file is generated at build time from the prop registry, the component types or the rules file — a stale instruction file is worse than none, because an agent trusts it over its priors.
| In the package | What it is |
|---|---|
AGENTS.md | The rules, and the block that tells a model its prior is a different library. Read first. |
docs/props.md | Every prop, the values it takes and one example measured from the engine. |
docs/components.md | Every component, its import specifier and whether it renders on a server — read off the built chunks' own exports. |
docs/a11y.md | The behaviour hooks, for a pattern this library does not ship. |
docs/anchor.md | Where a floating layer goes, in CSS and in the measured fallback. |
docs/catalog.md | What a generated UI may build, and how it renders. |
docs/interop.md | The agentic runtimes, each verified against its own published package. |
docs/mcp.md | The MCP server, and why one tool of it cannot be replaced by a file. |
BOX_KITE_AI_CONTEXT.md | The long-form reference: prop tables, the DataGrid API, debugging. |
.claude/skills/box-kite/ | The same rules as an installable skill, with four references beside it. |
.cursor/rules/box-kite.mdc | That skill as a Cursor rule. |
One line, whichever agent you use
cp node_modules/@box-kite/react/AGENTS.md ./AGENTS.mdnpx skills add box-kite/box-kitenpx @box-kite/mcpThe answer a file cannot give
A value this library does not accept writes no CSS and says nothing about having done so — so the one question a reference cannot settle is “did that actually work?”. @box-kite/mcp answers it by running the engine: hand check_styles a prop bag and it reports the CSS each prop wrote, or why it wrote none. Six tools over stdio, with the engine and the generated reference inlined.
| Tool | What it answers |
|---|---|
search_docs | The prop, component, nesting key or rule for what you are building — ranked across all four. |
get_props | A prop's whole record, with a numeric prop's scale measured at 1/2/4/8 rather than described. |
get_component | One component's contract: props and defaults, sub-parts, the keyboard map and the ARIA it writes. |
get_rules | The rules the library is written by, as an index or in full. |
get_blocks | Whole sections the shadcn CLI installs — a data-grid page, a settings form, a dashboard shell. |
check_styles | A prop bag through the real engine: the CSS each prop wrote, or why it wrote none. |
- Generated, not written. Every example in
docs/props.mdis the CSS the engine emitted for it, and CI fails when one drifts — so the reference cannot describe a divider the library no longer uses. - The prior is the problem, and it is addressed first. The prop names collide with other libraries’ while the numbers mean different things, so
AGENTS.mdopens by saying so rather than assuming a model arrives empty-handed. - The site is readable too. Every page here has a markdown mirror, and
llms.txtindexes them — so an agent browsing the docs gets prose rather than a React shell.
02A value the palette does not have is an error
Generated code is only as good as what catches it. Both columns below make the same mistake — the one a model makes most, a plausible value from a neighbouring scale, since no palette family has a 550 step. Only one of them says so.
<div className="rounded-lg bg-blue-550 p-4"><Box bgColor="blue-550" borderRadius={2} p={4} />error TS2820: Type '"blue-550"' is not assignable to type (every colour token in the palette). Did you mean '"blue-50"'?- It suggests the fix. The diagnostic ends in
Did you mean "blue-50"?— which is a repair an agent can apply on its own, from a message no string could have produced. - Nothing merges. Two components setting the same prop resolve by prop, last one wins — so there is no class-string arithmetic and no specificity to hold in your head.
- The class is shared.
p={4}is one rule however many components write it, generated the first time it is used and reused after that. - The 5% has a door. A property with no prop goes in
css={{ … }}, which still compiles to a shared class — never astyleattribute.
03A model composes it, your app still owns it
The other half of writing code with a model is letting one build the interface at runtime. catalog() describes every component and every value its props take as JSON Schema — read off the live prop registry, so a prop you added with Box.extend() is in it with nothing regenerated. specSchema() turns that into the one schema a model generates a whole tree under, and <SpecRenderer> renders what came back against an allow-list your app builds.
import { catalog, specSchema } from '@box-kite/react/catalog';
import { SpecRenderer, createSpecRegistry } from '@box-kite/react/spec';
const registry = createSpecRegistry({ catalog: catalog(), components: { Flex, H2, Sparkline } });
// On the server: what the model is allowed to generate.
const schema = specSchema(registry);
// In the app: what came back, rendered against the same description.
<SpecRenderer spec={spec} registry={registry} data={data} onAction={run} onIssues={log} />;- A colour stays a token. A colour prop is a pattern over the palette in the schema, which is the one thing a generated tree cannot get around — so a model cannot invent a brand colour into your app.
- Unknown names render nothing. A component the registry does not hold, a prop its schema refuses, an event the catalog does not list: each is dropped and reported through
onIssuesrather than thrown or silently obeyed. - A stream is handled as a stream. A spec arrives in pieces, so a half-written value is held back rather than rendered, and every node has an error boundary of its own — a component that throws on invented props costs that node and nothing around it.
- The runtimes are already mapped.
@box-kite/react/interopreads a tool call from AI SDK, assistant-ui, CopilotKit, AG-UI and A2UI into one vocabulary — and imports none of them, so the adapter does not choose a runtime for your app.
04What comes out is finished, not scaffolding
Code a model wrote is worth what it does for the person using it. Every pre-built component ships with its roles, its ARIA and its keyboard map, and the two numbers beside each one come out of the generated reference: how many keyboard rows that component documents, and how many fixtures the axe sweep renders it in.
| Component | W3C pattern | Keyboard rows | Axe fixtures |
|---|---|---|---|
| Dropdown | combobox | 18 | 3 |
| DataGrid | grid | 16 | 6 |
| Menu | menu-button | 7 | 1 |
| Tooltip | tooltip | 6 | 2 |
| Slider | slider | 5 | 1 |
| Tabs | tabs | 5 | 3 |
| Accordion | accordion | 4 | 1 |
| RadioGroup | radio | 4 | 1 |
| Switch | switch | 3 | 1 |
| Checkbox | checkbox | 2 | 2 |
It renders in a Server Component, with nothing added
A Server Component cannot inject styles: there is no effect to run and no document to write to. The react-server condition resolves to a build of Box that calls no hook and touches no DOM, and the rules it needs come back as <style href precedence> elements React 19 hoists and dedupes. Importing the package is the whole setup — which is also why generated code needs no 'use client' added to it after the fact.
import Flex from '@box-kite/react/components/flex';
import { H1, P } from '@box-kite/react/components/semantics';
export default function Page() {
return (
<Flex d="column" gap={2} p={6} borderRadius={2} bgColor="slate-50" theme={{ dark: { bgColor: 'slate-900' } }}>
<H1 fontSize={24} fontWeight={600}>Rendered on the server</H1>
<P color="slate-600" hover={{ color: 'sky-500' }}>Pseudo-classes, breakpoints and themes are just more rules.</P>
</Flex>
);
}- The boundary is a check, not a promise. CI fails if the
react-serverentry reaches a client hook, or if the engine itself imports React. - The class names match. They are content hashes, so the class the server resolved is the class the browser bundle resolves — there is no hydration mismatch to configure away.
- This site is the proof. Every page here is prerendered in Node through the library’s own
getStyles(), and a Next.js App Router example is built and smoke-tested on every commit.
And the data grid is in the box
Sorting, multi-column grouping with aggregation, row virtualization over 100,000 rows, tree data, a server-side row model, range selection with clipboard paste, cell editing and a styled XLSX export. MIT, one tier, in the same package as everything else, taking Box props like everything else — so a grid a model generated is the grid you would have written.
Two dependencies and no configuration
There is no plugin to register, no content globs to keep in step and nothing to import into your entry file.
npm install @box-kite/reactimport Box from '@box-kite/react';
export default function App() {
return (
<Box p={4} bgColor="indigo-500" color="white" borderRadius={2}>
Hello, Box Kite!
</Box>
);
}New to web development? Build a whole page step by step in Your first web page with Box Kite.