> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mauvely.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom code blocks

> Write your own interaction in HTML, CSS and JavaScript. How the sandbox works, how theme auto-styling works, and what a custom block can and cannot do.

The **Custom code** block lets you author an interaction Compose does not ship. You write HTML,
CSS and JavaScript; the exported course runs it in a sandboxed frame.

<Note>
  This is a first-class block, not an escape hatch. It exports the same way every other block does
  and runs inside any LMS that runs the rest of the course.
</Note>

## What you author

| Field            | Contents                                                                |
| ---------------- | ----------------------------------------------------------------------- |
| **HTML**         | The body of your block                                                  |
| **CSS**          | Styles for it                                                           |
| **JavaScript**   | The block's script                                                      |
| **Height**       | The frame's height in pixels; leave at 0 for the default                |
| **Auto styling** | Whether Compose injects the course theme into your frame. On by default |

## The sandbox

In the exported course, a custom block runs inside an isolated `<iframe>`. It inherits **nothing**
from the surrounding page — not the course stylesheet, not its scripts, not its variables.

That is deliberate. A broken or heavyweight custom block cannot take the course down with it, and
a course containing an author's experiment still renders and still reports completion.

The practical consequences:

* You cannot read or write the learner's progress from a custom block.
* You cannot reach the course's JavaScript or the SCORM API.
* A custom block does not gate navigation and does not contribute to the score.
* Anything you need must be inside the block's own HTML, CSS and JavaScript.

### The block cannot reach the network

<Warning>
  **A custom block has no network access at all.** No `fetch`, no `XMLHttpRequest`, no remote
  images, no scripts or stylesheets from a CDN, no web fonts from a URL.
</Warning>

Isolation stops a custom block reaching the *course*. It does nothing about the outside world — so
without this, a block copied from the internet could quietly call home from inside a course you
believed was self-contained, and your LMS would carry it to every learner.

So the exported frame declares a content policy that permits only what a self-contained block
needs: its own inline script and styles, and `data:` images, fonts and media.

If your block needs an asset, **import it into the course** and reference it. Compose embeds
`asset://` references directly into the block when it exports, so they work inside the frame
without a network request:

```html theme={null}
<img src="asset://your-image-key">
```

Pick images through the media picker rather than typing a path — see
[Files and media](/compose/saving-and-files). Embedded assets are capped at **512 KB** each,
because they end up base64-encoded inside the exported page; anything larger is left alone and
reported as an export warning.

<Note>
  A block that genuinely needs live data — a stock ticker, a shared leaderboard — cannot be built
  this way. That is the trade for a course that runs in an offline LMS and cannot leak anything.
</Note>

## Auto-styling

With **Auto styling** on — the default — Compose injects the course's theme values into the frame
before your CSS runs:

* CSS custom properties for the course's accent, background, surface and text colours, corner
  radius and typefaces
* Sensible base styles keyed off them: typography, links in the accent colour, buttons, inputs and
  code

So a custom block looks like it belongs in the course without you restyling everything. **Your CSS
is applied after**, so anything you set always wins.

Turn auto-styling off and the frame gets a minimal baseline instead — a system font and a small
margin — which is what you want if you are bringing a complete design of your own.

```css theme={null}
/* With auto styling on, these are already defined for you */
.my-button {
  background: var(--accent);
  border-radius: var(--radius);
  font-family: var(--font-body);
}
```

## In the editor

The canvas and the preview panel show a **static code panel** for a custom block, not the running
frame. To see it actually run, use **Preview** — the preview is the real exported course, so your
block executes there exactly as it will for a learner.

## Sharing a custom block

**Edit ▸ Share block as file…** writes any block, including a custom code block, to a small
`.ceblock` file. Blocks saved into your Blocks folder appear in the **Blocks** tab of the left
panel, ready to insert into any course.

Because a custom code block carries its own HTML, CSS and JavaScript, a shared `.ceblock` is
effectively a portable interaction — the closest thing Compose has to a plugin.

See [Templates and reusable pieces](/compose/templates).

## Things worth knowing

<AccordionGroup>
  <Accordion title="Keep it compatible with older browsers" icon="browser">
    Courses often run inside an LMS's embedded viewer, which can be an old engine. The course
    runtime Compose ships is deliberately conservative for that reason; your custom block should be
    too if your learners are on managed machines.
  </Accordion>

  <Accordion title="Nothing external will be there" icon="wifi-slash">
    A SCORM package is often served from an LMS with no outbound internet, or opened from disk.
    Anything your block fetches from a CDN will simply not load. Inline what you need.
  </Accordion>

  <Accordion title="Validation" icon="triangle-exclamation">
    Compose flags an empty custom code block in the status strip. It cannot check whether your code
    works — use **Preview** for that.
  </Accordion>
</AccordionGroup>
