Dev.to AI ๐Ÿค– Ai ๐Ÿ‘ 0 ๐Ÿ“– 7 min read

AI-written styles typecheck. Some still render nothing

Box Kite's MCP server runs your AI assistant's props through the real styling engine mid-task, and names each value that would render nothing. Below: the mistakes it catches, a worked session with every answer pasted fro

AI-written styles typecheck. Some still render nothing

Box Kite's MCP server runs your AI assistant's props through the real styling engine mid-task, and names each value that would render nothing. Below: the mistakes it catches, a worked session with every answer pasted from the server, and what it can't know about your project. It documents @box-kite/mcp 2.2.12.

The examples here are screenshots; they run live in the original article.

The problem

Three props an assistant wrote for a settings row, handed to the server:

check_styles { "props": { "jc": "between", "fontSize": 4, "lineHeight": 1.5 } }

Some of these write no CSS, or CSS they almost certainly did not mean.

- โŒ `jc` does not accept "between" โ€” no rule and no class name were written. It takes start, end, flex-start, flex-end, center, left, right, space-between, space-around, space-evenly, stretch, safe center, โ€ฆ (17 in all โ€” call `get_props`).
- โš ๏ธ `fontSize` โ†’ `.fontSize-4{font-size:0.25rem}` โ€” fontSize={4} is 4px text: fontSize's divider is 16, not the spacing scale's 4, so the number is the pixel size. For 16px write fontSize={16}.
- โš ๏ธ `lineHeight` โ†’ `.lineHeight-1\.5{line-height:1.5px}` โ€” lineHeight={1.5} is a 1.5px line: lineHeight is direct pixels, not the unitless multiple CSS also takes. Write the pixels (lineHeight={24} is 24px), lineHeight="font-size" for a multiple of 1, or css={{ lineHeight: 1.5 }} for any other multiple.
โ€ฆ

And here is what those props draw:

The three props as the engine draws them, each next to what was meant: the row stays bunched, the text is 4px, and the lines overlap.

The two numbers typecheck, the build passes, and nothing fails loudly. In development the engine logs a [box-kite] warning once the component renders. A production build says nothing.

Why a server and not a docs file

Box Kite's prop names look like the utility classes you already type, so an assistant brings its habits along. Three of them fail:

  • Each prop takes a closed set of values. Anything else writes no rule and no class name, so a typo can't become a broken declaration.
  • Numbers mean different units per prop. p={4} is 1rem (รท4), fontSize={14} is 14px (รท16), b={1} and lineHeight={24} are direct pixels.
  • HTML attributes go in props. A top-level href or data-* typechecks and is then dropped.

The package ships a rules file, but an agent reads it once, at the start of a task. It can ask a server at the moment the question comes up. No document can answer "does this exact value work?" either. The server bundles the engine of its own release, and check_styles runs each prop on a fresh engine of its own.

The four moments in a task when an agent asks the server, and the tool for each.

Install

Node 22 or newer. No key, no network. In Claude Code:

claude mcp add box-kite -- npx -y @box-kite/mcp

Cursor, VS Code, Codex, Zed and any other stdio client:

{
  "mcpServers": {
    "box-kite": { "command": "npx", "args": ["-y", "@box-kite/mcp"] }
  }
}

On connect, the server also hands the model the library's four core facts (the dividers, no style, the element comes from the component, attributes in props), plus "before believing a value, call check_styles".

A settings panel, start to finish

1. Find the component.

search_docs { "query": "toggle switch" }

- **Switch** (component) โ€” The APG switch: an on/off control drawn as a track and a thumb.
โ€ฆ

2. Read its contract. get_component returns props, parts, keyboard map and ARIA. Two rows change the code:

get_component { "name": "Switch", "include": ["props", "a11y"] }
โ€ฆ
| `label` | `React.ReactNode` | โ€” | The text beside the switch, rendered inside a label that wraps the input. |
| `labelProps` | `BoxProps<'label'>` | โ€” | Styles for the wrapping `<label>`: the row's layout, not the track's own appearance. |
โ€ฆ

So the row needs no Flex and Span of its own. The label names the switch and makes the whole row clickable.

3. The first draft, written from habit:

import Flex from '@box-kite/react/components/flex';
import { H2, Span } from '@box-kite/react/components/semantics';
import Switch from '@box-kite/react/components/switch';

export function NotificationSettings() {
  return (
    <Flex d="column" p={6} rounded="xl" shadow="md" bgColor="white" color="gray-900" borderColor="gray-150">
      <H2 fontSize={5} fontWeight="semibold">
        Notifications
      </H2>
      <Flex jc="between" ai="center" py={3} borderBottom={1} data-setting="email">
        <Span lineHeight={1.5}>Email me about new comments</Span>
        <Switch name="comments" defaultChecked />
      </Flex>
    </Flex>
  );
}

4. Check it, one check_styles call per element. The verdicts, summarized:

Draft Verdict Fix
rounded="xl" โŒ no prop of that name borderRadius={3}
borderColor="gray-150" โŒ not a palette step "gray-200"
fontSize={5} โš ๏ธ 5px text fontSize={20}
fontWeight="semibold" โŒ takes 100 to 900 fontWeight={600}
jc="between" โŒ not accepted jc="space-between"
borderBottom={1} โŒ no prop; suggests borderColor bb={1}
data-setting="email" โš ๏ธ HTML attribute, dropped props={{ 'data-setting': 'email' }}
lineHeight={1.5} โš ๏ธ a 1.5px line lineHeight={20}

One of those answers, as the agent sees it:

check_styles { "props": { "jc": "between", "ai": "center", "py": 3, "borderBottom": 1, "data-setting": "email" } }

Some of these write no CSS, or CSS they almost certainly did not mean.

- โŒ `jc` does not accept "between" โ€” no rule and no class name were written. It takes start, end, flex-start, flex-end, center, left, right, space-between, space-around, space-evenly, stretch, safe center, โ€ฆ (17 in all โ€” call `get_props`).
- โœ… `ai` โ†’ `.ai-center{align-items:center}`
- โœ… `py` โ†’ `.py-3{padding-block:0.75rem}`
- โŒ `borderBottom` no prop of that name. Try: `borderColor`.
- โš ๏ธ `data-setting` an HTML attribute, not a style prop. Written at the top level it typechecks and is then dropped โ€” it goes in `props={{ "data-setting": โ€ฆ }}`.
โ€ฆ

TypeScript catches the โŒ rows, one error per element, so it takes a few rounds. It never catches the โš ๏ธ rows. Two fixes came from elsewhere: search_docs for "rounded corners" returns borderRadius first, but nothing led to bb. Once you know the name, get_props confirms that bb={1} writes border-bottom-width:1px.

5. The second draft. Every call now answers "Every prop writes CSS.":

The first draft as the engine renders it, beside the version that passed check_styles.

import Flex from '@box-kite/react/components/flex';
import { H2 } from '@box-kite/react/components/semantics';
import Switch from '@box-kite/react/components/switch';

const row = {
  width: 'fit',
  d: 'row-reverse',
  jc: 'space-between',
  gap: 3,
  py: 3,
  bb: 1,
  borderColor: 'gray-200',
  fontSize: 14,
  lineHeight: 20,
} as const;

export function NotificationSettings() {
  return (
    <Flex d="column" p={6} borderRadius={3} shadow="md" bgColor="white" color="gray-900" b={1} borderColor="gray-200">
      <H2 fontSize={20} fontWeight={600} mb={2}>
        Notifications
      </H2>
      <Switch name="comments" defaultChecked label="Email me about new comments" labelProps={row} props={{ 'data-setting': 'email' }} />
      <Switch name="digest" label="Weekly digest" labelProps={row} props={{ 'data-setting': 'digest' }} />
    </Flex>
  );
}

d: 'row-reverse' puts the text first, because the label renders the input before it.

To skip the typing altogether, get_blocks has a ready settings panel: get_blocks { "name": "settings-form" } answers with npx shadcn@latest add https://www.box-kite.dev/r/settings-form.json.

Field notes from a production app

From a team running the server on a real app, each checked against the source.

Pin the server next to the library. Each release builds @box-kite/mcp from the same commit, at the same version, with that release's engine inlined. npx -y @box-kite/mcp runs whatever version npx resolves, not the one in your lockfile. So the team installed it as a dev dependency at the library's version (npm install -D @box-kite/[email protected]) and pointed the project's .mcp.json at its bin:

{
  "mcpServers": {
    "box-kite": { "command": "npx", "args": ["box-kite-mcp"] }
  }
}

Your own tokens look wrong to it. The app's colours came from Box.extend():

check_styles { "props": { "bgColor": "ui-primary/20", "borderColor": "ui-primary" } }

Some of these write no CSS, or CSS they almost certainly did not mean.

- โŒ `bgColor` does not accept "ui-primary/20" โ€” no rule and no class name were written. It takes currentColor, transparent, green, red, blue, gray, black, white, vi, slate-50, slate-100, slate-200, โ€ฆ (326 in all โ€” call `get_props`).
โ€ฆ

The server can't load your configuration. It reads no config file, environment variable or argument, so read its rejection of your own token as "unknown", not "wrong". What still works:

  • Every other prop in the bag is judged on its own, so those verdicts stand.
  • Your app's engine knows your tokens. With Box.extend({ 'ui-primary': '#3355ff' }, {}, {}), bgColor="ui-primary/20" renders with no warning, and the typo ui-primry/20 gets a [box-kite] โ€ฆ wrote no CSS warning in development. For your own tokens, the console is the check.
  • A design that only recolours the components can use Box.palette(). Its accent-* and base-* tokens are built in, and check_styles accepts them.

get_blocks goes through the shadcn CLI. The server installs nothing. The CLI copies a block's files into your project as code you own. That suits starting a new page, not editing an existing one. It needs a components.json (setup on the registry page), and installed blocks don't update.

The honest limits

  • Values, not layouts. jc="space-between" passes whether or not the element is a flex container.
  • Not your project. Your Box.extend() tokens and props and your Box.components() designs are invisible to it.
  • Keyword search. "on/off toggle" didn't find Switch, and "border bottom" never found bb. Searching by the CSS property name works best.
  • Gaps in coverage. VisuallyHidden and the SVG elements have no get_component entry, and the tool says so.
  • It can't make the agent ask. Ask for check_styles in your prompt or your rules file.

Try it

Built for AI runs check_styles in your browser on the same engine, before you install anything. Then:

claude mcp add box-kite -- npx -y @box-kite/mcp

Originally published on box-kite.dev. Box Kite is open source under MIT โ€” if it is useful to you, a star on GitHub helps other people find it. Install it with npm install @box-kite/react.

๐Ÿ“ฐ Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes โ€” full credit and traffic to the original publisher.