# Component overrides

The generated pages are Astro components. Each one renders a normalised model of your package, and
you can replace nine of them with your own components. Do this when you need different markup: an
extra badge in every signature, or a warning above every abstract class. If you only want different
colours or spacing, use [Theming](/starlight-pydocs/guides/theming/) instead. Theming needs CSS and
no components.

An override applies everywhere its component is used. This includes the generated pages, the
[`<Autodoc>`](/starlight-pydocs/guides/autodoc/) blocks you embed in hand-written pages, and other
overrides.

## Registering an override

Name the component and point at a file. A value starting with `.`, or an absolute path, resolves
against the Astro project root. Any other value is passed through as a package specifier. This lets
an override ship from your own npm package.

```js title="astro.config.mjs" {3-6}
starlightPydocs({
  packages: [{ name: 'mypkg', search: ['../src'] }],
  components: {
    SourceLink: './src/components/pydocs/SourceLink.astro',
    Signature: '@internal/docs-theme/PySignature.astro',
  },
});
```

The components themselves can live anywhere in your project. This page keeps them together:

- astro.config.mjs
- src/
  - components/
    - pydocs/
      - **ClassDoc.astro** wraps the built-in renderer
      - **SourceLink.astro** replaces it outright
- package.json

An unknown component name causes a configuration error, and the error message lists the nine valid
names. This way, a typo fails the build instead of being ignored.

## Wrapping the built-in component

The built-in components are exported from `starlight-pydocs/components`, so an override can add to
one rather than reimplement it. Importing the original there is safe. It bypasses the override
mechanism, so there is no loop.

A useful override often needs only three things: an import, the markup you add, and one line that
hands the rest back. Here, the override adds a warning above every abstract class:

```astro title="src/components/pydocs/ClassDoc.astro" {2, 15, 18-22}
---
import { ClassDoc } from 'starlight-pydocs/components';
import type { RenderScope } from 'starlight-pydocs/render';

// `DocObject` is the shape of every documented object, reachable through the scope type.
type DocObject = RenderScope['model']['root'];

interface Props {
  doc: DocObject;
  scope: RenderScope;
  labels?: Record<string, string> | undefined;
}

const { doc, scope, labels } = Astro.props;
const isAbstract = doc.labels.includes('abstract');
---

{
  isAbstract ? (
    <aside class="pyd-aside pyd-aside--caution">Abstract: subclass it, do not instantiate it.</aside>
  ) : null
}
<ClassDoc doc={doc} scope={scope} labels={labels} />
```

The last line hands everything else back to the built-in renderer, including `labels`. This keeps
the class bodies rendering exactly as they did before.

Overrides are compiled by your own project, so `astro check` covers them. Declare the props exactly
as listed below. Then the check is meaningful: an override with a missing prop fails at build time
instead of rendering a blank block.

## The nine components

| Name                | Renders                                                                 | Props                                            |
| ------------------- | ----------------------------------------------------------------------- | ------------------------------------------------ |
| `ModuleDoc`         | A whole module page: docstring, member summary, submodule list, members | `doc`, `scope`, `headingLevel?`, `labels?`       |
| `ClassDoc`          | A class body: signature, bases, docstring sections                      | `doc`, `scope`, `labels?`                        |
| `FunctionDoc`       | A function or method body: signature and docstring sections             | `doc`, `scope`, `labels?`                        |
| `AttributeDoc`      | An attribute or property body                                           | `doc`, `scope`, `labels?`                        |
| `Signature`         | The signature block, including `@overload` variants                     | `doc`, `scope`, `labels?`, `class?`              |
| `DocstringSections` | Every parsed docstring section, in docstring order                      | `doc`, `scope`, `labels?`                        |
| `MemberSummary`     | The table of members at the top of a page or class                      | `doc`, `scope`, `labels?`                        |
| `SourceLink`        | The `View source` link                                                  | `doc`, `labels?`                                 |
| `Heading`           | An anchor heading with its permalink                                    | `level`, `id`, `class?`, `labels?`, default slot |

Everything else is fixed. The object heading, badges, provenance line and deprecation notice come
from `ObjectDoc`, which also handles the recursion into members. `<Autodoc>` and `<SymbolSearch>`
are entry points, not pieces you can replace. Style those with CSS instead.

## The props

`doc` is one documented object and `scope` is everything needed to render it. The `doc` fields you
are most likely to use:

| `doc` field                       | Meaning                                                                 |
| --------------------------------- | ----------------------------------------------------------------------- |
| `path`, `canonicalPath`, `name`   | Documented path (also the heading id), definition path, short name      |
| `kind`                            | `module`, `class`, `function`, `attribute` or `alias`                   |
| `labels`                          | Griffe's labels: `property`, `classmethod`, `pydantic-model`, …         |
| `summary`, `docstring`            | First docstring line as plain text; the raw text plus parsed sections   |
| `members`, `groups`               | Filtered members, and the same members bucketed for rendering           |
| `bases`, `mro`, `unresolvedBases` | Declared bases with resolution, the linearisation, what did not resolve |
| `inheritedFrom`, `reexportedFrom` | Provenance, when the object is not defined where it is documented       |
| `deprecated`                      | `{ version, description }` when anything marks it deprecated            |
| `source`                          | `{ file, startLine, endLine, href }`                                    |
| `pageSlug`                        | The page the object is rendered on                                      |

| `scope` field | Meaning                                                                        |
| ------------- | ------------------------------------------------------------------------------ |
| `context`     | Site configuration: `siteBase`, `trailingSlash`, `packages`, the feature flags |
| `pkg`         | The package being rendered                                                     |
| `model`       | The whole normalised package: pages, `objectsByPath`, `symbolsByPath`, scopes  |
| `resolver`    | Annotation name resolver, wired to the site's inventories                      |
| `rendered`    | Pre-rendered docstring HTML, keyed by canonical path and section index         |

`labels` is a `StringOverrides` object. You can pass it to any component to override individual
strings. Pass it down so overrides keep working through your component.

`starlight-pydocs/render` exports the helpers the built-in components use, so an override rarely has
to compute anything itself: `hrefForPath`, `hrefForTarget`, `pageHref`, `packageAssetHref`,
`objectBadges`, `splitInherited`, `admonitionKind`, `admonitionTitle` and `createRenderScope`.

## Replacing a component outright

When the built-in markup is not the starting point you want, declare the same props and render
whatever you like. For example, make the `View source` link carry a forge icon and open in a new
tab:

```astro title="src/components/pydocs/SourceLink.astro" {18-19}
---
import type { RenderScope } from 'starlight-pydocs/render';

// `DocObject` is the shape of every documented object, reachable through the scope type.
type DocObject = RenderScope['model']['root'];

interface Props {
  doc: DocObject;
  labels?: Record<string, string> | undefined;
}

const { doc } = Astro.props;
const source = doc.source;
---

{
  source?.href === undefined ? null : (
    <a class="pyd-source-link" href={source.href} target="_blank" rel="noreferrer noopener">
      <svg aria-hidden="true" width="14" height="14" viewBox="0 0 16 16"><path d="M8 0a8 8 0 0 0-2.5 15.6c.4.1.5-.2.5-.4v-1.4c-2.2.5-2.7-1-2.7-1-.4-1-.9-1.2-.9-1.2-.7-.5 0-.5 0-.5.8.1 1.2.8 1.2.8.7 1.2 1.9.9 2.4.7.1-.5.3-.9.5-1.1-1.8-.2-3.6-.9-3.6-4 0-.9.3-1.6.8-2.1-.1-.2-.4-1 .1-2.1 0 0 .7-.2 2.2.8a7.7 7.7 0 0 1 4 0c1.5-1 2.2-.8 2.2-.8.5 1.1.2 1.9.1 2.1.5.5.8 1.2.8 2.1 0 3.1-1.8 3.8-3.6 4 .4.3.7.9.7 1.8v2.6c0 .2.1.5.6.4A8 8 0 0 0 8 0Z" /></svg>
      {' '}
      Source
    </a>
  )
}
```

Register it under its component name:

```js title="astro.config.mjs" {3}
starlightPydocs({
  packages: [{ name: 'mypkg', search: ['../src'] }],
  components: { SourceLink: './src/components/pydocs/SourceLink.astro' },
});
```

Every object on every generated page, and every `<Autodoc>` block, now uses it.

[Theming](/starlight-pydocs/guides/theming/)
  [Internationalisation](/starlight-pydocs/guides/i18n/)