# Escape Hatch

_Box Kite 2.2.5 · a markdown copy of https://www.box-kite.dev/escape-hatch/_

The 5% the typed set does not cover, without an inline style: css takes a style object and compiles it into a class like every other prop — shared, nestable, rendered on a server.

## A class, not a style attribute

Every prop on this site is a closed typed set, and that is the feature: a value that typechecks works, and a token cannot drift. The cost is the property nobody has typed yet — `mix-blend-mode`, `scroll-snap-type`, a vendor prefix — and the usual way out, `style={{ }}`, gives up everything else at once: no sharing, no `hover`, no breakpoint, no theme, and a byte of inline CSS on every element. `css` is the same style object handed to the engine instead. It becomes a rule, the rule gets a class, and the class is shared by every element writing the same object.

**A property with no prop**

```jsx
<Flex ai="center">
  <Box width={24} height={24} borderRadius={12} bgColor="sky-400" />
  <Box width={24} height={24} borderRadius={12} bgColor="rose-400" translateX={-8} css={{ mixBlendMode: 'multiply' }} />
</Flex>
```

## It nests wherever a prop does

The object is a prop value like any other, so it goes inside `hover`, a breakpoint, a theme, a `dataAttr`, a pseudo-element or `startingStyle` — and each nesting compiles to its own rule with the same selector every typed prop would get. A blend that multiplies on a light surface has to screen on a dark one; the theme decides, not a re-render.

**One blend mode per theme**

```jsx
<Flex ai="center">
  <Box width={24} height={24} borderRadius={12} bgColor="amber-400" />
  <Box
    width={24}
    height={24}
    borderRadius={12}
    bgColor="violet-500"
    translateX={-8}
    theme={{ light: { css: { mixBlendMode: 'multiply' } }, dark: { css: { mixBlendMode: 'screen' } } }}
  />
</Flex>
```

**Line clamp: three declarations, one of them prefixed — and a hover that lets it go**

```jsx
<Box
  maxWidth={96}
  overflow="hidden"
  css={{ display: '-webkit-box', WebkitBoxOrient: 'vertical', WebkitLineClamp: 2 }}
  hover={{ css: { WebkitLineClamp: 'none' } }}
>
  {clampedText}
</Box>
```

## Names are typed, values are CSS

Property names are camelCase, the way React spells them, and they come from `csstype` — so a misspelt property is a compile error and every name autocompletes. `WebkitLineClamp` becomes `-webkit-line-clamp`, `msOverflowStyle` becomes `-ms-overflow-style`, and a name already hyphenated, or a `--custom` one, is written as it stands. Values are CSS written out as they stand — a number too, which is why `width: 100` is a type error (that would be `width:100`, which is not CSS) while `zIndex`, `opacity`, `flexGrow` and `lineHeight` take theirs. The one thing resolved for you is a colour token: `outlineColor: 'sky-500'` is `var(--sky-500)` and `'rose-400/60'` is the mix, the same rule a `vars` value follows, so the hatch stays themed.

**The value grammar**

```jsx
<Box
  css={{
    outlineColor: 'sky-500', // a token resolves: outline-color: var(--sky-500)
    textDecorationColor: 'rose-400/60', // and takes the opacity modifier
    zIndex: 3, // a unitless property takes a number
    scrollSnapType: 'x mandatory', // everything else is written as it stands
    WebkitLineClamp: 2, // a vendor prefix by React's spelling
    '--rows': 3, // a custom property, though vars is the prop for those
  }}
/>
// width: 100 is a type error — a length wants its unit written out, as '100px'.
```

## It is sorted last

The registry declares `css` after every other prop, so its rule sorts after every typed prop's at the same specificity. On one element, the hatch wins the property both name — it is the override, and the cascade says so rather than the order you wrote the props in.

**The generated order**

```css
/* <Box p={4} css={{ padding: '3px 7px' }} /> */
.p-4 { padding: 1rem }
.css-padding-3px_7px { padding: 3px 7px } /* after every typed prop's rule, whatever the prop order */
```

## The three ways out, in order

**A typed prop**, if one exists. The prop reference lists all 221 of them with the CSS each one writes; most of what feels missing is a spelling away.

**`Box.extend()`** for a property you will use more than once: five lines, and afterwards it is indistinguishable from a built-in — typed, nested, shared, server-rendered.

**`css`** for the one-off. It is the 5%, and it is meant to stay that: an ESLint rule that flags or forbids it per team policy is planned, and until it ships the audit is one line.

**Where the hatch is used**

```shell
grep -rn "css={{" src
```

## What it will not do

**Write a selector.** A key is a property name, never `&:hover` or `> *`; the nesting keys — `hover`, `dataAttr`, `has`, `group`, `nth` — are how a rule gets a selector, and they wrap `css` like any prop.

**Let a value end the rule.** A value carrying `;` or a brace, or a name that is not a property name, is dropped — that entry only, not the object. A data URI's `;` is written `%3B`.

**Format a number.** The dividers belong to the typed props; here `0` is the only number a length takes, and a unitless property takes what you wrote.

---

_Every page: https://www.box-kite.dev/llms.txt · every prop, measured: https://www.box-kite.dev/props.md_

