> For the complete documentation index, see [llms.txt](https://beamline.gitbook.io/beamline-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://beamline.gitbook.io/beamline-docs/getting-started/customise-beamline.md).

# Customise Beamline

Build your own style from any look: local style files, public tokens, fonts, radii, density, motion, per-part CSS and direct source edits.

> The original of this page is on beamline.io: [beamline.io/docs/customize](https://beamline.io/docs/customize).

> Beamline code and your custom style files stay in your repo; your app has no Beamline runtime dependency. This documentation ships with the complete system (docs/CUSTOMIZE.md in the kit). Use [local kit tools](https://beamline.io/docs/install) or [connect an agent](https://beamline.io/connect) to install parts.

Everything visual reads design tokens (CSS custom properties) from the stylesheet. Change a token and every component that uses it follows; no component hard-codes a colour, radius, shadow or duration.

## Managed appearance in 0.2

Use the release's canonical runtime and import selected CSS explicitly. A preset reference is pure data; passing it never imports other looks, fonts or effects. The component UIRoot path remains a compatibility re-export of the same runtime.

```tsx
import type { ReactNode } from "react";
import { UIRoot } from "@beamline/runtime";
import { prism } from "@beamline/look-prism";
import "./styles/beamline/beamline.css"; // import the generated selection stylesheet once
export function Example({ children }: { children: ReactNode }) {
  return (
    <UIRoot preset={prism} theme="dark" brand="oklch(0.7 0.15 150)">
      {children}
    </UIRoot>
  );
}
```

A compiled install writes `beamline.css` with foundation, the selected looks and every selected component in order ([INSTALL](https://beamline.io/docs/install)). A full-page root also takes `className="look-page"` and the viewport's height; an embedded root paints nothing behind its children. For editable source, the runtime remains `@beamline/runtime`; the CLI installs the selected preset packages alongside managed source. The deprecated `look` shorthand still selects a built-in ID, but `preset` wins when both are supplied.

Nested roots inherit omitted inputs. `tokens` merges sparse public token overrides by key, and `defaults` merges by component and allowed design prop. `inherit={false}` drops managed ancestor choices before applying local inputs. A local `accent` replaces an inherited explicit brand; `brand` wins over accent in the same scope. Use the dedicated brand prop instead of putting `--pui-brand` in `tokens`.

```tsx
import type { ReactNode } from "react";
import { UIRoot } from "@beamline/runtime";

export function Example({ children }: { children: ReactNode }) {
  return (
    <UIRoot tokens={{ "--pui-radius-control": "12px" }} defaults={{ button: { size: "sm" }, table: { density: "compact" } }}>
      {children}
    </UIRoot>
  );
}
```

Default keys are lowercase catalog IDs. Only supported presentation props belong there: handlers, adapters, data, value/open/loading and selection remain application state. Explicit instance props win, including controls inside blocks. Each supporting component exports a lightweight `./defaults` type.

`appearanceClassName` applies to both the region and its independent portal host. Omit it to inherit, replace it with a supplied class string, or use `""` to clear it. Its selectors must work on each boundary without requiring an ancestor. Ordinary `className` and `style` stay local. Managed portals wait for their host and keep the same destination while appearance changes, so an open task retains its focus and draft.

A static `.pui`/`data-pui-preset` wrapper provides DOM styling only. Raw descendant CSS writes do not mirror to portals or invalidate canvas renderers. Managed renderer integrations use `useResolvedAppearance(hostRef, request)` and its explicit `refresh()` when an external stylesheet changes.

## Custom styles, by hand or with an agent

If your bundle includes the optional local studio, start it with Node 20+:

```sh
node /path/to/release/tools/beamline.mjs studio
```

Open the localhost address it prints. This is the gallery's Foundations pages, style builder and selected live components, compiled into the bundle; it needs no app dependency installation or Beamline service. Use `--port 5191` for a stable local address. Stop with Ctrl+C. Your saved styles and settings live in that browser at that address; export the editable file to keep them independently of browser storage. The studio is separate from the component archives your app installs. Release maintainers include it with `build-bundle.mjs --studio`; bundles without it keep the same CLI style commands.

The local studio's **Export** downloads an editable `.style.json` and a complete `.preset.css`. Both carry the name, base look, pins and saved foundation parameters. Start from any approved look. A pin keeps that radius fixed when roundness changes; removing a Blueprint pin stays removed in both the preview and export. Zero is a valid radius.

The matching bundle's CLI uses the same style operations and validates against that release's preset contract:

```sh
node /path/to/release/tools/beamline.mjs style create calm.style.json --base prism --name "Warm and quiet" --id acme/calm
node /path/to/release/tools/beamline.mjs style set calm.style.json --token --pui-radius-control --value 12px
node /path/to/release/tools/beamline.mjs style parameter calm.style.json radiusScale 1.5
node /path/to/release/tools/beamline.mjs style parameter calm.style.json motionScale 2
node /path/to/release/tools/beamline.mjs style parameter calm.style.json brand '#c57736'
node /path/to/release/tools/beamline.mjs style validate calm.style.json --release /path/to/release/release.json
node /path/to/release/tools/beamline.mjs style export calm.style.json --release /path/to/release/release.json --out calm.preset.css
node /path/to/release/tools/beamline.mjs style add calm.style.json --app /path/to/app --root-file src/app.tsx
```

`style add` also accepts a studio-exported `.preset.css`. It requires the base look already installed from the app's accepted release. Name the file containing the intended single `UIRoot` imported from `@beamline/runtime` or `@beamline/ui/ui-root` (a named import alias is supported). It adds one appearance import and one props spread while preserving the root's children, event handlers, existing imports and client directive. The appearance owns `preset`, `look` and every prop it writes (brand, scale parameters, motion, density, tokens): those props are removed from the root, reported with their previous source, and replaced by the spread where the first one stood, so the root still type-checks. A look import only they used is removed too. Props you put after the spread later stay and win. If the app has another wrapper, apply the generated appearance props in that wrapper as part of the agent's normal integration work.

The default destination is `src/styles/beamline`, outside the managed component source. Change it with `--styles-to`. This directory contains the editable source, complete preset CSS, `appearance.ts` with the saved `UIRoot` props, and `beamline.css`, the app's one Beamline stylesheet, which now also imports the custom preset (`appearance.ts` imports it, so the style works even before the entry does). Repeating the same command adds nothing twice. Editing the original source and applying again updates only files that still match the previous application; a customer edit is preserved and named. Source and package upgrades leave this customer-owned directory alone.

Use `style pin <file> --token --pui-radius-control --reason "Part of our identity"` or `style unpin <file> --token --pui-radius-control`. `style modifier <file> motion 1.25` composes with the app's speed; reduced/none still win. `style set` and `style parameter` accept `--reset`. `style set` refuses a token the base look does not declare and names the closest ones; it checks against `--release`, the bundle's own release, or a source checkout's presets. Invalid fields/tokens are named by validation. Edit the JSON source, then export CSS. The CLI is local and uses no model, hosted editor or new service.

For direct integration, import the selected base look's stylesheet plus your custom stylesheet and use the canonical reference (custom IDs do not go in the deprecated built-in `look` prop):

```tsx
import type { ReactNode } from "react";
import { UIRoot } from "@beamline/runtime";

export function Example({ children }: { children: ReactNode }) {
  return (
    <UIRoot preset={{ id: "acme/calm", visualContract: 1, base: "prism" }} radiusScale={1.5} motionScale={2}>
      {children}
    </UIRoot>
  );
}
```

The static-scope CSS includes suggested numeric/focus/brand parameters; `UIRoot` props also carry density and motion policy to stable portals. A custom preset's fonts must be selected/installed explicitly.

An example's **Use with my agent** instruction and downloaded choice include its custom style, active foundation parameters and explicit token overrides. Shared preview links reproduce those settings without using the recipient's saved appearance. A preview does not replace their saved studio library.

## Brand colour (one change)

```css
/* For a static root with class="pui acme-brand". On UIRoot use its brand prop instead. */
.pui.acme-brand { --pui-brand: oklch(0.7 0.15 150); }
```

Accent, accent text, hover and pressed states, focus rings, selection tints, glows and the first chart series all derive from `--pui-brand`. Text on a brand fill (a primary accent button, a selected day, a checked box) picks its own ink from the brand's lightness, so any brand keeps at least 4.5:1 without a second token. Status fills work the same way through `--pui-on-red`, `--pui-on-green`, `--pui-on-orange` and `--pui-on-blue` (`text-pui-on-red` … with `@beamline/foundation/tailwind.css`; `text-on-red` … in the source). OKLCH keeps lightness predictable: keep L between 0.55 and 0.75 for a brand that also reads as text on both themes. Six presets swap only the brand: `data-accent="neutral|violet|green|amber|orange|rose"` on `<html>`.

The brand engine also exposes seven lightness steps and six chroma steps of the brand (`--pui-brand-l-14 … --pui-brand-l-86`, `--pui-brand-c-0 … --pui-brand-c-22`) for brand-tinted illustrations and fills.

## Theme

Dark and light are both complete in every look; dark is the default: `class="pui dark"` (or just `pui`) on `<html>`, or `<UIRoot theme="dark">`. Light: `class="pui light"` or `theme="light"`; `data-theme="dark|light"` works too. A region can switch theme for its own subtree (a `UIRoot` inside another). Both themes define every token, so components never branch on the theme. `ThemeSwitch` (`@beamline/theme-switch`) is a ready control.

## Look

Every element has a core (size, layout, states, keyboard, motion) and a look: the whole style on top of it, meaning the fonts, icon weight, corner radii, the page behind everything and the paint of every surface. The look is chosen like the theme and independently of it, and changing it on the root restyles the whole app:

Install Glass and Glow with the CLI first so the generated stylesheet includes both. Use nested roots for a different look in one part of the page:

```tsx
import { UIRoot } from "@beamline/runtime";
import { Button } from "@beamline/button";
import { glass } from "@beamline/look-glass";
import { glow } from "@beamline/look-glow";

export function Looks() {
  return (
    <UIRoot preset={glass} theme="light">
      <p>A region, including its menus and dialogs.</p>
      <UIRoot preset={glow}>
        <Button variant="accent">Upgrade</Button>
      </UIRoot>
    </UIRoot>
  );
}
```

`prism` is the default: cut crystal in a dark room (a white room in light). Parts are drawn by their cut edges, which catch light from the top left; the main action is white light with near-black words; colour appears only where attention is (a line of split light under the field being written in, the chosen tab and the pointed-at main action) and in charts, drawn in the light band's own colours (blue, teal, red, amber, then deep blue, coral, sky). Set `brand` and the main action, focus ring and highlights take your colour instead of white. `soft` is clean surfaces tinted with a trace of your brand's hue, hairline rims, short shadows, Geist headings. `blueprint` is the drafting table for dense tools, drawn as CAD: cyan wireframe frames with corner ticks, square corners, mono micro-labels. `glass` is Liquid Glass as iOS 27 draws it: controls and menus bend what is behind their rims, every edge is lit by two crescents of light, controls are capsules and a press lights the glass from the point pressed, over a faint grid and soft pools of your brand's light; `--pui-glass-frost` (0 clear to 1 frosted) moves every glass part at once. `metal` is black anodised metal with diamond-cut edges (anodised aluminium in light) with wide Archivo headings and monospaced figures. `glow` keeps a calm night and puts light in one place: a faint haze of your brand's spectrum at the top, and soft spectral light under the main buttons. `pop` is a sticker sheet: a warm page, ink outlines, a hard lip under every control, Gabarito and Figtree. `mono` is monochrome: controls are ink and your brand colour appears only in the data it leads. `ink` is a pen-and-ink drawing in two inks. A look never changes the size scale (control heights, spacing), so switching it never re-flows a layout beyond text widths following its fonts. The look names are in `LOOKS` from `@beamline/lib-look`.

Card rims can catch light as the pointer comes near. This is an explicit optional feature: import `setup` from the release's `@beamline/effect-edge-light` entry (also used with managed source) and call it once in an application effect, returning its cleanup. `UIRoot` does not start it or import every look initializer. It does nothing while the pointer is still, on touch screens, or with reduced motion.

Looks that use SVG resources declare a separate runtime entry: `@beamline/look-glow/runtime`, `@beamline/look-mono/runtime` or `@beamline/look-ink/runtime`. Import only the selected `setup` and return its cleanup from an application effect. The setup shares resources within the document; the preset's root entry stays pure.

Your own parts can wear the same paint with the role classes `m-control`, `m-solid` (set `--m-color`), `m-field`, `m-inset`, `m-toggle`, `m-knob`, `m-tag`, `m-surface` and `m-overlay`, the look's page with `look-page`, a sidebar with `look-chrome` (frosted in glass) and a meter track with `meter` (segmented in metal). A new look implements visual contract 1 as complete CSS. Built-in authored recipes live in `ui/styles/preset-source.css`; `looks.css` is the explicit aggregate. The generator flattens inheritance into each selected stylesheet and validates its declared contract. External IDs need no runtime registry or edit to the built-in Look type.

## Other tokens worth knowing

| change                    | token                                                                                               |
| ------------------------- | --------------------------------------------------------------------------------------------------- |
| corner radius family      | `--pui-radius-chip` 6 · `--pui-radius-control` 9 · `--pui-radius-card` 12 · `--pui-radius-panel` 14 |
| surfaces                  | `--pui-canvas`, `--pui-surface`, `--pui-surface-2`, `--pui-inset`, `--pui-field`                    |
| text                      | `--pui-ink` (primary), `--pui-ink-2` (body), `--pui-ink-3` (meta only)                              |
| trading direction colours | `--pui-up`, `--pui-down` (separate from success/danger so red-up markets can swap them)             |
| chart series              | `--pui-series-1 … --pui-series-8` (series 1 follows the brand)                                      |
| motion                    | `--pui-motion-in` (90 ms), `--pui-motion-out` (180 ms), easing `--pui-ease-default`                 |
| type faces                | `--pui-font-body` (Inter), `--pui-font-display` (Geist), `--pui-font-code` (Geist Mono)             |

The full list, drawn and explained, is in `docs/DESIGN-LANGUAGE.md` and on the gallery's Foundations pages (`previews/gallery/index.html`, complete bundle).

## Per-component changes

* `className`, `style` and `classNames.root` address the documented visual root. Component CSS sits in `@layer components`, below normal unlayered customer CSS and Tailwind utilities; importance, layers and specificity still govern the real cascade. Putting a class last does not guarantee that it wins.
* Input's 0.2 `style` target is its field wrapper, matching `className`. Use `classNames.input` for the native input. Its ref, native attributes and value still target the input. Select keeps its ref on the trigger; `classNames.trigger` styles that trigger. Styling does not replace measurement refs, keyboard behavior or accessibility relationships.
* Every meaningful part carries `data-slot="<part>"` (for example `data-slot="order-book-row"`) and state in data attributes (`data-state="open"`, `data-side="bid"`), so you can style parts without forking:

  ```css
  [data-slot="order-book-row"][data-side="bid"] { font-weight: 500; }
  ```
* Variants and sizes use one vocabulary everywhere: `variant` (primary · secondary · outline · ghost · danger), `size` (sm · md · lg), `tone` (neutral · accent · success · warning · danger · info), `density` (compact · default · comfortable).

## Fonts

Selected installs include the chosen look's font packages in the generated stylesheet. Their `@beamline/font-<id>/styles.css` files serve the fonts from your own app (variable fonts, SIL Open Font License 1.1; each font package includes its licence). Browsers download only the faces and character sets a page uses. The full aggregate also exports `@beamline/ui/fonts.css`. To use other faces, set `--pui-font-inter`, `--pui-font-geist` and `--pui-font-mono-face` to your families and omit the corresponding font imports from your app-owned stylesheet.

## Motion and effects

Motion follows the reader's `prefers-reduced-motion`: durations become instant and decorative loops stop, while live data and progress keep updating. Managed `motion="auto"` follows that preference, `"reduced"` removes decorative displacement, and `"none"` removes decorative transitions. Meaningful loading/status feedback and live data remain. Button retains its Motion label, loading and press behavior; that dependency is part of the selected Button artifact. The old `Button metal` effect was removed; the accepted Metal look is selected through the preset, with no replacement MetalFx effect.

## Right-to-left

Use `UIRoot dir="rtl"` when the region includes portals; its direction reaches both the region and its stable portal host. `Direction` (`@beamline/direction`) remains available for a local direction provider. Components use logical properties, and directional icons flip.

## Editing the source

With the source copy (INSTALL.md, path B) you can change any component directly. Keep the conventions in `docs/CONVENTIONS.md` (props vocabulary, `data-slot`, tokens only) so your edits stay consistent with the rest.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://beamline.gitbook.io/beamline-docs/getting-started/customise-beamline.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
