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

# Add a component

> Build a renderer for a new section type, register it, and put it on a page. What each part of a renderer is for.

A component is a **type name** plus a **renderer**. The row stores `{ id, type, props }`; the page template looks the type up in the registry and hands the renderer the props. The renderer emits HTML with anchors on it, and those anchors are what make it editable. There is no field schema to write: the fields are whatever the renderer anchors.

## 1. Build the renderer

`src/components/<area>/Callout.astro`. Group by where it lives in the site: `home/`, `about/`, `careers/`, `global/`.

```astro theme={null}
---
import { sanitizeHtml } from "@sp-stage/sdk";
import { stageAttrs, stagePath } from "../../lib/stage-anchors";
import Media from "../global/Media.astro";

interface Props {
  eyebrow?: string;
  heading?: string;
  body?: string;          // rich text, stored as HTML
  image?: string;         // CDN URL or ""
  imageVideo?: string;    // paired with image; one of the two is set
  index?: number;         // position on the page, from the template
  _stageId?: string;      // the component id, from the template
  _stageType?: string;    // "callout", from the template
}
const { eyebrow, heading, body, image, imageVideo, index = 0, _stageId, _stageType } = Astro.props;
---

<section class="mx-auto max-w-content px-6 py-24" id={`callout-${index}`} {...stageAttrs(_stageId, _stageType)}>
  {eyebrow && <p class="text-sm uppercase text-muted" {...stagePath("props.eyebrow")}>{eyebrow}</p>}
  <h2 class="mt-2 text-3xl text-foreground" {...stagePath("props.heading")}>{heading}</h2>
  <div class="prose mt-6" set:html={sanitizeHtml(body)} {...stagePath("props.body", { rich: true })} />
  <Media image={image} video={imageVideo} path="props.image" alt="" class="mt-10 aspect-video w-full object-cover" sizes="(min-width: 1024px) 960px, 100vw" />
</section>
```

What each part does:

| Part                                 | Why                                                                                                                |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------ |
| `Props`                              | One optional prop per editable field. Everything is optional because a fresh section has no content yet            |
| `_stageId`, `_stageType`, `index`    | Passed in by the page template. The renderer only forwards the first two to the wrapper                            |
| `stageAttrs` on the wrapper          | Tells the editor which component a click belongs to                                                                |
| `stagePath("props.heading")`         | Marks the element that holds the text. Paths are relative to the component, so the renderer never knows its own id |
| `{ rich: true }` with `sanitizeHtml` | The field is HTML. Inline marks survive, scripts do not. Sanitised on render and again on save                     |
| `<Media>`                            | An image or video slot. Renders a clickable picker even when empty. Never a hand-written `<img>`                   |
| Tailwind against the tokens          | `text-foreground`, `bg-background`, `max-w-content`. A rebrand is a token change, not a markup change              |

Two rules that matter in edit mode:

* **Render the stored value as it is.** No truncating, no reformatting. The editor compares what is on screen with what is stored.
* **Anchor the element that holds the text**, not a parent. That exact element becomes editable.

A field the design does not show, such as a link target or a toggle, has no anchor. It is edited from the section's cog instead. See [Page schemas](/developers/structure/schemas#a-schema-entry).

## 2. Register it

`src/lib/components.ts` maps the stored type to the renderer. The key is exactly what the row will store.

```ts theme={null}
import Callout from "../components/about/Callout.astro";

const registry = {
  hero: Hero,
  callout: Callout,
  // …
};
```

An unknown type renders nothing, so a typo here is a blank section, not an error.

## 3. Put it on a page

Two ways, and a page can use both.

**Fixed.** Always there, editors cannot remove it. One line under the page's `sections`:

```ts theme={null}
about: {
  title: "About",
  sections: {
    hero:    { type: "hero" },
    callout: { type: "callout", props: { eyebrow: "Our approach", heading: "", body: "", image: "", imageVideo: "" } },
  },
},
```

The key becomes the component's id. `props` are what the section holds until someone edits it.

**Addable.** Editors add it from the tab on the right, and can reorder and remove it. An entry in the page's `palette`:

```ts theme={null}
"landing/*": {
  palette: [
    { type: "callout", label: "Callout", starter: { eyebrow: "", heading: "", body: "", image: "", imageVideo: "" } },
  ],
},
```

`starter` is what a fresh block stores. Text fields start as `""`. A media slot is the pair `image` and `imageVideo`, both `""`.

## 4. Run dev

A fixed section renders on the next dev start, empty until someone types. A palette entry appears on the tab in Edit mode. Either way, click a field in `/admin` and it is editable.

## If the component has a list

A row of logos, cards or team members inside the component is an array. Its items are editable with anchors. Adding, reordering and removing need one schema entry. See [Make a list addable](/developers/recipes/make-a-list-addable).

## If it renders on two pages

A value with one home shown in two places, such as a stat on the home page that belongs to a case study, is anchored with `stageRef` instead of `stagePath`. The editor saves to the one row. See [Shared content](/developers/editing/components#shared-content).

## Next steps

<CardGroup cols={2}>
  <Card title="Components" href="/developers/editing/components">
    The full renderer contract.
  </Card>

  <Card title="Edit anchors" href="/developers/editing/anchors">
    Every attribute and helper.
  </Card>
</CardGroup>
