# Introduction
URL: /remote-components/docs

Show an HTML page from another origin inside your app. It runs in a sandboxed iframe and lays out like an inline element.

> For AI agents: a documentation index is available at [llms.txt](/llms.txt). Use `.md` for canonical markdown pages; `.mdx` is kept as a backwards-compatible alias on supported URL paths.

> [!info]
>
> The packages are not on npm yet. In the assistant-ui monorepo, depend on them with `workspace:*`.

A remote component is an HTML document with one script in it, `rc-runtime.js`. Your app renders it with `<RemoteComponent>`. The page gets its width from your layout and its height from its content. It inherits your fonts and colors, reads your props and calls your functions.

Any HTML works. A page with no doctype, a fragment, a page a model just wrote, a built React app. The page is the component, and its `<html>` element is the box in your layout.

## Install

```bash
npm install @assistant-ui/remote-components-host-react @assistant-ui/remote-components-serve
```

The host package goes in the app that renders remotes. The serving layer goes on the server that serves them.

## Quickstart

### 1. Write a page

Plain HTML. Props are on `rc.props`, function props call back into your app, and `props-changed` fires when they change. No build step.

widgets/rsvp.html

```html
<!-- No doctype, no build step. The serving layer adds the doctype and the runtime. -->
<style>
  .event { font-weight: 600; }
  .when { color: var(--color-text-muted); }
  .row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
  button { font: inherit; color: inherit; height: 28px; padding: 0 10px; border: 1px solid var(--color-border); border-radius: var(--radius-sm); background: var(--color-surface); cursor: pointer; }
  button:hover { background: var(--color-surface-muted); }
  button:focus-visible { outline: 2px solid var(--focus-ring, var(--color-accent)); outline-offset: 2px; }
</style>

<div><span class="event">Team lunch</span> <span class="when">Friday, 12:30</span></div>
<div class="row">
  <button value="yes">Going</button>
  <button value="no">Can't make it</button>
</div>

<script>
  const render = ({ event = "Team lunch", when = "Friday, 12:30" }) => {
    document.querySelector(".event").textContent = event;
    document.querySelector(".when").textContent = when;
  };
  render(rc.props);
  rc.addEventListener("props-changed", () => render(rc.props));
  for (const button of document.querySelectorAll("button")) {
    button.onclick = () => rc.props.onReply(button.value);
  }
</script>
```

### 2. Serve it

The serving layer adds the doctype, the runtime tag and a Content Security Policy to every page under `root`. It also serves `rc-runtime.js`.

```
import http from "node:http";
import { remoteComponentsMiddleware } from "@assistant-ui/remote-components-serve/node";

const serve = remoteComponentsMiddleware({ root: new URL("./widgets", import.meta.url).pathname });

http
  .createServer((req, res) => serve(req, res, () => res.writeHead(404).end()))
  .listen(5173, "127.0.0.1");
```

Serve remotes from a different site than your app. In development, use `127.0.0.1` for remotes and `localhost` for the app.

### 3. Render it

```
import { useState } from "react";
import { RemoteComponent } from "@assistant-ui/remote-components-host-react";

export function Lunch() {
  const [reply, setReply] = useState<string | null>(null);
  return (
    <>
      <RemoteComponent
        src="http://127.0.0.1:5173/rsvp.html"
        event="Design sync"
        when="Thursday, 10:00"
        onReply={async (answer: string) => setReply(answer)}
      />
      <span>onReply({reply})</span>
    </>
  );
}
```

This site has no second origin to serve pages from, so the examples in these docs pass the same documents with the `html` prop. They run in an opaque sandboxed frame with the runtime inlined.

\[interactive preview omitted]

Click Going. The page calls `onReply`, which runs in your app.

## With React

The fastest path is the [CLI](/remote-components/docs/cli). It builds one component file into one HTML file, with no config:

```
npx remote-components build rating.tsx
```

A React app is a page too. Add the runtime tag to its `index.html` and read props with `useProps()` from `@assistant-ui/remote-components-react`. In an existing Vite app, the [Vite plugin](/remote-components/docs/vite) turns each component file into its own page.

```
import { useState } from "react";
import { useProps } from "@assistant-ui/remote-components-react";

export default function Counter() {
  const { label, onChange } = useProps<{ label: string; onChange?: (n: number) => Promise<void> }>();
  const [n, setN] = useState(0);
  return (
    <button
      onClick={() => {
        setN(n + 1);
        void onChange?.(n + 1);
      }}
    >
      {label}: {n}
    </button>
  );
}
```

```
<RemoteComponent src="http://127.0.0.1:5173/counter/" label="Clicks" />
```

The counter below is the same component written as a plain page.

\[interactive preview omitted]

widgets/counter.html

```html
<button id="count"></button>
<script>
  const button = document.querySelector("#count");
  let n = 0;
  const render = () => (button.textContent = `${rc.props.label ?? "Clicks"}: ${n}`);
  render();
  rc.addEventListener("props-changed", render);
  button.onclick = () => {
    n += 1;
    render();
    void rc.props.onChange?.(n);
  };
</script>
```

## No server

You can skip the server. `<RemoteComponent html={…} />` shows a string of HTML in an opaque frame, with the runtime inlined. See [Generated HTML](/remote-components/docs/html).

## Packages

| Package                                      |                                                                                    |
| -------------------------------------------- | ---------------------------------------------------------------------------------- |
| `@assistant-ui/remote-components-runtime`    | `rc-runtime.js`, the script inside the page. It exposes `window.rc`. No framework. |
| `@assistant-ui/remote-components-host-react` | `<RemoteComponent>`, the provider and overlays.                                    |
| `@assistant-ui/remote-components-serve`      | Normalizes any HTML into a remote. Node middleware at `/node`.                     |
| `@assistant-ui/remote-components-react`      | Optional hooks for React pages.                                                    |
| `@assistant-ui/remote-components-cli`        | `remote-components build`: one component to one HTML file.                         |
| `@assistant-ui/remote-components-vite`       | Optional Vite plugin.                                                              |
| `@assistant-ui/remote-components-protocol`   | Messages, value codec, channel and validation. Internal.                           |

Next: [CLI](/remote-components/docs/cli) builds a React component. [Pages](/remote-components/docs/pages) covers what a page can do. [Layout](/remote-components/docs/layout) covers how it fits.