---
title: Images and files
description: Where images go, how to reference them so they survive the build, Frame, theme variants, and non-image files.
---

This page covers the mechanics: where to put images, the one reference form that works,
and what happens to files that are not images. For when to use a screenshot, how to size
it and how to keep it current, see
[How to use images, screenshots and videos](/docs/guides/media).

## How Reed handles files

Reed copies your content directory into an Astro project it generates, then builds that
project. The output is **not** a byte-for-byte copy of your content directory. Two things
follow from that:

- **An image reaches the output only when a page references it with a relative markdown
  image.** The build processes it and writes it to `_astro/` under a hashed name. It may
  also convert it: a PNG can come out as `.webp`. SVG files keep their format.
- **Any other file in the content directory is not published.** A PDF, CSV or video next
  to your pages does not appear in the output, even if a page links to it.

## Add an image

Put the image inside your content directory, near the page that uses it:

```text
content/
  guides/
    webhooks.mdx
    images/
      webhook-settings.png
```

Reference it with markdown image syntax and a path relative to the page file:

```mdx
![The webhook settings page with the signing secret field highlighted](./images/webhook-settings.png)
```

The rendered `<img>` gets the processed file's URL, its intrinsic `width` and `height`,
and lazy loading. Because the file name changes at build time, never link to an image by
its original name from anywhere else.

### Forms that do not work

Two forms compile without an error and ship a broken image:

- **A root-relative path.** `/docs/guides/images/webhook-settings.png` is emitted as
  written, and no file exists at that URL. Reed does not serve the content directory at
  the site root.
- **An `<img>` tag with a local path.** `<img src="./images/webhook-settings.png" />` is
  not processed. The relative `src` is emitted unchanged, and the file is not in the
  output.

Use `<img>` only for images hosted at an absolute `https://` URL. It is useful there
because it accepts attributes markdown cannot, such as `width`:

```mdx
<img src="https://cdn.example.com/architecture.png" alt="Requests flow from the edge through the API to the queue" width="600" />
```

JSX rules apply: close the tag with `/>`.

### Link an image

Wrap the image syntax in a link:

```mdx
[![The dashboard's usage chart for the last 30 days](./images/usage-chart.png)](https://dashboard.example.com/usage)
```

## Alt text

Every image needs non-empty alt text. A markdown image with empty brackets, an `<img>`
without `alt`, and an `<img>` with `alt=""` all raise `DS-A01`, which is an error and
fails the build. There is no way to mark an image as decorative. If an image carries no
information, remove it.

The check only confirms that alt text exists. Whether it describes the image is up to you.
The [media guide](/docs/guides/media) and
[How to create accessible documentation](/docs/guides/accessibility) cover writing it.

## Frame an image

`Frame` puts an image in a bordered surface, with an optional `hint` above it and a
`caption` below it. The caption accepts inline markdown such as emphasis and links. Props
are in [Layout and surfaces](/docs/reference/components/layout).

**Put the markdown image directly inside the `Frame`.** Write it on its own line with
blank lines around it, or inline as `<Frame>![alt](./x.png)</Frame>`:

```mdx
<Frame caption="Signing secrets are shown once. Copy yours before closing the dialog.">

![The new signing secret dialog with the Copy button](./images/signing-secret.png)

</Frame>
```

It no longer needs a `<div>` around it. The image must be the paragraph's only content: an
image followed by a sentence in the same paragraph does not count as the child. An empty
`Frame` fails the page with `DS-S02`.

For an image at an absolute URL, an `<img>` is the child:

```mdx
<Frame caption="The request lifecycle.">
  <img src="https://cdn.example.com/lifecycle.png" alt="A request passing through auth, rate limiting and the handler" />
</Frame>
```

The caption is visible to everyone. It adds to the alt text; it does not replace it.

## Light and dark variants

Reed has no image prop or syntax for theme variants. What works is the utility classes
Starlight includes in every Reed build: `dark:sl-hidden` hides an element in the dark
theme, and `light:sl-hidden` hides it in the light theme.

Markdown images cannot take a class, so wrap each variant in a `<div>`:

```mdx
<div className="dark:sl-hidden">

![The API keys table](./images/api-keys-light.png)

</div>

<div className="light:sl-hidden">

![The API keys table](./images/api-keys-dark.png)

</div>
```

On an `<img>` with an absolute URL, put the class on the tag itself. Either `className` or
`class` works: MDX parses the tag as JSX, and the output maps `className` to `class`.

Both images are in the page's markup, and CSS displays one of them. Give both the same
alt text. These classes come from Starlight rather than from Reed's own component
catalog, so check the rendered page after upgrading Reed.

## Files other than images

Reed does not publish PDFs, archives, data files or other downloads from your content
directory. A relative link such as `[Download the guide](./files/guide.pdf)` builds
without an error, is emitted as written, and 404s, because the file is not in the output.

Host downloads somewhere built for them, such as your product's CDN, object storage or a
release page, and link to them by absolute URL:

```mdx
[Download the integration guide (PDF, 2 MB)](https://downloads.example.com/integration-guide.pdf)
```

Say the format and size in the link text, so the reader knows what the click does.

## Videos and embeds

Reed has no video component. Host the video on a platform built for it and link to it,
with a sentence saying what it covers and how long it runs.

An `<iframe>` or `<video>` with an absolute `src` is emitted into the page as written.
Reed does not check or style it. Write it as JSX, with camelCase attributes such as
`allowFullScreen`, and give an iframe a `title` so a screen reader can announce it:

```mdx
<iframe
  src="https://www.youtube.com/embed/VIDEO_ID"
  title="Setting up webhooks, a four-minute walkthrough"
  width="560"
  height="315"
  allowFullScreen
></iframe>
```

A local video file is not published, for the same reason as a PDF. Review the built page
before you rely on an embed, and put the information the reader needs in the page text
too.

## Logo and favicon

Your site's `logo` and `favicon` are not content. They are set in `docs.json`, with paths
relative to the directory `docs.json` is in, and `reed check` confirms the files exist.
See [Logo, favicon and custom CSS](/docs/design/assets).

## Related

- [How to use images, screenshots and videos](/docs/guides/media)
- [Layout and surfaces](/docs/reference/components/layout)
- [Diagnostics](/docs/reference/diagnostics)
