@box-kite/react 2.3.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/box-kite-rules.md +3 -1
- package/.claude/skills/box-kite/SKILL.md +16 -14
- package/.claude/skills/box-kite/references/styling.md +2 -2
- package/.cursor/rules/box-kite.mdc +4 -2
- package/AGENTS.md +4 -2
- package/BOX_AI_CONTEXT.md +43 -40
- package/BOX_KITE_AI_CONTEXT.md +43 -40
- package/README.md +13 -7
- package/catalog.cjs +1 -1
- package/catalog.mjs +3 -0
- package/components/dataGrid/components/dataGridFooter.d.ts +6 -2
- package/components/dataGrid/models/gridModel.d.ts +21 -2
- package/components/dataGrid/models/groupRowModel.d.ts +1 -1
- package/components/dataGrid/models/rowModel.d.ts +1 -1
- package/components/dataGrid/models/sourceGroupRowModel.d.ts +5 -1
- package/components/dataGrid/models/sourceRowModel.d.ts +5 -1
- package/components/dataGrid.cjs +3 -3
- package/components/dataGrid.mjs +815 -765
- package/components/sortableList.cjs +1 -1
- package/components/sortableList.mjs +1 -1
- package/components/svg.d.ts +2 -2
- package/components/swipeItem.cjs +1 -1
- package/components/swipeItem.mjs +1 -1
- package/dataGridList.cjs +1 -1
- package/dataGridList.mjs +3 -5
- package/docs/props.md +43 -19
- package/package.json +5 -5
|
@@ -14,7 +14,7 @@ globs: '**/*.{ts,tsx,jsx}'
|
|
|
14
14
|
7. **Border width and lineHeight are direct px**: `b={1}` → 1px. **borderRadius uses divider 4**: `borderRadius={2}` → 8px
|
|
15
15
|
8. **A gradient fill is a value, not an attribute**: `fill="url(#sky)"`, `stroke="var(--chart-1)"`, `clipPath="url(#frame)"` — so it can be themed and hovered. **A dashboard shape is `components/chart`**: `<Sparkline data={[…]}/>`, `<ProgressRing value={0.6}/>`, `<Gauge>`, `<MiniDonut>` — Box props, no chart library
|
|
16
16
|
9. **A CSS variable is a prop too**: `vars={{ 'color-revenue': 'sky-500' }}` declares `--color-revenue` on the element and everything inside it — the answer for markup this library does not render. **A third-party chart goes in `<ChartContainer series={['revenue', 'cost']}>`** (`components/chart`), which declares `--chart-1..6` in both themes and one `--color-<series>` per series, so `<Line stroke="var(--color-revenue)"/>` is all the chart ever says about colour. A record names its own paints, as anything `fill` takes: `series={{ done: 'ui-primary', open: 'ui-primary/35' }}` works with a `Box.extend()` token too.
|
|
17
|
-
10. **SVG lengths have no divider
|
|
17
|
+
10. **SVG lengths have no divider**: `strokeWidth={2}` → `stroke-width: 2` (user units), same for `strokeDasharray`/`strokeDashoffset`, and the geometry props `cx`/`cy`/`r`/`rx`/`ry`/`x`/`y` write the number as `px`, which is a user unit (`r={20}` → `r: 20px`) — Firefox and Safari drop a bare number there. `<Rect width={40} height={40}>` and `<Path d="M…">` take those as the SVG attributes they are — the Box props of those names mean something else
|
|
18
18
|
11. **HTML attributes go in `props` prop**: `<Link props={{ href: '/about' }}>` not `<Link href>`
|
|
19
19
|
12. **Size shortcuts**: `width="fit"` = 100%, `width="fit-screen"` = 100vw, `width="1/2"` = 50%
|
|
20
20
|
13. **An animation is a prop**: `animation="spin|pulse|bounce|ping"` (keyframes included, and they stop under reduced motion on their own), `Box.keyframes({ name: { from, to } })` for your own — its steps are Box props. **Times are milliseconds**: `animationDuration={1100}`, `transitionDelay={150}`. `transition` takes a group (`colors`, `transform`, `opacity`, `shadow`, `size`, `filter`), and `translateX`/`translateY`/`rotate`/`scale` are longhands that compose. **A spring is two props**: `transitionTimingFunction="spring-bouncy"` with `transitionDuration="spring-bouncy"` (also `spring`, `spring-gentle`, `spring-snappy`; `Box.spring({ stiffness, damping })` for your own)
|
|
@@ -99,3 +99,5 @@ Full reference: `src/BOX_KITE_AI_CONTEXT.md` or invoke `/box-kite` skill.
|
|
|
99
99
|
54. **A design is two families and a `Box.components()` object, and it is chosen in CSS.** Every built-in component is drawn in `accent` and `base` — two palette families that start as copies of `indigo` and `gray` — so `vars={Box.palette({ accent: 'orange', base: 'stone' })}` on an ancestor recolours every component inside it, by family name or with a ramp of your own (`{ 500: 'oklch(…)' }`); write `accent-*`/`base-*` in your own components when they should follow, and a named family when the colour is the content. The rest of a design is `Box.components(styles, { design: 'ember' })`: merged over the app's own and applied under `data-design="ember"`, which `<Box.Theme design="classic" designStorageKey="design">` writes on `<html>` and `Box.useDesign()` switches — chosen by an attribute rather than by context, so a pre-paint script that copies the stored name onto `<html>` is all a prerendered page needs, and server and browser render the same classes. **Three things to know.** A design merges and cannot delete a key, so it takes something back off with that prop's `none` (`shadow: 'none'`, `bgGradient: 'none'` — never `bgImage: 'none'`, which loses `background-image` to `bgGradient`). A design nested inside another stops the outer one, but `vars` inherit, so a `data-design="classic"` preview inside an Ember page redeclares its own `Box.palette()`. And a Box under `<Box.Components value>` resolves against that registry and no named design — `Box.createComponents()` is still the answer for a subtree whose design React decides. A part extends like a component does (`trigger: { extends: 'button' }`, `'dialog.title'`), and `letterSpacing` takes the tracking names `tighter`…`widest` in `em`.
|
|
100
100
|
|
|
101
101
|
55. **Every component is drawn at one of three sizes — `compact`, `normal` (the default) and `large` — and a size is two CSS variables.** A field or a button (`Button`, `Textbox`, `Dropdown`/`Select`, `Combobox`/`TimeZoneSelect`, `DateField`/`DatePicker`) is `height="control"` and `fontSize="control"`: 32, 40 or 48px tall, 13, 14 or 16px text. An item or a row (a menu item, a dropdown option, a tab, an accordion header, a calendar day, a time in a picker) is that height as a `minHeight`, so a long label still wraps. A checkbox, a radio, a switch and a slider scale whole, and a progress bar changes its thickness. **`variant="compact"` or `variant="large"` on any of them sizes it and everything inside it** — `<Menu variant="compact">` makes every item compact, `<DatePicker variant="large">` its calendar too, because the size is `--control-height`/`--control-font-size` and they inherit; `variant="normal"` puts the default back inside a sized subtree. **What the three sizes are is the project's, declared once**: `vars={Box.sizes({ compact: 8, normal: 9, large: 11 })}` on the app's root (a number is the ÷4 scale for the height, `{ height, fontSize }` sets both, a string is CSS) — that line is how a project matches shadcn's 36px — and `vars={Box.sizes({ size: 'compact' })}` draws a whole subtree compact. **Never size one control by hand**: `height={9}` on a single button wins as any prop does, and that is exactly how a row ends up with three heights; padding no longer sizes a control at all (`py` changes nothing, `height="auto"` hands it back). Your own control joins the standard with `height="control"` (`minHeight` where it wraps) and `fontSize="control"`; `'control-compact'`/`'control-normal'`/`'control-large'` name one size outright. `variant="square"` on a `Button` is an icon button as wide as it is tall: `<Flex gap={2}><Textbox …/><Button variant={{ secondary: true, square: true }} props={{ 'aria-label': 'Copy link' }}><Icon size={4}><Copy /></Icon></Button></Flex>`. A `DataGrid` is drawn compact.
|
|
102
|
+
|
|
103
|
+
56. **An SVG shape turns about the viewBox's corner, not its own centre.** Its `transform-box` is `view-box`, so `rotate={45}` alone swings it round `0 0`: write `<G rotate={45} transformBox="fill-box" transformOrigin="center">`. `transformOrigin` takes a keyword (`'top left'`), a fraction or a percentage, a ÷4 number, or positions as CSS writes them (`'48px 48px'` — a px is a user unit). The `transform` attribute stays for a matrix or an angle that is data. Two more props an interactive drawing needs: `pointerEvents="visibleStroke"` makes an edge's stroke its hit area (over a wide transparent one), and `touchAction="none"` on the pressed element keeps a finger's drag from panning the page.
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: box-kite
|
|
3
|
-
description: "@box-kite/react (Box Kite) expert — the runtime CSS-in-JS library whose Box component takes
|
|
3
|
+
description: "@box-kite/react (Box Kite) expert — the runtime CSS-in-JS library whose Box component takes CSS properties as typed props and writes the CSS itself. Use for any work with Box props, the Flex/Grid/Button/Textbox/Dropdown/DataGrid components, themes, Box.extend() or Box.components() — and read it before writing props, because the prop names collide with Tailwind's and Chakra's while the numbers mean different things. Also covers installing and updating the package."
|
|
4
4
|
license: MIT
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# @box-kite/react
|
|
8
8
|
|
|
9
|
-
Runtime CSS-in-JS. `Box` takes
|
|
9
|
+
Runtime CSS-in-JS. `Box` takes CSS properties as typed props and generates the CSS for them at runtime — no stylesheet, no class name to invent, and two elements written with the same value share one class.
|
|
10
10
|
|
|
11
11
|
## This library is not in your training data
|
|
12
12
|
|
|
@@ -21,16 +21,16 @@ It was renamed at 1.0.0 (September 2026) and its prop surface nearly doubled on
|
|
|
21
21
|
|
|
22
22
|
The divider is per prop, so the same number is a different length depending on which prop it is written on — `p={4}` is 16px and `b={4}` is 4px. Every row here is what the engine emitted for that example, not what a doc claims about it:
|
|
23
23
|
|
|
24
|
-
| Prop | Which props | Written | Emitted
|
|
25
|
-
| ------------------- | ---------------------------------------------------------- | -------------------------- |
|
|
26
|
-
| `p` | spacing — `p`/`m`/`gap`/`px`/`py`/`pt`… all of them | `p={4}` | `padding: 1rem`
|
|
27
|
-
| `width` | sizing — `width`/`height` and the four `min`/`max` props | `width={4}` | `width: 1rem`
|
|
28
|
-
| `borderRadius` | the corner radius, on the spacing scale | `borderRadius={4}` | `border-radius: 1rem`
|
|
29
|
-
| `b` | border width — `b`/`bx`/`by`/`bt`/`bs`… | `b={4}` | `border-width: 4px`
|
|
30
|
-
| `fontSize` | the one that is ÷16 | `fontSize={14}` | `font-size: 0.875rem`
|
|
31
|
-
| `lineHeight` | and `letterSpacing` | `lineHeight={24}` | `line-height: 24px`
|
|
32
|
-
| `strokeWidth` | SVG lengths — and the geometry props `cx`/`cy`/`r`/`x`/`y` | `strokeWidth={4}` |
|
|
33
|
-
| `animationDuration` | every animation and transition time | `animationDuration={1100}` | `animation-duration: 1100ms`
|
|
24
|
+
| Prop | Which props | Written | Emitted |
|
|
25
|
+
| ------------------- | ---------------------------------------------------------- | -------------------------- | ---------------------------------------- |
|
|
26
|
+
| `p` | spacing — `p`/`m`/`gap`/`px`/`py`/`pt`… all of them | `p={4}` | `padding: 1rem` |
|
|
27
|
+
| `width` | sizing — `width`/`height` and the four `min`/`max` props | `width={4}` | `width: 1rem` |
|
|
28
|
+
| `borderRadius` | the corner radius, on the spacing scale | `borderRadius={4}` | `border-radius: 1rem` |
|
|
29
|
+
| `b` | border width — `b`/`bx`/`by`/`bt`/`bs`… | `b={4}` | `border-width: 4px` |
|
|
30
|
+
| `fontSize` | the one that is ÷16 | `fontSize={14}` | `font-size: 0.875rem` |
|
|
31
|
+
| `lineHeight` | and `letterSpacing` | `lineHeight={24}` | `line-height: 24px` |
|
|
32
|
+
| `strokeWidth` | SVG lengths — and the geometry props `cx`/`cy`/`r`/`x`/`y` | `strokeWidth={4}` | `--boxStrokeWidth: 4px; stroke-width: 4` |
|
|
33
|
+
| `animationDuration` | every animation and transition time | `animationDuration={1100}` | `animation-duration: 1100ms` |
|
|
34
34
|
|
|
35
35
|
## Read this next
|
|
36
36
|
|
|
@@ -40,7 +40,7 @@ The divider is per prop, so the same number is a different length depending on w
|
|
|
40
40
|
| [`references/components.md`](references/components.md) — Components reference | choosing a component, or wiring a Dropdown, a Select or a DataGrid |
|
|
41
41
|
| [`references/extending.md`](references/extending.md) — Extending Box | a value used twice, or a prop that does not exist yet |
|
|
42
42
|
| [`references/patterns.md`](references/patterns.md) — Patterns and the rest of the surface | server rendering, the behaviour hooks, portals, form controls, the tooltip |
|
|
43
|
-
| `node_modules/@box-kite/react/docs/props.md` | one prop exactly:
|
|
43
|
+
| `node_modules/@box-kite/react/docs/props.md` | one prop exactly: every one, the CSS each writes, one measured example |
|
|
44
44
|
| `node_modules/@box-kite/react/BOX_KITE_AI_CONTEXT.md` | the long-form reference, when a reference above is not enough |
|
|
45
45
|
|
|
46
46
|
With the package not installed, the prop reference is https://www.box-kite.dev/props.md, and https://www.box-kite.dev/llms.txt indexes every documentation page as markdown beside it.
|
|
@@ -87,7 +87,7 @@ Detect the package manager from the lock file rather than asking:
|
|
|
87
87
|
7. **Border width and lineHeight are direct px**: `b={1}` → 1px. **borderRadius uses divider 4**: `borderRadius={2}` → 8px
|
|
88
88
|
8. **A gradient fill is a value, not an attribute**: `fill="url(#sky)"`, `stroke="var(--chart-1)"`, `clipPath="url(#frame)"` — so it can be themed and hovered. **A dashboard shape is `components/chart`**: `<Sparkline data={[…]}/>`, `<ProgressRing value={0.6}/>`, `<Gauge>`, `<MiniDonut>` — Box props, no chart library
|
|
89
89
|
9. **A CSS variable is a prop too**: `vars={{ 'color-revenue': 'sky-500' }}` declares `--color-revenue` on the element and everything inside it — the answer for markup this library does not render. **A third-party chart goes in `<ChartContainer series={['revenue', 'cost']}>`** (`components/chart`), which declares `--chart-1..6` in both themes and one `--color-<series>` per series, so `<Line stroke="var(--color-revenue)"/>` is all the chart ever says about colour. A record names its own paints, as anything `fill` takes: `series={{ done: 'ui-primary', open: 'ui-primary/35' }}` works with a `Box.extend()` token too.
|
|
90
|
-
10. **SVG lengths have no divider
|
|
90
|
+
10. **SVG lengths have no divider**: `strokeWidth={2}` → `stroke-width: 2` (user units), same for `strokeDasharray`/`strokeDashoffset`, and the geometry props `cx`/`cy`/`r`/`rx`/`ry`/`x`/`y` write the number as `px`, which is a user unit (`r={20}` → `r: 20px`) — Firefox and Safari drop a bare number there. `<Rect width={40} height={40}>` and `<Path d="M…">` take those as the SVG attributes they are — the Box props of those names mean something else
|
|
91
91
|
11. **HTML attributes go in `props` prop**: `<Link props={{ href: '/about' }}>` not `<Link href>`
|
|
92
92
|
12. **Size shortcuts**: `width="fit"` = 100%, `width="fit-screen"` = 100vw, `width="1/2"` = 50%
|
|
93
93
|
13. **An animation is a prop**: `animation="spin|pulse|bounce|ping"` (keyframes included, and they stop under reduced motion on their own), `Box.keyframes({ name: { from, to } })` for your own — its steps are Box props. **Times are milliseconds**: `animationDuration={1100}`, `transitionDelay={150}`. `transition` takes a group (`colors`, `transform`, `opacity`, `shadow`, `size`, `filter`), and `translateX`/`translateY`/`rotate`/`scale` are longhands that compose. **A spring is two props**: `transitionTimingFunction="spring-bouncy"` with `transitionDuration="spring-bouncy"` (also `spring`, `spring-gentle`, `spring-snappy`; `Box.spring({ stiffness, damping })` for your own)
|
|
@@ -172,3 +172,5 @@ Full reference: `references/` beside this file, and `docs/props.md` inside the p
|
|
|
172
172
|
54. **A design is two families and a `Box.components()` object, and it is chosen in CSS.** Every built-in component is drawn in `accent` and `base` — two palette families that start as copies of `indigo` and `gray` — so `vars={Box.palette({ accent: 'orange', base: 'stone' })}` on an ancestor recolours every component inside it, by family name or with a ramp of your own (`{ 500: 'oklch(…)' }`); write `accent-*`/`base-*` in your own components when they should follow, and a named family when the colour is the content. The rest of a design is `Box.components(styles, { design: 'ember' })`: merged over the app's own and applied under `data-design="ember"`, which `<Box.Theme design="classic" designStorageKey="design">` writes on `<html>` and `Box.useDesign()` switches — chosen by an attribute rather than by context, so a pre-paint script that copies the stored name onto `<html>` is all a prerendered page needs, and server and browser render the same classes. **Three things to know.** A design merges and cannot delete a key, so it takes something back off with that prop's `none` (`shadow: 'none'`, `bgGradient: 'none'` — never `bgImage: 'none'`, which loses `background-image` to `bgGradient`). A design nested inside another stops the outer one, but `vars` inherit, so a `data-design="classic"` preview inside an Ember page redeclares its own `Box.palette()`. And a Box under `<Box.Components value>` resolves against that registry and no named design — `Box.createComponents()` is still the answer for a subtree whose design React decides. A part extends like a component does (`trigger: { extends: 'button' }`, `'dialog.title'`), and `letterSpacing` takes the tracking names `tighter`…`widest` in `em`.
|
|
173
173
|
|
|
174
174
|
55. **Every component is drawn at one of three sizes — `compact`, `normal` (the default) and `large` — and a size is two CSS variables.** A field or a button (`Button`, `Textbox`, `Dropdown`/`Select`, `Combobox`/`TimeZoneSelect`, `DateField`/`DatePicker`) is `height="control"` and `fontSize="control"`: 32, 40 or 48px tall, 13, 14 or 16px text. An item or a row (a menu item, a dropdown option, a tab, an accordion header, a calendar day, a time in a picker) is that height as a `minHeight`, so a long label still wraps. A checkbox, a radio, a switch and a slider scale whole, and a progress bar changes its thickness. **`variant="compact"` or `variant="large"` on any of them sizes it and everything inside it** — `<Menu variant="compact">` makes every item compact, `<DatePicker variant="large">` its calendar too, because the size is `--control-height`/`--control-font-size` and they inherit; `variant="normal"` puts the default back inside a sized subtree. **What the three sizes are is the project's, declared once**: `vars={Box.sizes({ compact: 8, normal: 9, large: 11 })}` on the app's root (a number is the ÷4 scale for the height, `{ height, fontSize }` sets both, a string is CSS) — that line is how a project matches shadcn's 36px — and `vars={Box.sizes({ size: 'compact' })}` draws a whole subtree compact. **Never size one control by hand**: `height={9}` on a single button wins as any prop does, and that is exactly how a row ends up with three heights; padding no longer sizes a control at all (`py` changes nothing, `height="auto"` hands it back). Your own control joins the standard with `height="control"` (`minHeight` where it wraps) and `fontSize="control"`; `'control-compact'`/`'control-normal'`/`'control-large'` name one size outright. `variant="square"` on a `Button` is an icon button as wide as it is tall: `<Flex gap={2}><Textbox …/><Button variant={{ secondary: true, square: true }} props={{ 'aria-label': 'Copy link' }}><Icon size={4}><Copy /></Icon></Button></Flex>`. A `DataGrid` is drawn compact.
|
|
175
|
+
|
|
176
|
+
56. **An SVG shape turns about the viewBox's corner, not its own centre.** Its `transform-box` is `view-box`, so `rotate={45}` alone swings it round `0 0`: write `<G rotate={45} transformBox="fill-box" transformOrigin="center">`. `transformOrigin` takes a keyword (`'top left'`), a fraction or a percentage, a ÷4 number, or positions as CSS writes them (`'48px 48px'` — a px is a user unit). The `transform` attribute stays for a matrix or an angle that is data. Two more props an interactive drawing needs: `pointerEvents="visibleStroke"` makes an edge's stroke its hit area (over a wide transparent one), and `touchAction="none"` on the pressed element keeps a finger's drag from panning the page.
|
|
@@ -15,9 +15,9 @@ Every prop by category, the six kinds of nesting, and the theme system. Read thi
|
|
|
15
15
|
|
|
16
16
|
**SVG text**: `textAnchor` (`start`/`middle`/`end` — which part of a label sits on its `x`) and `dominantBaseline` (`alphabetic`/`central`/`hanging`/… — which part sits on its `y`). `textAnchor` is inherited; `dominantBaseline` is not, so like `vectorEffect` its rule targets the element _and_ its descendants and still works on the `<svg>`.
|
|
17
17
|
|
|
18
|
-
**SVG geometry** (SVG 2, all **user units — no divider**, or a percentage): `cx`/`cy` (`<circle>`, `<ellipse>`), `r` (`<circle>`), `rx`/`ry` (`<ellipse>` radii, `<rect>` corners; also `auto`), `x`/`y` (`<rect>`, `<image>`, `<use>`, `<foreignObject>`, nested `<svg>` — **not** `<text>`). They are real CSS, so they transition: `<Circle r={38} hover={{ r: 40 }} />` is a whole animation. Not inherited — put them on the shape. A `<rect>`'s `width`/`height` are NOT in this family (those prop names are the ÷4 layout scale), which is why `<Rect>` claims them back as its own attributes — `<Rect width={40} height={40} />`. A path's `d` stays an attribute too (no Safari support), and `<Path d="M…" />` is how you write it.
|
|
18
|
+
**SVG geometry** (SVG 2, all **user units — no divider**, written as `px` because Firefox and Safari drop a bare number; or a percentage): `cx`/`cy` (`<circle>`, `<ellipse>`), `r` (`<circle>`), `rx`/`ry` (`<ellipse>` radii, `<rect>` corners; also `auto`), `x`/`y` (`<rect>`, `<image>`, `<use>`, `<foreignObject>`, nested `<svg>` — **not** `<text>`). They are real CSS, so they transition: `<Circle r={38} hover={{ r: 40 }} />` is a whole animation. Not inherited — put them on the shape. A `<rect>`'s `width`/`height` are NOT in this family (those prop names are the ÷4 layout scale), which is why `<Rect>` claims them back as its own attributes — `<Rect width={40} height={40} />`. A path's `d` stays an attribute too (no Safari support), and `<Path d="M…" />` is how you write it.
|
|
19
19
|
|
|
20
|
-
**SVG elements** (`@box-kite/react/components/svg`, 20 components, server-safe): `Svg`, `G`, `Defs`, `Path`, `Circle`, `Ellipse`, `Rect`, `Line`, `Polyline`, `Polygon`, `SvgText`, `TSpan`, `LinearGradient`, `RadialGradient`, `Stop`, `ClipPath`, `Mask`, `Use`, `SvgSymbol`, `Marker` — so a drawing never writes `tag`. Each one is a Box and takes every prop above. **Each also settles the names SVG and Box both use, for its own element**: `Path`'s `d` is path data, `Rect`'s `width`/`height` are user units, `SvgText`/`TSpan`'s `x`/`y`/`dx`/`dy` are attributes (CSS geometry does not apply to text), `RadialGradient`'s `cx`/`cy`/`r` are attributes (nor to a gradient) — while `Circle`'s `cx` stays CSS and transitions. `transform` is a prop on every shape and group. `Svg` takes `viewBox`/`preserveAspectRatio`/`width`/`height` as attributes (so no ÷4 layout `width` on it) and a `label` prop: no label means `aria-hidden`, a label means `role="img"` with that name. A paint server is a value, not an attribute: `fill="url(#sky)"`, `clipPath="url(#frame)"` — themed and hoverable like any other paint. A themed gradient stop is `<Stop stopColor="currentColor" color="amber-300" />`. `BaseSvg` is deprecated — it is `Svg` with a 24×24 preset.
|
|
20
|
+
**SVG elements** (`@box-kite/react/components/svg`, 20 components, server-safe): `Svg`, `G`, `Defs`, `Path`, `Circle`, `Ellipse`, `Rect`, `Line`, `Polyline`, `Polygon`, `SvgText`, `TSpan`, `LinearGradient`, `RadialGradient`, `Stop`, `ClipPath`, `Mask`, `Use`, `SvgSymbol`, `Marker` — so a drawing never writes `tag`. Each one is a Box and takes every prop above. **Each also settles the names SVG and Box both use, for its own element**: `Path`'s `d` is path data, `Rect`'s `width`/`height` are user units, `SvgText`/`TSpan`'s `x`/`y`/`dx`/`dy` are attributes (CSS geometry does not apply to text), `RadialGradient`'s `cx`/`cy`/`r` are attributes (nor to a gradient) — while `Circle`'s `cx` stays CSS and transitions. `transform` is a prop on every shape and group, for a matrix or an angle that is data; a fixed turn about a shape's own centre is `rotate={45} transformBox="fill-box" transformOrigin="center"` (an SVG shape's `transform-box` is `view-box`, so `rotate` alone swings it round the viewBox's corner). `pointerEvents="visibleStroke"` makes the stroke the hit area, `touchAction="none"` keeps a finger's drag from panning the page, `paintOrder` takes up to three keywords, and `strokeDasharray="dashed"`/`"dotted"` scale with `strokeWidth`. `Svg` takes `viewBox`/`preserveAspectRatio`/`width`/`height` as attributes (so no ÷4 layout `width` on it) and a `label` prop: no label means `aria-hidden`, a label means `role="img"` with that name. A paint server is a value, not an attribute: `fill="url(#sky)"`, `clipPath="url(#frame)"` — themed and hoverable like any other paint. A themed gradient stop is `<Stop stopColor="currentColor" color="amber-300" />`. `BaseSvg` is deprecated — it is `Svg` with a 24×24 preset.
|
|
21
21
|
|
|
22
22
|
**Icons** (`@box-kite/react/components/icon`, server-safe): `<Icon size={5} color="amber-500" label="Sunny"><Sun /></Icon>` — Box props on an icon somebody else drew, wrapping exactly one element from lucide, Tabler, react-icons, or a raw `<svg>`. It resolves the props to a class and puts that on the icon's own `<svg>`, so it knows no set's API: `size` is the ÷4 scale (`size={6}` is 24px, the default; an icon set's own `size` counts in pixels, so `size={20}` becomes `size={5}`) and it lands in the _class_, where a CSS declaration outranks the `width`/`height` attributes the set writes for itself. `strokeWidth` is likewise the ordinary Box prop, so it can change on hover or at a breakpoint. No `label` means `aria-hidden`, a `label` means `role="img"` — the same rule `Svg` follows. **Prefer `Svg` over wrapping one in `Icon`**: `Svg` takes these props directly. (Wrapping works — `Icon` asks the child which convention it follows and routes `role`/`aria-label` into `props` for a component of ours — but it is a layer nobody needs.) The hook underneath is public — `useClassNames(props)` from the main entry returns `{ className, styles }` for anything Box cannot render (a `motion.div`, a `NavLink`); render `styles` beside the element (it is defined in element mode only). **Beyond lucide**: Iconify's 300k+ icons in 200+ sets reach the same `<Icon>`, the choice being only when the icon becomes markup — paste one icon's SVG (no dependency, server-renders); `unplugin-icons` for a set at build time (`npm i -D unplugin-icons @iconify-json/<set> @svgr/core @svgr/plugin-jsx`, plugin with `{ compiler: 'jsx', jsx: 'react' }`, `/// <reference types="unplugin-icons/types/react" />` in a `.d.ts`, then `import SiGithub from '~icons/simple-icons/github'`); or `@iconify/react` when the name is data — it fetches in the browser, so it is a client component and the server sends no icon. Turbopack runs no unplugin: under Next.js 16 the build-time recipe needs `next build --webpack`.
|
|
23
23
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: @box-kite/react (Box Kite): the runtime CSS-in-JS library whose Box component takes
|
|
2
|
+
description: @box-kite/react (Box Kite): the runtime CSS-in-JS library whose Box component takes CSS properties as typed props. Read before writing Box props, a component, a theme or an extension — the prop names collide with Tailwind and Chakra while the numbers mean different things.
|
|
3
3
|
globs: **/*.tsx,**/*.jsx,**/*.ts
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
@@ -22,7 +22,7 @@ This library is not in your training data: it was renamed at 1.0.0 (September 20
|
|
|
22
22
|
7. **Border width and lineHeight are direct px**: `b={1}` → 1px. **borderRadius uses divider 4**: `borderRadius={2}` → 8px
|
|
23
23
|
8. **A gradient fill is a value, not an attribute**: `fill="url(#sky)"`, `stroke="var(--chart-1)"`, `clipPath="url(#frame)"` — so it can be themed and hovered. **A dashboard shape is `components/chart`**: `<Sparkline data={[…]}/>`, `<ProgressRing value={0.6}/>`, `<Gauge>`, `<MiniDonut>` — Box props, no chart library
|
|
24
24
|
9. **A CSS variable is a prop too**: `vars={{ 'color-revenue': 'sky-500' }}` declares `--color-revenue` on the element and everything inside it — the answer for markup this library does not render. **A third-party chart goes in `<ChartContainer series={['revenue', 'cost']}>`** (`components/chart`), which declares `--chart-1..6` in both themes and one `--color-<series>` per series, so `<Line stroke="var(--color-revenue)"/>` is all the chart ever says about colour. A record names its own paints, as anything `fill` takes: `series={{ done: 'ui-primary', open: 'ui-primary/35' }}` works with a `Box.extend()` token too.
|
|
25
|
-
10. **SVG lengths have no divider
|
|
25
|
+
10. **SVG lengths have no divider**: `strokeWidth={2}` → `stroke-width: 2` (user units), same for `strokeDasharray`/`strokeDashoffset`, and the geometry props `cx`/`cy`/`r`/`rx`/`ry`/`x`/`y` write the number as `px`, which is a user unit (`r={20}` → `r: 20px`) — Firefox and Safari drop a bare number there. `<Rect width={40} height={40}>` and `<Path d="M…">` take those as the SVG attributes they are — the Box props of those names mean something else
|
|
26
26
|
11. **HTML attributes go in `props` prop**: `<Link props={{ href: '/about' }}>` not `<Link href>`
|
|
27
27
|
12. **Size shortcuts**: `width="fit"` = 100%, `width="fit-screen"` = 100vw, `width="1/2"` = 50%
|
|
28
28
|
13. **An animation is a prop**: `animation="spin|pulse|bounce|ping"` (keyframes included, and they stop under reduced motion on their own), `Box.keyframes({ name: { from, to } })` for your own — its steps are Box props. **Times are milliseconds**: `animationDuration={1100}`, `transitionDelay={150}`. `transition` takes a group (`colors`, `transform`, `opacity`, `shadow`, `size`, `filter`), and `translateX`/`translateY`/`rotate`/`scale` are longhands that compose. **A spring is two props**: `transitionTimingFunction="spring-bouncy"` with `transitionDuration="spring-bouncy"` (also `spring`, `spring-gentle`, `spring-snappy`; `Box.spring({ stiffness, damping })` for your own)
|
|
@@ -107,3 +107,5 @@ Full reference: `node_modules/@box-kite/react/docs/props.md` in the project, or
|
|
|
107
107
|
54. **A design is two families and a `Box.components()` object, and it is chosen in CSS.** Every built-in component is drawn in `accent` and `base` — two palette families that start as copies of `indigo` and `gray` — so `vars={Box.palette({ accent: 'orange', base: 'stone' })}` on an ancestor recolours every component inside it, by family name or with a ramp of your own (`{ 500: 'oklch(…)' }`); write `accent-*`/`base-*` in your own components when they should follow, and a named family when the colour is the content. The rest of a design is `Box.components(styles, { design: 'ember' })`: merged over the app's own and applied under `data-design="ember"`, which `<Box.Theme design="classic" designStorageKey="design">` writes on `<html>` and `Box.useDesign()` switches — chosen by an attribute rather than by context, so a pre-paint script that copies the stored name onto `<html>` is all a prerendered page needs, and server and browser render the same classes. **Three things to know.** A design merges and cannot delete a key, so it takes something back off with that prop's `none` (`shadow: 'none'`, `bgGradient: 'none'` — never `bgImage: 'none'`, which loses `background-image` to `bgGradient`). A design nested inside another stops the outer one, but `vars` inherit, so a `data-design="classic"` preview inside an Ember page redeclares its own `Box.palette()`. And a Box under `<Box.Components value>` resolves against that registry and no named design — `Box.createComponents()` is still the answer for a subtree whose design React decides. A part extends like a component does (`trigger: { extends: 'button' }`, `'dialog.title'`), and `letterSpacing` takes the tracking names `tighter`…`widest` in `em`.
|
|
108
108
|
|
|
109
109
|
55. **Every component is drawn at one of three sizes — `compact`, `normal` (the default) and `large` — and a size is two CSS variables.** A field or a button (`Button`, `Textbox`, `Dropdown`/`Select`, `Combobox`/`TimeZoneSelect`, `DateField`/`DatePicker`) is `height="control"` and `fontSize="control"`: 32, 40 or 48px tall, 13, 14 or 16px text. An item or a row (a menu item, a dropdown option, a tab, an accordion header, a calendar day, a time in a picker) is that height as a `minHeight`, so a long label still wraps. A checkbox, a radio, a switch and a slider scale whole, and a progress bar changes its thickness. **`variant="compact"` or `variant="large"` on any of them sizes it and everything inside it** — `<Menu variant="compact">` makes every item compact, `<DatePicker variant="large">` its calendar too, because the size is `--control-height`/`--control-font-size` and they inherit; `variant="normal"` puts the default back inside a sized subtree. **What the three sizes are is the project's, declared once**: `vars={Box.sizes({ compact: 8, normal: 9, large: 11 })}` on the app's root (a number is the ÷4 scale for the height, `{ height, fontSize }` sets both, a string is CSS) — that line is how a project matches shadcn's 36px — and `vars={Box.sizes({ size: 'compact' })}` draws a whole subtree compact. **Never size one control by hand**: `height={9}` on a single button wins as any prop does, and that is exactly how a row ends up with three heights; padding no longer sizes a control at all (`py` changes nothing, `height="auto"` hands it back). Your own control joins the standard with `height="control"` (`minHeight` where it wraps) and `fontSize="control"`; `'control-compact'`/`'control-normal'`/`'control-large'` name one size outright. `variant="square"` on a `Button` is an icon button as wide as it is tall: `<Flex gap={2}><Textbox …/><Button variant={{ secondary: true, square: true }} props={{ 'aria-label': 'Copy link' }}><Icon size={4}><Copy /></Icon></Button></Flex>`. A `DataGrid` is drawn compact.
|
|
110
|
+
|
|
111
|
+
56. **An SVG shape turns about the viewBox's corner, not its own centre.** Its `transform-box` is `view-box`, so `rotate={45}` alone swings it round `0 0`: write `<G rotate={45} transformBox="fill-box" transformOrigin="center">`. `transformOrigin` takes a keyword (`'top left'`), a fraction or a percentage, a ÷4 number, or positions as CSS writes them (`'48px 48px'` — a px is a user unit). The `transform` attribute stays for a matrix or an angle that is data. Two more props an interactive drawing needs: `pointerEvents="visibleStroke"` makes an edge's stroke its hit area (over a wide transparent one), and `touchAction="none"` on the pressed element keeps a finger's drag from panning the page.
|
package/AGENTS.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @box-kite/react — read this before writing any of it
|
|
2
2
|
|
|
3
|
-
**This library is not in your training data.** It was renamed at 1.0.0 (September 2026) and its prop surface nearly doubled on the way, so a plausible-looking memory of it is a memory of a different library. Its prop names collide with Tailwind's and Chakra's while the numbers mean different things, which is the failure mode to expect: code that compiles and is laid out wrong. Read the rules below, then `docs/props.md` for the
|
|
3
|
+
**This library is not in your training data.** It was renamed at 1.0.0 (September 2026) and its prop surface nearly doubled on the way, so a plausible-looking memory of it is a memory of a different library. Its prop names collide with Tailwind's and Chakra's while the numbers mean different things, which is the failure mode to expect: code that compiles and is laid out wrong. Read the rules below, then `docs/props.md` for the 238 props themselves.
|
|
4
4
|
|
|
5
5
|
## Where the files are
|
|
6
6
|
|
|
@@ -54,7 +54,7 @@ Every value there is a prop, and every prop value becomes one class the engine i
|
|
|
54
54
|
7. **Border width and lineHeight are direct px**: `b={1}` → 1px. **borderRadius uses divider 4**: `borderRadius={2}` → 8px
|
|
55
55
|
8. **A gradient fill is a value, not an attribute**: `fill="url(#sky)"`, `stroke="var(--chart-1)"`, `clipPath="url(#frame)"` — so it can be themed and hovered. **A dashboard shape is `components/chart`**: `<Sparkline data={[…]}/>`, `<ProgressRing value={0.6}/>`, `<Gauge>`, `<MiniDonut>` — Box props, no chart library
|
|
56
56
|
9. **A CSS variable is a prop too**: `vars={{ 'color-revenue': 'sky-500' }}` declares `--color-revenue` on the element and everything inside it — the answer for markup this library does not render. **A third-party chart goes in `<ChartContainer series={['revenue', 'cost']}>`** (`components/chart`), which declares `--chart-1..6` in both themes and one `--color-<series>` per series, so `<Line stroke="var(--color-revenue)"/>` is all the chart ever says about colour. A record names its own paints, as anything `fill` takes: `series={{ done: 'ui-primary', open: 'ui-primary/35' }}` works with a `Box.extend()` token too.
|
|
57
|
-
10. **SVG lengths have no divider
|
|
57
|
+
10. **SVG lengths have no divider**: `strokeWidth={2}` → `stroke-width: 2` (user units), same for `strokeDasharray`/`strokeDashoffset`, and the geometry props `cx`/`cy`/`r`/`rx`/`ry`/`x`/`y` write the number as `px`, which is a user unit (`r={20}` → `r: 20px`) — Firefox and Safari drop a bare number there. `<Rect width={40} height={40}>` and `<Path d="M…">` take those as the SVG attributes they are — the Box props of those names mean something else
|
|
58
58
|
11. **HTML attributes go in `props` prop**: `<Link props={{ href: '/about' }}>` not `<Link href>`
|
|
59
59
|
12. **Size shortcuts**: `width="fit"` = 100%, `width="fit-screen"` = 100vw, `width="1/2"` = 50%
|
|
60
60
|
13. **An animation is a prop**: `animation="spin|pulse|bounce|ping"` (keyframes included, and they stop under reduced motion on their own), `Box.keyframes({ name: { from, to } })` for your own — its steps are Box props. **Times are milliseconds**: `animationDuration={1100}`, `transitionDelay={150}`. `transition` takes a group (`colors`, `transform`, `opacity`, `shadow`, `size`, `filter`), and `translateX`/`translateY`/`rotate`/`scale` are longhands that compose. **A spring is two props**: `transitionTimingFunction="spring-bouncy"` with `transitionDuration="spring-bouncy"` (also `spring`, `spring-gentle`, `spring-snappy`; `Box.spring({ stiffness, damping })` for your own)
|
|
@@ -139,3 +139,5 @@ Full reference: `docs/props.md` and `BOX_KITE_AI_CONTEXT.md`, in this package.
|
|
|
139
139
|
54. **A design is two families and a `Box.components()` object, and it is chosen in CSS.** Every built-in component is drawn in `accent` and `base` — two palette families that start as copies of `indigo` and `gray` — so `vars={Box.palette({ accent: 'orange', base: 'stone' })}` on an ancestor recolours every component inside it, by family name or with a ramp of your own (`{ 500: 'oklch(…)' }`); write `accent-*`/`base-*` in your own components when they should follow, and a named family when the colour is the content. The rest of a design is `Box.components(styles, { design: 'ember' })`: merged over the app's own and applied under `data-design="ember"`, which `<Box.Theme design="classic" designStorageKey="design">` writes on `<html>` and `Box.useDesign()` switches — chosen by an attribute rather than by context, so a pre-paint script that copies the stored name onto `<html>` is all a prerendered page needs, and server and browser render the same classes. **Three things to know.** A design merges and cannot delete a key, so it takes something back off with that prop's `none` (`shadow: 'none'`, `bgGradient: 'none'` — never `bgImage: 'none'`, which loses `background-image` to `bgGradient`). A design nested inside another stops the outer one, but `vars` inherit, so a `data-design="classic"` preview inside an Ember page redeclares its own `Box.palette()`. And a Box under `<Box.Components value>` resolves against that registry and no named design — `Box.createComponents()` is still the answer for a subtree whose design React decides. A part extends like a component does (`trigger: { extends: 'button' }`, `'dialog.title'`), and `letterSpacing` takes the tracking names `tighter`…`widest` in `em`.
|
|
140
140
|
|
|
141
141
|
55. **Every component is drawn at one of three sizes — `compact`, `normal` (the default) and `large` — and a size is two CSS variables.** A field or a button (`Button`, `Textbox`, `Dropdown`/`Select`, `Combobox`/`TimeZoneSelect`, `DateField`/`DatePicker`) is `height="control"` and `fontSize="control"`: 32, 40 or 48px tall, 13, 14 or 16px text. An item or a row (a menu item, a dropdown option, a tab, an accordion header, a calendar day, a time in a picker) is that height as a `minHeight`, so a long label still wraps. A checkbox, a radio, a switch and a slider scale whole, and a progress bar changes its thickness. **`variant="compact"` or `variant="large"` on any of them sizes it and everything inside it** — `<Menu variant="compact">` makes every item compact, `<DatePicker variant="large">` its calendar too, because the size is `--control-height`/`--control-font-size` and they inherit; `variant="normal"` puts the default back inside a sized subtree. **What the three sizes are is the project's, declared once**: `vars={Box.sizes({ compact: 8, normal: 9, large: 11 })}` on the app's root (a number is the ÷4 scale for the height, `{ height, fontSize }` sets both, a string is CSS) — that line is how a project matches shadcn's 36px — and `vars={Box.sizes({ size: 'compact' })}` draws a whole subtree compact. **Never size one control by hand**: `height={9}` on a single button wins as any prop does, and that is exactly how a row ends up with three heights; padding no longer sizes a control at all (`py` changes nothing, `height="auto"` hands it back). Your own control joins the standard with `height="control"` (`minHeight` where it wraps) and `fontSize="control"`; `'control-compact'`/`'control-normal'`/`'control-large'` name one size outright. `variant="square"` on a `Button` is an icon button as wide as it is tall: `<Flex gap={2}><Textbox …/><Button variant={{ secondary: true, square: true }} props={{ 'aria-label': 'Copy link' }}><Icon size={4}><Copy /></Icon></Button></Flex>`. A `DataGrid` is drawn compact.
|
|
142
|
+
|
|
143
|
+
56. **An SVG shape turns about the viewBox's corner, not its own centre.** Its `transform-box` is `view-box`, so `rotate={45}` alone swings it round `0 0`: write `<G rotate={45} transformBox="fill-box" transformOrigin="center">`. `transformOrigin` takes a keyword (`'top left'`), a fraction or a percentage, a ÷4 number, or positions as CSS writes them (`'48px 48px'` — a px is a user unit). The `transform` attribute stays for a matrix or an angle that is data. Two more props an interactive drawing needs: `pointerEvents="visibleStroke"` makes an edge's stroke its hit area (over a wide transparent one), and `touchAction="none"` on the pressed element keeps a finger's drag from panning the page.
|
package/BOX_AI_CONTEXT.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @box-kite/react - AI Assistant Context
|
|
2
2
|
|
|
3
|
-
Runtime CSS-in-JS library. `Box` component accepts
|
|
3
|
+
Runtime CSS-in-JS library. `Box` component accepts CSS properties as typed props and generates CSS classes at runtime. Same prop values share a single class.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -191,7 +191,7 @@ All sizing, spacing, and positioning props also accept percentage strings: `p="5
|
|
|
191
191
|
| `fieldSizing` | field-sizing | `'content'` — a control that grows with what is typed into it, no JavaScript measuring anything — or `'fixed'` |
|
|
192
192
|
| `scrollbarGutter` / `scrollbarWidth` / `scrollbarColor` | scrollbar-gutter / -width / -color | `'stable'` / `'stable both-edges'` reserve the scrollbar's space before there is one, so nothing shifts when content overflows. `scrollbarWidth`: `'auto'`/`'thin'`/`'none'`; `scrollbarColor`: `[thumb, track]`, each a token, a `Box.extend()` colour or either with `/50` |
|
|
193
193
|
| `willChange` | will-change | `'transform'`, `'opacity'`, `'filter'`, `'scroll-position'`, `'contents'`, `'auto'`. A hint with a real cost (a promoted layer holds memory), so it belongs on the few elements that animate, not on a list |
|
|
194
|
-
| `cursor` / `pointerEvents` / `userSelect`
|
|
194
|
+
| `cursor` / `pointerEvents` / `userSelect` / `touchAction` | misc | string values. For `transition`, `animation` and the transform props see _Animation and transitions_ below |
|
|
195
195
|
|
|
196
196
|
### Anchor positioning — a floating layer with no JavaScript
|
|
197
197
|
|
|
@@ -468,26 +468,27 @@ one, nested wherever a prop nests, rendered on a server.
|
|
|
468
468
|
|
|
469
469
|
Twenty-three SVG properties, on any Box, plus twenty element components in `components/svg` — `Svg`, `G`, `Defs`, `Path`, `Circle`, `Ellipse`, `Rect`, `Line`, `Polyline`, `Polygon`, `SvgText`, `TSpan`, `LinearGradient`, `RadialGradient`, `Stop`, `ClipPath`, `Mask`, `Use`, `SvgSymbol`, `Marker`. **Never `<Box tag="path">`.** For an icon from somebody else's set, `<Icon>` (`components/icon`) — see below. (`BaseSvg` is deprecated: it is `Svg` with a 24×24 preset.)
|
|
470
470
|
|
|
471
|
-
| Prop | CSS Property | Accepts
|
|
472
|
-
| ------------------------------- | ----------------------------- |
|
|
473
|
-
| `fill` / `stroke` | fill / stroke | any colour variable, or `'none'`
|
|
474
|
-
| `fillOpacity` / `strokeOpacity` | fill-opacity / stroke-opacity | `0`–`1` in tenths, same scale as `opacity`
|
|
475
|
-
| `fillRule` | fill-rule | `'nonzero'`, `'evenodd'`
|
|
476
|
-
| `strokeWidth` | stroke-width | number, **user units — no divider** (`strokeWidth={2}` → `2`)
|
|
477
|
-
| `strokeLinecap` | stroke-linecap | `'butt'`, `'round'`, `'square'`
|
|
478
|
-
| `strokeLinejoin` | stroke-linejoin | `'miter'`, `'round'`, `'bevel'`
|
|
479
|
-
| `strokeMiterlimit` | stroke-miterlimit | number, 1 or greater
|
|
480
|
-
| `strokeDasharray` | stroke-dasharray | number (`{12}` = dash 12, gap 12)
|
|
481
|
-
| `strokeDashoffset` | stroke-dashoffset | number, or a percentage of the path length (`'40%'`)
|
|
482
|
-
| `paintOrder` | paint-order | `'normal'`, `'fill'
|
|
483
|
-
| `
|
|
484
|
-
| `
|
|
485
|
-
| `
|
|
486
|
-
| `
|
|
487
|
-
| `
|
|
488
|
-
| `
|
|
489
|
-
| `
|
|
490
|
-
| `
|
|
471
|
+
| Prop | CSS Property | Accepts |
|
|
472
|
+
| ------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
473
|
+
| `fill` / `stroke` | fill / stroke | any colour variable, or `'none'` |
|
|
474
|
+
| `fillOpacity` / `strokeOpacity` | fill-opacity / stroke-opacity | `0`–`1` in tenths, same scale as `opacity` |
|
|
475
|
+
| `fillRule` | fill-rule | `'nonzero'`, `'evenodd'` |
|
|
476
|
+
| `strokeWidth` | stroke-width | number, **user units — no divider** (`strokeWidth={2}` → `2`) |
|
|
477
|
+
| `strokeLinecap` | stroke-linecap | `'butt'`, `'round'`, `'square'` |
|
|
478
|
+
| `strokeLinejoin` | stroke-linejoin | `'miter'`, `'round'`, `'bevel'` |
|
|
479
|
+
| `strokeMiterlimit` | stroke-miterlimit | number, 1 or greater |
|
|
480
|
+
| `strokeDasharray` | stroke-dasharray | number (`{12}` = dash 12, gap 12), the pattern as a string (`"12 4"`), or `'dashed'`/`'dotted'`, which scale with `strokeWidth` |
|
|
481
|
+
| `strokeDashoffset` | stroke-dashoffset | number, or a percentage of the path length (`'40%'`) |
|
|
482
|
+
| `paintOrder` | paint-order | `'normal'`, or up to three of `'fill'`/`'stroke'`/`'markers'` in paint order (`'stroke fill markers'`) |
|
|
483
|
+
| `pointerEvents` | pointer-events | `'none'`, `'auto'`, `'all'`, and which part of a shape takes the press: `'visiblePainted'`, `'visibleFill'`, `'visibleStroke'`, `'visible'`, `'painted'`, `'fill'`, `'stroke'`, `'bounding-box'` (not in Firefox) |
|
|
484
|
+
| `vectorEffect` | vector-effect | `'none'`, `'non-scaling-stroke'` |
|
|
485
|
+
| `shapeRendering` | shape-rendering | `'auto'`, `'optimizeSpeed'`, `'crispEdges'`, `'geometricPrecision'` |
|
|
486
|
+
| `textAnchor` | text-anchor | `'start'`, `'middle'`, `'end'` — which part of a label sits on its `x` |
|
|
487
|
+
| `dominantBaseline` | dominant-baseline | `'auto'`, `'alphabetic'`, `'central'`, `'middle'`, `'hanging'`, `'text-top'`, `'text-bottom'`, `'ideographic'`, `'mathematical'` |
|
|
488
|
+
| `cx` / `cy` | cx / cy | number in user units, or a percentage — `<circle>`, `<ellipse>` |
|
|
489
|
+
| `r` | r | number in user units, or a percentage — `<circle>` |
|
|
490
|
+
| `rx` / `ry` | rx / ry | number, a percentage, or `'auto'` — `<ellipse>` radii, `<rect>` corners |
|
|
491
|
+
| `x` / `y` | x / y | number in user units, or a percentage — `<rect>`, `<image>`, `<use>`, `<foreignObject>`, nested `<svg>`. NOT `<text>` |
|
|
491
492
|
|
|
492
493
|
```tsx
|
|
493
494
|
import { Circle, Path, Rect, Svg, SvgText } from '@box-kite/react/components/svg';
|
|
@@ -515,17 +516,18 @@ import { Circle, Path, Rect, Svg, SvgText } from '@box-kite/react/components/svg
|
|
|
515
516
|
<Rect x={16} y={32} width={24} height={64} rx={3} fill="sky-600" />
|
|
516
517
|
```
|
|
517
518
|
|
|
518
|
-
**
|
|
519
|
+
**Ten things to get right:**
|
|
519
520
|
|
|
520
|
-
1. **No divider on SVG lengths.** `strokeWidth`, `strokeDasharray`, `strokeDashoffset` and `strokeMiterlimit` pass the number through unchanged — they are measured in the coordinate system the `viewBox` sets up.
|
|
521
|
+
1. **No divider on SVG lengths.** `strokeWidth`, `strokeDasharray`, `strokeDashoffset` and `strokeMiterlimit` pass the number through unchanged — they are measured in the coordinate system the `viewBox` sets up. The geometry props (`cx`/`cy`/`r`/`rx`/`ry`/`x`/`y`) write that number as `px` — a user unit in SVG — because Firefox and Safari drop a bare number in them.
|
|
521
522
|
2. **Inheritance does the work.** Put `fill`/`stroke`/`strokeWidth` on the `<svg>`, not on every shape. The exception is a shape carrying its own `fill=` attribute — a presentation attribute on an element beats a value inherited from its parent, so style that shape directly.
|
|
522
523
|
3. **`vectorEffect` and `dominantBaseline` are not inherited**, so their rules target the element _and_ its descendants (`.cls, .cls *`). `vectorEffect="non-scaling-stroke"` on the `<svg>` keeps a hairline one pixel wide at any scale; `dominantBaseline="central"` on the `<svg>` reaches every label inside it.
|
|
523
524
|
4. **Geometry belongs to the shape, and it transitions.** `cx`/`cy`/`r`/`rx`/`ry`/`x`/`y` are real CSS in SVG 2, so a gauge or a growing bar is a pseudo-class and no JavaScript. They are not inherited and should not be — set them on the shape.
|
|
524
|
-
5. **Each component settles the names SVG and Box both use, for its own element.** On `Path`, `d` is path data (not `flexDirection`); on `Rect`, `width`/`height` are user units (not the ÷4 layout scale); on `SvgText` and `TSpan`, `x`/`y`/`dx`/`dy` are attributes, because the CSS geometry properties do not apply to text; on `RadialGradient`, `cx`/`cy`/`r` are attributes, because they do not apply to a gradient either — while on `Circle` the same names stay CSS and transition. `transform` is a prop on every shape and group (`transform="rotate(-90 48 48)"`
|
|
525
|
+
5. **Each component settles the names SVG and Box both use, for its own element.** On `Path`, `d` is path data (not `flexDirection`); on `Rect`, `width`/`height` are user units (not the ÷4 layout scale); on `SvgText` and `TSpan`, `x`/`y`/`dx`/`dy` are attributes, because the CSS geometry properties do not apply to text; on `RadialGradient`, `cx`/`cy`/`r` are attributes, because they do not apply to a gradient either — while on `Circle` the same names stay CSS and transition. `transform` is a prop on every shape and group, for a matrix or an angle that is data (`transform="rotate(-90 48 48)"`); a fixed turn about the shape's own centre is CSS (item 10).
|
|
525
526
|
6. **`Svg` sizes with attributes and names itself with `label`.** `viewBox`, `preserveAspectRatio`, `width` and `height` are the SVG attributes (`width="100%"`, `width={200}`), so the ÷4 layout `width`/`height` are not available on it. No `label` means `aria-hidden` — decoration, which is what most SVG is; `label` makes it `role="img"` with that name. A role or `aria-*` of your own in `props` wins over both.
|
|
526
527
|
7. **An icon from a set is not SVG you draw — it is `<Icon>`.** `<Icon size={5} color="amber-500" label="Sunny"><Sun /></Icon>` from `components/icon`, wrapping one element from lucide, Tabler, react-icons, or a raw `<svg>`. `size` is the ÷4 scale (`size={6}` is 24px, the default) and lands in the _class_, where a CSS declaration outranks the `width`/`height` attributes the icon set writes — which is why `Icon` needs to know no set's API. `strokeWidth` is the same: an ordinary Box prop, so it can change on hover. No `label` means `aria-hidden`; a `label` means `role="img"`. **Reach for `Svg` rather than wrapping one in `Icon`** — `Svg` takes these props directly. (Wrapping one does work: `Icon` asks the child which convention it follows and routes `role`/`aria-label` into `props` for a component of ours. It is still a layer nobody needs.)
|
|
527
528
|
8. **A paint server is a value, not an attribute.** `fill`, `stroke` and `clipPath` take `url(#id)` (a `<LinearGradient>`, a pattern, a `<ClipPath>` the document defines) and `var(--name)` (a variable somebody else declared) beside the colour tokens — so `fill="url(#sky)"` and `clipPath="url(#frame)"`, and both can differ per theme, on `hover` and per breakpoint. Only those two shapes are accepted: anything else emits no rule at all rather than a broken declaration. A gradient stop is still themed the same way — `<Stop stopColor="currentColor" color="amber-300" />`.
|
|
528
529
|
9. **An icon outside lucide comes through the same `<Icon>`.** Iconify carries 300,000+ icons in 200-plus sets, and the choice is only about _when_ the icon becomes markup. One icon: copy its SVG and paste it into an `<Icon>` — no dependency, renders on a server. A set: `unplugin-icons` compiles `~icons/<set>/<name>` into a component at build time from an `@iconify-json/<set>` devDependency (`npm i -D unplugin-icons @iconify-json/<set> @svgr/core @svgr/plugin-jsx`, the plugin with `{ compiler: 'jsx', jsx: 'react' }`, and `/// <reference types="unplugin-icons/types/react" />` in a `.d.ts`) — nothing fetched at runtime, server-renders. A name that is data (from a CMS, from a user): `@iconify/react`'s `<Icon icon="set:name"/>` inside our `<Icon>` — it fetches in the browser, so it is a client component and the server sends no icon. **Turbopack runs no unplugin**, so under Next.js 16 the build-time recipe needs `next build --webpack`; the other two work as they are.
|
|
530
|
+
10. **A shape turns about the viewBox's corner unless told otherwise.** An SVG element's `transform-box` is `view-box`, so `rotate={45}` alone swings a shape round `0 0`. `<G rotate={45} transformBox="fill-box" transformOrigin="center">` turns it about its own centre (measured in Chrome, Firefox and WebKit). `transformOrigin` takes a keyword (`'center'`, `'top left'`), a fraction or a percentage, a number on the ÷4 scale, or two or three positions as CSS writes them (`'48px 48px'` — in SVG a px is a user unit). An edge you can press is `pointerEvents="visibleStroke"` over a wide transparent stroke, and a drag on a touch screen needs `touchAction="none"` on the element pressed, or the page pans instead.
|
|
529
531
|
|
|
530
532
|
---
|
|
531
533
|
|
|
@@ -534,20 +536,21 @@ import { Circle, Path, Rect, Svg, SvgText } from '@box-kite/react/components/svg
|
|
|
534
536
|
Every Box already transitions `all` its properties over `--transitionTime` (0.25s), which is why a
|
|
535
537
|
`hover` colour fades without being asked. On top of that:
|
|
536
538
|
|
|
537
|
-
| Prop | CSS Property
|
|
538
|
-
| ----------------------------------------------------------------- |
|
|
539
|
-
| `animation` | animation
|
|
540
|
-
| `animationName` | animation-name
|
|
541
|
-
| `animationDuration` / `animationDelay` | ms
|
|
542
|
-
| `animationIterationCount` | number
|
|
543
|
-
| `animationDirection` / `animationFillMode` / `animationPlayState` | keywords
|
|
544
|
-
| `animationTimingFunction` / `transitionTimingFunction` | timing function
|
|
545
|
-
| `transition` | transition-property
|
|
546
|
-
| `transitionDuration` / `transitionDelay` | ms
|
|
547
|
-
| `transitionBehavior` | transition-behavior
|
|
548
|
-
| `interpolateSize` | interpolate-size
|
|
549
|
-
| `startingStyle` | `@starting-style`
|
|
550
|
-
| `translateX` / `translateY` / `rotate` / `scale` / `flip` | transform longhands
|
|
539
|
+
| Prop | CSS Property | Notes |
|
|
540
|
+
| ----------------------------------------------------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
541
|
+
| `animation` | animation | One of four presets: `'spin'`, `'pulse'`, `'bounce'`, `'ping'`, or `'none'`. Keyframes come with the engine |
|
|
542
|
+
| `animationName` | animation-name | A sequence registered with `Box.keyframes()`, a preset name, or a name from any stylesheet |
|
|
543
|
+
| `animationDuration` / `animationDelay` | ms | **Milliseconds, no divider**: `animationDuration={1100}` → `1100ms` |
|
|
544
|
+
| `animationIterationCount` | number | A number or `'infinite'` |
|
|
545
|
+
| `animationDirection` / `animationFillMode` / `animationPlayState` | keywords | `'normal'`/`'reverse'`/`'alternate'`/`'alternate-reverse'`; `'none'`/`'forwards'`/`'backwards'`/`'both'`; `'running'`/`'paused'` |
|
|
546
|
+
| `animationTimingFunction` / `transitionTimingFunction` | timing function | Keywords, the four springs (`'spring'`, `'spring-gentle'`, `'spring-bouncy'`, `'spring-snappy'`), or a curve: `'cubic-bezier(0.4, 0, 0.6, 1)'`, `'steps(4, end)'`, `'linear(0, 0.5, 1)'` |
|
|
547
|
+
| `transition` | transition-property | `'all'`, `'none'`, or a group: `'colors'`, `'opacity'`, `'shadow'`, `'transform'`, `'size'`, `'filter'` |
|
|
548
|
+
| `transitionDuration` / `transitionDelay` | ms | Milliseconds, same as the animation pair. Both duration props also take a spring name — that spring's settling time |
|
|
549
|
+
| `transitionBehavior` | transition-behavior | `'normal'` or `'allow-discrete'`, which lets `display`, `overlay` and `content-visibility` transition — the property that makes an element animate _out_ without unmounting |
|
|
550
|
+
| `interpolateSize` | interpolate-size | `'numeric-only'` or `'allow-keywords'`, which is what makes `height: auto` animate. Inherited, so it belongs on a container. Chromium-only; elsewhere the size snaps |
|
|
551
|
+
| `startingStyle` | `@starting-style` | A nested block of plain props: what they start from the first time the element is styled. `startingStyle={{ opacity: 0, translateY: 2 }}` is an entrance with no JavaScript |
|
|
552
|
+
| `translateX` / `translateY` / `rotate` / `scale` / `flip` | transform longhands | Each writes its own CSS property, so they compose. `scale={1.05}` is unitless; `rotate={45}` is degrees |
|
|
553
|
+
| `transformOrigin` / `transformBox` | transform-origin / transform-box | The point `rotate`/`scale` turn about (`'center'`, `'top left'`, `'1/2 1/4'`, `'48px 48px'`, a ÷4 number) and the box it is measured in (`'fill-box'` for an SVG shape's own) |
|
|
551
554
|
|
|
552
555
|
```tsx
|
|
553
556
|
// A preset: no registration, and it stops under prefers-reduced-motion on its own
|
|
@@ -3830,7 +3833,7 @@ allowed.tokens; // the colours, the @keyframes names and the style-tree nodes a
|
|
|
3830
3833
|
`DataGrid`'s `def` (required — the columns and the grid-wide flags) and `data`, `DashboardGrid`'s
|
|
3831
3834
|
`layout`/`defaultLayout`/`columns`, `Widget`'s `empty`, `ChartContainer`'s `series`. A column's nested
|
|
3832
3835
|
`columns`, and `dataSource`/`onCellEdit`/`rowDetail`/`treeData`, stay out — they are the app's to pass.
|
|
3833
|
-
- **The allow-list is the app's.** `catalog()` with no options is
|
|
3836
|
+
- **The allow-list is the app's.** `catalog()` with no options is every component and every prop —
|
|
3834
3837
|
~4.6 MB serialized. `include`, `exclude` and `styleProps` are how an app narrows it.
|
|
3835
3838
|
|
|
3836
3839
|
Full reference: `docs/catalog.md`.
|