# React Server Components

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

Box renders on the server with no 'use client', no provider and no stylesheet to import. Its CSS is part of the HTML.

## How it works

A Server Component cannot inject styles: there is no effect to run and no document to write to. In this mode Box does not inject anything — each rule it needs comes back as a `<style href precedence>` element rendered next to the markup, and React 19 hoists those into `<head>` and dedupes them by `href`. Styling becomes rendering, which is why it also streams: nothing waits for a commit that Suspense may split.

Nothing to configure

The `react-server` export condition resolves to a build of Box that calls no hook, schedules no effect and never touches the DOM. Importing the package from a Server Component is the whole setup.

Deduped by content

Every `href` is a hash of the rule text, and class names are content hashes too — so the class the server resolved is the class the browser bundle resolves, and two Boxes with `p={4}` produce one style element.

**A page with no 'use client'**

```jsx
// app/page.tsx — a Server Component.
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} bgColor="slate-50" borderRadius={2} theme={{ dark: { bgColor: 'slate-900' } }}>
      <H1 fontSize={24} fontWeight={600}>Rendered on the server</H1>
      <P color="slate-600" md={{ fontSize: 16 }} hover={{ color: 'sky-500' }}>
        Pseudo-classes, breakpoints and themes all work — they are just more rules.
      </P>
    </Flex>
  );
}
```

**Client components: one line, once**

```jsx
// app/elementMode.ts — imported by every 'use client' module in the app.
'use client';
import Box from '@box-kite/react';

// The client bundle resolves the client Box, which inserts rules through the CSSOM after
// hydration — so an island's CSS would be missing from the server-rendered HTML. This puts
// it back in the markup, on the same emission path the server used.
Box.configure({ sink: 'element' });
```

## Theming without a provider

`Box.Theme` needs state, storage and a media-query listener, so it stays client-only — but theme _rules_ are ancestor-scoped (`.dark .someClass`), which means a server component only has to put the theme name on `<html>`. Switching it later is a DOM write, and `createThemeController()` is that state machine with no React in it.

**The server decides the theme**

```jsx
// app/layout.tsx — a Server Component.
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className="dark" data-theme="dark">
      <body>{children}</body>
    </html>
  );
}
```

**…and the browser can change it**

```jsx
// app/themeToggle.tsx — the island that changes it.
'use client';
import { createThemeController } from '@box-kite/core';

const controller = createThemeController({ storageKey: 'theme', theme: 'dark' });

export default function ThemeToggle() {
  return <button onClick={() => controller.set(controller.theme === 'dark' ? 'light' : 'dark')}>Theme</button>;
}
```

## The cascade comes from @layer

React hoists elements in render order, and atomic rules are shared between Boxes — so element order can never be relied on to keep `p` losing against `px`. Every rule is therefore wrapped in a cascade layer, and the base element declares the full layer order once, up front. Layer order beats source order, so this mode is exactly as deterministic as the CSSOM one. Two consequences are worth knowing:

Your own unlayered CSS wins

Unlayered CSS beats every layer, so a plain stylesheet of yours overrides Box props here — which matches where the library puts its `<style>` element in the other modes.

Call Box.extend() before the first render

CSS appends a layer the first time it meets it, after every layer already named, so a prop registered mid-render sorts after the built-ins.

## What stays on the client

- Hover-callback children (`{({ isHover }) => …}`) — they need state. The server Box throws a message saying so.
- `Box.Theme`, for the same reason. Theme _styles_ are unaffected.
- The pre-built components that hold state — `Dropdown`, `Tooltip`, `DataGrid`, `Checkbox`, `Select`, `Form`. Their chunks ship a `'use client'` banner, so a Server Component can still import one — it just becomes a client boundary. The hook-free ones (`Flex`, `Grid`, `Button`, `Textbox`, `H1`, …) render on the server like Box does.
- React 19 only. On React 18 the elements cannot be hoisted; keep the default sink there.

## Server rendering without Server Components

With `renderToString` (or a static build) the default sink collects the CSS in memory, and `getStyleTag()` hands it back as the `<style>` element for the head. That element carries a short manifest in `data-box-kite`, and the browser _adopts_ the sheet on hydration: every rule in it counts as already generated, so nothing is rebuilt or inserted again and the sheet is never swapped out. Only the rules the client adds afterwards are inserted, each into its place in the cascade.

Name classes the same way on both sides

Adoption needs `Box.configure({ classNames: 'stable' })` in the server and in the browser — the content-hashed names are what lets the browser recognise the server's rules. Register the same `Box.extend()` props on both.

A sheet that cannot be adopted still works

If the browser dropped a rule it could not parse, the manifest no longer matches. The sheet stays in place to style the page, and the engine writes its own after it.

**One request**

```jsx
import { renderToString } from 'react-dom/server';
import Box from '@box-kite/react';
import { getStyleTag, resetStyles } from '@box-kite/react/ssg';

Box.configure({ classNames: 'stable' });

export function render(app: React.ReactElement): string {
  const html = renderToString(app);
  const styleTag = getStyleTag(); // <style id="box-kite-styles" data-box-kite="…">…</style>
  resetStyles();

  return `<!doctype html><html><head>${styleTag}</head><body><div id="root">${html}</div></body></html>`;
}
```

## The example app

`examples/next-app` in the repository is a Next.js App Router app whose pages are Server Components, with a streamed Suspense boundary, a client island, the theme toggle above and a page of pre-built components rendered by the server. Its smoke test starts the production server and asserts on the HTML that the base element was hoisted, that every class in the markup has its rule in the response, and that the streamed section brought its own CSS with it.

```shell
npm run build            # the library itself
npm run build:next-app   # packs dist/, installs it, builds the app
npm run smoke:next-app   # 13 assertions against the served HTML
```

---

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

