# Your own components

> Teach the model your product's own components. Describe one once, give it to the API and the page, and say how to draw it.

Next to the built-in components, an app can add its own: a shop's product card, a booking's seat map, a support ticket. It takes three steps, and the same description serves all three: the model learns it from the prompt, the core checks answers against it, and your page draws it.

## Describe it

A component lives in a pack: a name, one line on what it's for, its attributes, and what goes between its tags.

```json
{
  "name": "shop",
  "version": "1.0.0",
  "components": {
    "ProductCard": {
      "about": "a product with its price",
      "attrs": {
        "name": { "type": "string", "required": true },
        "price": { "type": "string", "required": true },
        "stock": ["in", "low", "out"]
      },
      "content": "none"
    }
  }
}
```

Attribute types are `string`, `number`, `boolean`, `url`, `list` (values split by ` | `), or a list of the values allowed, the first being the default. Content is `none`, `text`, `table`, `list`, `images` or `any` (Markdown and other components). One attribute, one meaning: `stock="low"`, not `style="color: orange"`.

A `text` component can take `"actions": true`. The model may then put `Button`s inside it, each with `ask` (the message a tap sends) or `href`, drawn under it as what the reader can do, like "Add to cart".

## Give it to the API

The API puts the pack's components in the system prompt and checks the answer against them. Send it from your server with every call:

```js
{ "model": "deepseek/deepseek-v4.1-flash", "messages": [...], "morphscreen": { "components": [shop] } }
```

```js
run.forwardedProps = { ...run.forwardedProps, model: MODEL, components: [shop] };
```

## Draw it

Give the page the same pack, and a function that draws the component into an element. It gets the component's attributes, and its content as HTML and as text; it may return a cleanup.

```ts
import { createAnswer } from "@morphscreen/web";
import shop from "./shop.json";

createAnswer(el, {
  core,
  custom: [shop],
  components: {
    ProductCard: (el, { attrs }) => {
      el.textContent = `${attrs.name}: ${attrs.price}`;
    },
  },
});
```

With React, pass components:

```tsx
import { Answer, type CustomProps } from "@morphscreen/web/react";
import shop from "./shop.json";

function ProductCard({ attrs }: CustomProps) {
  return (
    <div className="product">
      <strong>{attrs.name}</strong> {attrs.price}
    </div>
  );
}

<Answer core={core} text={text} custom={[shop]} components={{ ProductCard }} />;
```

`connectAgent` takes `custom` and `components` the same way.

## Your styles, untouched

Your component is drawn in a `<div class="ms-custom">` inside the answer. MorphScreen's stylesheet leaves everything in it to yours: it only inherits the answer's font and colour.

## When something's missing

A component your page doesn't draw still shows: its attributes as a list, then its content. Nothing an answer holds is dropped without a word.

## Names

A name defined twice is an error, never an override, so your pack can't replace a built-in component by accident. The one exception: when you leave `packs` out (every pack), your own `Product` or `Map` replaces the pack's. Name the pack outright and the two clash.
