---
title: Artifacts
description: Add side-panel content that opens from inline previews in chat.
---

Artifacts let a component render a compact inline preview inside the chat message and expand into a full side panel when clicked. Use them for code viewers, document previews, embedded frames, or any content that benefits from a larger canvas.

```tsx
import { defineComponent } from "@openuidev/react-lang";
import { Artifact } from "@openuidev/react-ui";
import { z } from "zod";

const ArtifactCodeBlock = defineComponent({
  name: "ArtifactCodeBlock",
  props: z.object({
    language: z.string(),
    title: z.string(),
    codeString: z.string(),
  }),
  description: "Code block that opens in the artifact side panel",
  component: Artifact({
    title: (props) => props.title,
    preview: (props, { open, isActive }) => (
      <CodeChip title={props.title} language={props.language} onClick={open} isActive={isActive} />
    ),
    panel: (props) => (
      <SyntaxHighlighter language={props.language}>{props.codeString}</SyntaxHighlighter>
    ),
  }),
});
```

## How it works

An artifact component has two parts:

- **Preview** — a compact element rendered inline in the chat message. It receives an `open` callback to activate the side panel.
- **Panel** — the full content rendered inside `ArtifactPanel`, portaled into the `ArtifactPortalTarget` in your layout. Only one panel is visible at a time.

`Artifact()` is a factory function that wires these together. It generates a `ComponentRenderer` that handles ID generation, artifact state, and panel portaling internally. Pass the result as the `component` field of `defineComponent`.

## `Artifact()` config

```ts
import { Artifact } from "@openuidev/react-ui";

Artifact({
  title, // string | (props) => string
  preview, // (props, controls) => ReactNode
  panel, // (props, controls) => ReactNode
  panelProps, // optional — className, errorFallback, header
});
```

| Option       | Type                                                  | Description                                              |
| ------------ | ----------------------------------------------------- | -------------------------------------------------------- |
| `title`      | `string \| (props: P) => string`                      | Panel header title. Static string or derived from props. |
| `preview`    | `(props: P, controls: ArtifactControls) => ReactNode` | Inline preview rendered in the chat message.             |
| `panel`      | `(props: P, controls: ArtifactControls) => ReactNode` | Content rendered inside the side panel.                  |
| `panelProps` | `{ className?, errorFallback?, header? }`             | Optional overrides forwarded to `ArtifactPanel`.         |

Both `preview` and `panel` receive the full Zod-inferred props as the first argument and `ArtifactControls` as the second.

## `ArtifactControls`

The controls object passed to `preview` and `panel` render functions.

```ts
interface ArtifactControls {
  isActive: boolean; // whether this artifact's panel is currently open
  open: () => void; // activate this artifact
  close: () => void; // deactivate this artifact
  toggle: () => void; // toggle open/close
}
```

The preview typically uses `open` and `isActive` to show a click-to-expand button. The panel can use `close` to render a dismiss button inside the panel body.

## Layout setup

Built-in layouts (`FullScreen`, `Copilot`, `BottomTray`) mount `ArtifactPortalTarget` automatically. Artifact panels render into this target with no extra setup.

If you build a custom layout with the headless hooks, mount one `ArtifactPortalTarget` in your layout where the panel should appear.

```tsx
import { ArtifactPortalTarget } from "@openuidev/react-ui";

function Layout() {
  return (
    <div className="flex h-screen">
      <main className="flex-1">{/* chat area */}</main>
      <ArtifactPortalTarget className="w-[480px]" />
    </div>
  );
}
```

Only one `ArtifactPortalTarget` should be mounted at a time. All artifact panels portal into this single element.

## Headless hooks

For custom layouts or advanced control, use the artifact hooks from `@openuidev/react-headless`.

### `useArtifact(id)`

Binds a component to a specific artifact by ID. Returns activation state and actions.

```ts
import { useArtifact } from "@openuidev/react-headless";

const { isActive, open, close, toggle } = useArtifact(artifactId);
```

### `useActiveArtifact()`

Returns global artifact state — whether any artifact is open, and a close action. Use this in layout components that resize or show overlays when any artifact is active.

```ts
import { useActiveArtifact } from "@openuidev/react-headless";

const { isArtifactActive, activeArtifactId, closeArtifact } = useActiveArtifact();
```

Both hooks require a `ChatProvider` ancestor in the component tree.

## Manual wiring

If `Artifact()` does not fit your use case, wire the pieces directly. This is the escape hatch for full control.

```tsx
import { defineComponent } from "@openuidev/react-lang";
import { ArtifactPanel } from "@openuidev/react-ui";
import { useArtifact } from "@openuidev/react-headless";
import { useId } from "react";

const CustomArtifact = defineComponent({
  name: "CustomArtifact",
  props: CustomSchema,
  description: "Artifact with full manual control",
  component: ({ props }) => {
    const artifactId = useId();
    const { isActive, open, close } = useArtifact(artifactId);

    return (
      <>
        <button onClick={open}>{isActive ? "Viewing" : "Open"}</button>
        <ArtifactPanel artifactId={artifactId} title="Custom">
          <div>{/* panel content */}</div>
        </ArtifactPanel>
      </>
    );
  },
});
```

`ArtifactPanel` accepts `artifactId`, `title`, `children`, `className`, `errorFallback`, and `header` (boolean or custom ReactNode). It renders nothing when the artifact is inactive.

## Related guides

<Cards>
  <Card title="Defining Components" href="/docs/openui-lang/defining-components">
    Create custom openui-lang components with `defineComponent`.
  </Card>
  <Card title="Custom UI Guide" href="/docs/chat/custom-ui-guide">
    Build a fully custom chat UI with headless hooks.
  </Card>
  <Card title="Headless Hooks" href="/docs/chat/hooks">
    Full reference for all headless hooks.
  </Card>
  <Card title="Theming" href="/docs/chat/theming">
    Adjust colors, mode, and theme overrides.
  </Card>
</Cards>
