# The component library

> How the composer discovers your sections, what it reads out of each file, and the override block for the facts your source cannot state.

Source: https://editor.astrocraftthemes.com/docs/component-library/

The [page composer's](/docs/page-composer/) library lists **your** components. It can only ever name what already exists, which is the same promise the entry editor makes about [your schema](/docs/schema-fields/) — no `config.yml` tax, no registry to keep in step with a directory.

## Discovery

Two directories, named in [`src/admin.config.ts`](/docs/configuration/):

```ts
components: {
  sections: "src/components/sections",
  layouts: "src/layouts",
}
```

Both are walked recursively — files before subdirectories, each sorted — and read at request time. Both must exist even if empty, both are path-contained (a `..` would list your file tree on a screen), and both must live under `src/`, because a composed page imports through `@/*` and that is the only alias reaching an arbitrary directory.

A directory becomes a group in the library, so an emptied folder hides along with its children.

Sibling directories of your sections directory that also hold `.astro` files are listed as **not composable**, so you can see that they were considered rather than wondering where they went. That only applies when the parent is not `src/` itself.

## What it reads out of each file

**`interface Props`** — the declared props and their types. This is the contract half, and it is what a component author controls.

**The `Astro.props` destructure** — `const { wide = true } = Astro.props`. The frontmatter's last word on a prop, so a control nobody has touched can show what the component actually renders. A quoted string, a number, `true` or `false` is taken as the default; an expression, an array or an object is not. An enum takes a default only when the literal is one of its own options.

**`<slot />`** — a component that renders one is listed and **tagged**. It is still addable; the tag exists because a section with a slot the composer cannot fill would otherwise be a silent dead end.

The composer never imports one of your components. An `.astro` component's props do not survive to runtime, so importing would buy nothing and would breach the package boundary besides.

### How much this actually covers

Measured against four real Astro themes when the feature was designed: for AstroWind, one of twenty-two widgets was readable from its source; for ScrewFast, four of nineteen. But _per field, of what is in the file_, the parser reads 70–85%.

So coverage is bimodal **per component**, not per field: a component either declares its props inline and is almost fully readable, or centralises its types in another file and is barely readable at all. That shape is what the override block exists for. Resolving imported types with the TypeScript compiler was priced and refused — it buys only what a declaration already covers.

## Overrides

```ts
components: {
  sections: "src/components/sections",
  layouts: "src/layouts",
  overrides: {
    CallToAction: { wireframe: "cta" },

    Features: {
      category: "Content",
      wireframe: "list",
      props: [
        { kind: "string", name: "title", label: "Title", required: true, widget: "text" },
        {
          kind: "array",
          name: "items",
          label: "Items",
          required: true,
          of: {
            kind: "object",
            name: "items",
            label: "Item",
            fields: [
              { kind: "string", name: "name", label: "Name", required: true, widget: "text" },
              { kind: "string", name: "detail", label: "Detail", required: true, widget: "textarea" },
            ],
          },
        },
      ],
    },
  },
}
```

Keyed by the component's name — its filename without `.astro`. Every field is optional; **an override states only what is wrong.**

| Key         | Corrects                                                        |
| :---------- | :-------------------------------------------------------------- |
| `label`     | the library card's title; defaults to the component's name      |
| `category`  | its grouping; defaults to _Sections_                            |
| `wireframe` | the placeholder drawing; defaults to the generic block          |
| `props`     | the component's props, declared, when the source cannot be read |

Leave a component out entirely and it still appears, drawn with the generic wireframe and whatever its `interface Props` declares. You add an entry only to correct or complete it.

**Wireframes are derived from the component's name** where that works — `Hero.astro` finds `hero`. `CallToAction` finds nothing, because "calltoaction" is not a drawing, hence the one-line override above. `Features` finds nothing either, and what that section actually looks like is a list.

**`props` is the declaration road.** `Features` types its `items` as an imported interface, so the parser reaches `title` and stops. One entry and the composer can edit the cards. This is the shape to copy when your own sections centralise their types in a `types.ts`.

On an array, `required` means the **prop must be present**, not that the list must be non-empty. `items={[]}` renders an empty list; `<Features />` throws at render and fails your build. Adding a required array seeds it with `[]` so the requirement is satisfiable the moment the section exists.

## Layouts and the SEO tab

A page layout can declare which of its props are the search title, the search description and the link picture — the **same five key names** a collection uses, because it is one declaration shape rather than two:

```ts
BaseLayout: {
  titleField: "title",
  descriptionField: "description",
  socialImageField: "image",
  socialTitleField: "socialTitle",
  socialDescriptionField: "socialDescription",
},
```

That is what puts the SEO tab on the composer's rail, over a layout's props instead of a schema's fields.

It is a **declaration rather than a derivation** for one visible reason: this layout spells its picture prop `image`, and the next adopter's will not. Leave the block out and the composer simply shows no SEO tab for that layout, which is the honest answer — a control addressing `socialImage` on a layout that takes `image` would write a prop the component ignores, and that is a page that looks edited and is not.

## Name collisions

Two components with the same name in different directories are reported in the library. The declaration side already refuses a duplicate key.

## Troubleshooting

**A component shows no props at all.** Its `interface Props` is not in the file, or its types are imported. Declare them in an override.

**A control shows the wrong default.** The `Astro.props` destructure default is only read for a literal. `const { size = SIZES.md }` is an expression and carries nothing.

**An enum control has no default although the component sets one.** The literal has to be one of the enum's own options. A mistyped default carries nothing rather than adding an option.

**A component is missing from the library.** It is not under one of the two configured directories, or those directories are wrong in `admin.config.ts`. Both paths are repo-relative and must be under `src/`.

**A section renders as a labelled placeholder in the canvas.** Its file is gone, or it was added in a commit this deployment's bundle predates. The row survives on purpose, so the counter does not lie.