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'.

Card.tsx
<Flex d="column" gap
p={4}
padding: 1rem
spacing is ÷4
fontSize={14}
font-size: 0.875rem
type is ÷16, so the number is the pixel size
borderRadius={4}
border-radius: 1rem
the spacing scale again
b={4}
border-width: 4px
a border is measured in pixels
lineHeight={24}
line-height: 24px
so is a line box
bgColor="sky-500"
background-color: var(--sky-500,oklch(68.5% .169 237.3))
a token, so a theme can move it
The same 4 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.
01

The 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 packageWhat it is
AGENTS.mdThe rules, and the block that tells a model its prior is a different library. Read first.
docs/props.mdEvery prop, the values it takes and one example measured from the engine.
docs/components.mdEvery component, its import specifier and whether it renders on a server — read off the built chunks' own exports.
docs/a11y.mdThe behaviour hooks, for a pattern this library does not ship.
docs/anchor.mdWhere a floating layer goes, in CSS and in the measured fallback.
docs/catalog.mdWhat a generated UI may build, and how it renders.
docs/interop.mdThe agentic runtimes, each verified against its own published package.
docs/mcp.mdThe MCP server, and why one tool of it cannot be replaced by a file.
BOX_KITE_AI_CONTEXT.mdThe 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.mdcThat skill as a Cursor rule.

One line, whichever agent you use

Any agent that reads a root AGENTS.md
Terminal
cp node_modules/@box-kite/react/AGENTS.md ./AGENTS.md
As a skill, in roughly forty-five agents
Terminal
npx skills add box-kite/box-kite
As an MCP server, with the engine behind it
Terminal
npx @box-kite/mcp

The 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.

ToolWhat it answers
search_docsThe prop, component, nesting key or rule for what you are building — ranked across all four.
get_propsA prop's whole record, with a numeric prop's scale measured at 1/2/4/8 rather than described.
get_componentOne component's contract: props and defaults, sub-parts, the keyboard map and the ARIA it writes.
get_rulesThe rules the library is written by, as an index or in full.
get_blocksWhole sections the shadcn CLI installs — a data-grid page, a settings form, a dashboard shell.
check_stylesA prop bag through the real engine: the CSS each prop wrote, or why it wrote none.
  • Generated, not written. Every example in docs/props.md is 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.md opens 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.txt indexes them — so an agent browsing the docs gets prose rather than a React shell.
02

A 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.

A class name
<div className="rounded-lg bg-blue-550 p-4">
No error anywhere — not in the editor, not in the build, not at runtime. The element renders with no background, and whoever asked for it finds out by looking.
A typed prop
<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"'?
The compiler's own message, in the editor, before it is ever run. This site's test suite re-runs it rather than trusting what is printed here.
  • 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 a style attribute.
03

A 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.

The constraint and the renderer come out of one description
JSX
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 onIssues rather 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/interop reads 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.
04

What 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.

105
keyboard rows documented and driven, across every component
51
axe fixtures rendered on every commit, over 38 components
0
violations in the ledger, which fails on a listed one that stops firing too

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.

app/page.tsx — no 'use client', no provider, no stylesheet
JSX
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-server entry 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.

1. Install
Terminal
npm install @box-kite/react
2. There is no step two
JSX
import 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.

Licence
MIT
Every component, every version. There is no tier above this one.
Latest
2.2.2
Semver, with a written note for every change a consumer sees.
Runtime dependencies
0
React and React DOM are peers; nothing else ships.
Peer range
React 18 & 19
Both run the whole suite on every commit.