# 2. Add ClayJS to a page

> The loader tag, versions, plugins and satellites, and how ClayJS boots.

Source: https://clayjs.com/docs/guide/loading/

## The loader tag

Load ClayJS with a single `<script>` tag and select plugins in its query string:

```html
<script src="https://clayjs.com/v1/clay.js?plugins=sync,sortable,undo,indicator&exclude=richclay"></script>
```

- **Use a classic script with a `src`.** The loader reads `document.currentScript` to find its own URL and locate its modules. As `type="module"` or pasted inline, it logs an error and never boots.
- **`/v1/`** is the rolling 1.x alias, cached for 10 minutes: files get fixes automatically. **`/1.8.2/`** and every exact version are immutable for a year: use one when a file must never change behavior.
- **Use the same URL prefix on every tag.** Companion scripts (satellites, such as `clay-ui.js`) import ClayJS modules relative to their own URL. `clay.js` from `/1.8.2/` with `clay-ui.js` from `/v1/` gives two copies of shared modules: transforms registered through one are invisible to the other, and save feedback appears twice.
- npm and jsDelivr package paths work too (`@panphora/clayjs`).
- Put the tags at the end of `<body>`. The loader waits for `DOMContentLoaded` anyway.

`plugins=` and `exclude=` are comma-separated lists. Only the first of each parameter counts. Unknown names warn and are ignored.

## Plugins

Plugins add editing, sync, and UI features to `clay.js`. Most are off by default, and edit-only plugins skip view mode.

| Plugin | Default | Loads in view mode | Adds |
|---|---|---|---|
| `richclay` | on | no | the `editable` attribute, `clay.RichClay` |
| `source` | on | no | byte-preserving saves, `clay.source` |
| `sync` | off | yes | live sync, `clay.conflicts`, `clay.morph`, presence and the editing marker |
| `live` | off | yes | `clay.live(name)`: a `clay="live"` JSON tag as a live object |
| `sortable` | off | no | the `sortable` attribute, `window.Sortable`, `clay:sorted` |
| `undo` | off | no | `clay.undo` (whole-page undo, takes Cmd+Z for the window) |
| `indicator` | off | no | the save status chip |
| `upload` | off | no | `clay.upload` |
| `quickcrop` | off | yes | `clay.quickcrop` |
| `cms` | off | yes | `clay.cms`; implies `quickcrop` and `upload` |
| `wire` | off | yes | `clay.wire`, the channel to a local agent process |
| `message` | off | yes | `message-form`: a visitor's contact form to the owner, `clay.message` ([message: contact forms](https://clayjs.com/docs/guide/components/#message-contact-forms)) |
| `ai-edit` | off | no | select text, ask an agent to rewrite it; implies `wire` |
| `demo` | off | yes | fakes saving into `localStorage` for public demos |
| `visitor-drafts` | off | yes | `clay.visitorDrafts`; in draft mode, the draft store, download prompt and intro area; outside it, only the owner chip, fetched when the owner edits ([Free documents for visitors](https://clayjs.com/docs/guide/free-documents/)) |
| `derive` | on for any page with a `[sap]` root | yes | the sapjs core: records and paints, treated as derived ([sap.js: records, views and totals in markup](https://clayjs.com/docs/guide/components/#sap-js-records-views-and-totals-in-markup)) |

- Edit-only plugins do not load in a tab that booted in view, even when named. Their members are absent there. Test `clay.isEditMode` (or `clay.modeLifecycle.canWrite()` before a write), never `'save' in clay` or `clay.RichClay` alone, because a live exit leaves those members attached. On a `<html clay-mode-switch="live">` document, entering edit imports them while the tab still shows view, and they stay loaded after the exit ([Leaving edit](https://clayjs.com/docs/guide/edit-mode/#leaving-edit)).
- `exclude` wins over implications. `exclude=wire` also drops `ai-edit`.
- Load order is fixed regardless of how you list them.
- Use `exclude=richclay` when the app has no rich text. `exclude=source` saves one request and about 48 KB, at the cost of saves reprinting the whole document instead of keeping hand formatting.
- Nothing implies `undo`, on purpose: it intercepts Cmd+Z in every text field on the page ([undo: whole-page undo](https://clayjs.com/docs/guide/components/#undo-whole-page-undo)).
- In view mode, `page` loads only its reader half and nothing it implies. A Document on `plugins=page` resolves to `page-view`, with no live data service, no generic `sync`, and no `upload`. Name `sync` as well (`plugins=page,sync`) when the reader should take its saved files from sync's lane instead of its own.
- `derive` loads automatically when the page has a `[sap]` root at boot, and stays idle when full sapjs is on the page. `exclude=derive` opts out; `plugins=derive` loads it for a page that adds its `[sap]` root later.
- `visitor-drafts` loads its draft store only in draft mode. A reader or an owner editing the original gets a small module that publishes an inert `clay.visitorDrafts` and, for the owner, the owner chip. On a page with no `visitor-drafts` slot, no `api` rules tag, or a rules tag this version cannot read (not `data-rules-version="1"`, or not JSON), it warns in the console and draws nothing. [Free documents for visitors](https://clayjs.com/docs/guide/free-documents/) covers the opt in.

Measure what each mode you ship downloads, and keep a byte budget and a test that fails when one grows: [Keep a byte budget per boot, and fail a test when it grows](https://clayjs.com/docs/practices/#keep-a-byte-budget-per-boot-and-fail-a-test-when-it-grows).

**A good default for apps:** `?plugins=sync,sortable,undo,indicator`, plus `upload` for images or attachments, plus `exclude=richclay` when there is no rich text.

## Satellites

Satellites are separate classic script tags. Each publishes a readiness promise on `clay.loaded`.

| Tag | Promise | Adds | Section |
|---|---|---|---|
| `clay-ui.js` | `clay.loaded.ui` | `clay.toast`, `toastPersistent`, `ask`, `confirm`, `tell`, `snippet`, `modal`; automatic save feedback toasts | [clay-ui: toasts and dialogs](https://clayjs.com/docs/guide/components/#clay-ui-toasts-and-dialogs) |
| `clay-options.js` | `clay.loaded.options` | `show-when:`, `hide-when:`, `option:`, `option-not:` visibility | [Showing and hiding edit controls: clay-options](https://clayjs.com/docs/guide/edit-mode/#showing-and-hiding-edit-controls-clay-options) |
| `clay-events.js` | `clay.loaded.events` | `onclickaway`, `onclickchildren`, `onclone`, `onmutation`, `onglobalmutation`, `onrender` | [clay-events, clay-dom and All](https://clayjs.com/docs/guide/components/#clay-events-clay-dom-and-all) |
| `clay-dom.js` | `clay.loaded.dom` | `el.nearest`, `el.val`, `el.text`, `el.exec`, `cycleAttr`, `clay.dom.createContentView` | [clay-events, clay-dom and All](https://clayjs.com/docs/guide/components/#clay-events-clay-dom-and-all) |
| `all.js` | `clay.loaded.all` | `All(selector)` collection helper | [clay-events, clay-dom and All](https://clayjs.com/docs/guide/components/#clay-events-clay-dom-and-all) |
| `clay-utils.js` | `clay.loaded.utils` | `clay.utils.debounce`, `throttle`, `cookie.get/remove`, `slugify`, `copyToClipboard` | |
| `clay-internals.js` | `clay.loaded.internals` | `clay.internals`: capture, transforms, raw save | |
| `clay-data.js` | `clay.loaded.data` | `clay.extractData`, `clay.applyData` | [Import and export](https://clayjs.com/docs/guide/architecture/#import-and-export) |
| `sap.js` | `clay.loaded.sap` | `Sap`, the reactive attribute layer | [sap.js: records, views and totals in markup](https://clayjs.com/docs/guide/components/#sap-js-records-views-and-totals-in-markup) |

Satellites do not wait for `clay.ready` and work without `clay.js`, including in view mode and on a static host. In code that runs early, await the promise before using what it adds (`await clay.loaded.ui` before `clay.toast`). Inside a `clay.ready` callback, satellites have usually finished loading, so `clay.toast?.(...)` is safe and the optional call costs nothing.

`clay.standalone.js` bundles the loader, every plugin, and every satellite into one 2 MB file for offline use. `plugins=` still decides what runs. With it, remove the satellite tags and always `await clay.loaded.sap` or `.data` before use.

In Hyperclay Local and HTML Clay, a document needs no special loading to work offline. Keep the ordinary `https://clayjs.com/v1/` tags and the host serves them from its bundled release. Use `clay.standalone.js` for a file opened with no host at all. [Offline in a local host](https://clayjs.com/docs/guide/hosts/#offline-in-a-local-host) covers the route and what a host must do.

## `clay.ready`

`clay.ready` is a promise that resolves with `window.clay` once boot is done and every plugin has loaded. `clay:ready` fires on `document` at the same moment. Before it resolves, members like `clay.save` and `clay.undo` do not exist. Start app code with `clay.start(...)`: it waits for `clay.ready` and runs once per page ([Starting the app once](https://clayjs.com/docs/guide/architecture/#starting-the-app-once)).

`clay.plugins` is the frozen list of plugin names in the plan the loader resolved, late plugins included. It says what the page asked for, not what has finished loading.

`clay.ready` means the loader is done, not that a plugin has finished its own startup. The `page` plugin keeps going: it exports no `ready`, so `clay.ready` fires while the person still sees the article's saved content. `clay.page.ready` is that plugin's boot, and `clay.page.status` is its current result, `{ phase, reason, installId, at }`. Await it before touching `clay.page`:

```js
clay.start("document-ui", async () => {
  const boot = await clay.page?.ready;
  if (boot?.phase !== "ready") return;
  document.documentElement.setAttribute("document-ready", "true");
});
```

Boot moves through `loading` (the saved article is read and validated), `joining` (the editor's code downloads beside the room join, plus capability, the first save, and the page handle), and `adopting` (the editor is mounted and the saved article is adopted into the room). It then settles at `ready` (input enabled), `blocked`, or `error`. Each boundary is a `performance.mark` carrying `{ detail: { installId } }`:

| Mark | Exact boundary |
|---|---|
| `document:boot` | an installation starts, before the source is validated |
| `document:assets-ready` | the editor's code is in hand |
| `document:authority-ready` | the capability and first-save gates passed, the page handle is admitted and the binding is created |
| `document:input-ready` | adoption succeeded and editable was enabled on the current mounted view |

A failed or cancelled installation never emits `document:input-ready`. Blocked readiness still resolves: `clay.page.ready` settles, `clay.page.status.phase` is `blocked` with a `reason`, the saved article stays as the host served it, and no editable page appears. [When the page editor is ready](https://clayjs.com/docs/guide/edit-mode/#when-the-page-editor-is-ready) covers the reasons and what a page can offer instead.

You can set configuration before the tag, and the loader merges into it: `window.clay = { saveToast: true }`.

## Theming ClayJS's own UI

ClayJS draws dialogs (`clay.ask`, `clay.confirm`, `clay.tell`, `clay.snippet`, `clay.modal`) and the `people` plugin's name prompt. By default they take your page's font. On a page with a plain solid background, they also take its background and text colours. Everything else comes from ClayJS's built-in look.

To match them to your design, set `window.clayTheme` before the loader tag:

```html
<script>
window.clayTheme = {
  tokens: { font: "Inter, sans-serif", text: "#263d4e", surface: "#ffffff", border: "#e0e7ee", accent: "#0c2635", accentText: "#ffffff", radius: "8px" },
  parts: { "dialog.title": { base: { "font-size": "18px", "font-weight": "600" } } }
};
</script>
<script src="https://clayjs.com/v1/clay.js?plugins=people"></script>
```

- **Tokens** set the basics: `font`, `headingFont`, `text`, `mutedText`, `background`, `surface`, `border`, `accent`, `accentText`, `danger`, `dangerText`, `success`, `warning`, `radius`, `shadow`, `overlay`, `spacing`, `buttonHeight`. Each is a CSS value, and `var(--your-variable, fallback)` works. A page can also set a token in CSS as `--clay-ui-<name>` (`--clay-ui-accent-text` for `accentText`). `spacing` is one length, such as `20px`.
- **Parts** set anything else. Every element ClayJS draws has a stable name in `data-clay-part`, such as `dialog.panel`, `dialog.title`, `button.primary`, or `people.choice`. Each takes `base`, `hover`, `active`, `focus`, and `disabled` declarations. A part names its surface, so `people.title` overrides `dialog.title` in the name prompt only. The full list is in `docs/injected-ui.md`.
- Values win in this order: `tokens`, then `--clay-ui-*` properties, then the page's sampled font and colours, then the built-in look.
- `window.clayTheme = false` keeps the exact built-in look. After loading, `clay.theme(options)` replaces the theme, `clay.theme(false)` and `clay.theme(null)` restore the built-in look or the default, and `clay.theme()` returns the current one. A change affects the next dialog, not one already open.

Your stylesheet cannot reach these elements: every declaration is inline and `!important`, so a stray `button { ... }` rule never breaks a prompt. On a phone, dialog buttons stay at least 44px tall regardless of the theme.

## Plugins that load late

`source`, `sortable`, and `menu` load after `clay:ready`, one at a time, in the order the plan resolves them. Nothing on first paint or the first keystroke waits for them. A late plugin that fails to load is logged and left absent, like any other.

Each late plugin fires `clay:plugin-ready` on `document` once its own `ready` settles, with `detail.plugin` naming it. Use this event to tune something the plugin built for itself. For example, a Kanban board waits for `sortable` and then sets the drag delay its cards need on a phone:

```js
document.addEventListener("clay:plugin-ready", (event) => {
  if (event.detail.plugin !== "sortable") return;
  for (const list of document.querySelectorAll("ol.cards")) {
    const sortable = window.Sortable?.get?.(list);
    sortable?.option("delay", 220);
    sortable?.option("delayOnTouchOnly", true);
  }
});
```

Do not read `window.Sortable` in a `clay.ready` callback: the plugin may not have arrived. `clay.plugins` tells you it is coming; `clay:plugin-ready` tells you it is here.

## Libraries on demand

`clay.assets.load(name)` loads a library from the same release that served `clay.js` the first time a page asks for it. The document needs no vendor URL, so a copy served by another host or opened from disk still finds it. The names are `overtype`, `marked`, and `highlight`, plus one grammar per `highlight/<language>` for `bash`, `css`, `javascript`, `json`, `python`, and `xml`. `clay.assets.names()` lists them.

```js
const { Marked } = await clay.assets.load("marked");
const { default: hljs } = await clay.assets.load("highlight");
const { default: javascript } = await clay.assets.load("highlight/javascript");
hljs.registerLanguage("javascript", javascript);
```

`overtype` resolves the vendored OverType editor with its toolbar buttons. `marked` resolves the `Marked` class. `highlight` resolves the core with no grammar of its own, so a page registers only the grammars it uses. Each name loads once: a second call before the first settles shares it, a failed one is forgotten and can be tried again, and an unknown name rejects with `clay.assets: no asset named "<name>"`. The [`window.clay` reference](https://clayjs.com/docs/api/) lists what each name resolves to.
