Appearance

A design for the whole site, in light or dark. Kept on this device.

Design
Mode

Box Kite 2.4.0

7 November 2026
What changed in this version, written as it landed. The same notes are the body of the GitHub release.

7 November 2026 · npm · Compare v2.3.1...v2.4.0

Box Kite now installs nothing but its own engine and asks for React 18 or newer, which is what its components already needed. The rules an AI agent loads are a fifth of the size, and six reported bugs are fixed, among them a system theme that would not switch to dark and a Combobox that screen readers named after its placeholder.

Box Kite now needs React 18 or newer

The peer range was React 16.14 and newer, but the DataGrid, the date controls, Popover, Dialog, Menu and Toaster already needed React 18, and CI only ever tested 18 and 19. The peer range is now >=18.0.0, which is what was really supported. No API changed, so this is not a major. But an app on React 16 or 17 that used only Box and the simpler components did work before, and npm will now refuse to install this version alongside it.

On React 16 or 17: stay on 2.3.x, or upgrade React to 18 or newer first.

Box Kite installs no third-party packages

@box-kite/react now depends on @box-kite/core and nothing else. The css prop's types used to come from the csstype package; they are now a generated copy inside @box-kite/core, so css={{ … }} is typed exactly as before and nothing extra is installed. The use-sync-external-store shim is gone too: the components use React's own useSyncExternalStore.

Terminal
npm view @box-kite/react dependencies
# { '@box-kite/core': '<this version>' }

The rules your agent loads are a fifth of the size

The rules file, the skill and the Cursor rule that ship in the package were a 90 KB wall loaded on every request that touched a component, and the rules that prevent most mistakes were buried in it. Each rule is now what to write and the one trap, about 19 KB in all, with the same numbers. Nothing was dropped: the reasons, measurements and edge cases behind each rule moved to references/rules.md in the skill, under the same number, and get_rules in @box-kite/mcp reads that long form.

FileBeforeAfter
.claude/rules/box-kite-rules.md89 KB19 KB
.cursor/rules/box-kite.mdc91 KB20 KB
.claude/skills/box-kite/SKILL.md97 KB27 KB

If you copied the Cursor rule or installed the skill, copy or install it again to pick up the short version.

Breaking changes

None.

Fixes

  • @box-kite/core's types pointed at a package it did not install. Its css prop types imported csstype, which the package never listed, so a TypeScript app using the engine without React got a missing-module error (or, with skipLibCheck, a css prop that accepted anything). The types are inside the package now.
  • A <Box.Theme> that started on the system theme ignored a theme given later. Passing theme="dark" after starting with no theme left the page light, because React replayed the provider's own no-op update over the new value. It switches now, and a design prop change after a stored design of the same name is no longer lost the same way.
  • A Combobox was named after its placeholder, and its list after what was typed. An id written in props was replaced by a generated one, so a <label htmlFor> of your own named nothing and the browser fell back to the placeholder. And without a label prop the list was labelled by the field, which reads out the field's text. The id is kept now, and the list carries the field's name: the label, your aria-labelledby, your aria-label or the text of your own <label>.
  • Restyling every Textbox reached the DataGrid's page number. Box.components({ textbox: { styles: { width: 'fit' } } }) stretched the page-number field and wrapped the pagination bar. The field sets its own width now.
  • A Skeleton could not be taller than one line. <Skeleton height={40} /> drew one thin bar at the top. With one line the bar fills the skeleton now, so a height makes a block.
  • A horizontal RadioGroup put its label in the row with the radios. The label has a line of its own now, and the radios wrap when the row runs out of room.
  • A colour of your own refused the opacity modifier in TypeScript. bgColor="ui-primary/35" rendered correctly but was a type error for a colour added through Box.extend() and declared in Augmented.BoxPropTypes. Every colour prop that takes blue-500/40 now takes your colour with a modifier too.
  • Docs. A custom DataGrid Cell is drawn edge to edge with no padding (the built-in text pads itself px={3}), which was never written down. The Grid example used a gridCols prop that does not exist (gridTemplateColumns), and Textbox, Textarea, Combobox and TimeZoneSelect said an id goes in props, where the types refuse it: it is the top-level prop.