# Extending

Add your own utility families in mirror.config.ts, and narrow or drop ours by role rather than by class name.

The generator’s config never names a group, a rung or a state. It names roles,
token paths and the shape of the CSS, and everything else falls out of the
token tree, so adding a token gets you a class and adding a subtree gets you a
family.

You append emitters under `utilities.emitters`, and narrow or drop Mirror’s own
under `utilities.overrides`.

## An emitter

::docs-table
---
columns:
  - label: Key
  - label: Type
rows:
  - items:
      - label: role
        description: 'Role name, and the theme-key segment: `--mirror-<role>-<rung>`.'
      - label: string
  - items:
      - label: values
        description: Sub-namespace → dot path into the compiled tree. `*` segments are discovered, and their captures joined with `-` become the rung name.
      - label: 'Record<string, TokenPath | TokenPath[]>'
        escape: true
  - items:
      - label: omit
        description: 'Segments elided from the rung name. Defaults to `[''default'']`.'
      - label: string[]
  - items:
      - label: '`include` / `exclude`'
        description: Globs over rung names, applied at discovery, before any family sees them.
        plaintext: true
      - label: RungPattern[]
  - items:
      - label: families
        description: The `@utility` families this role writes.
      - label: Family[]
---
::

A `Family` is made up of a `prefix`, a `properties` map of property to value
template, an optional `nested` map of selectors like `&::before`, its own
`include` and `exclude`, and `static` to emit one block per rung instead of one
functional family. A `RungPattern` is a glob where `*` matches any run of
characters, such as `'surface*'` or `'*-hover'`.

Value templates take three forms, and everything else is copied through as CSS,
so `blur({})` and `linear-gradient(180deg, {stop0} 0%, {stop100} 100%)` both
work.

::docs-table
---
columns:
  - label: Form
  - label: Expands to
rows:
  - items:
      - label: '{}'
      - label: '--value(--<ns>-<role>-*)'
        escape: true
  - items:
      - label: '{leading}'
      - label: '--value(--<ns>-<role>-leading-*)'
        escape: true
  - items:
      - label: '@color.focus.border'
      - label: The token’s literal value, hoisted as a constant.
        plaintext: true
---
::

::note
There is no `modifier` field. Generated utilities have no slash modifiers at
all. See [Color](/utilities/color#limitations).
::

## A worked example

Say you want a `material-*` family with a gradient fill, a backdrop blur and a
`::before` border mask. It starts with the tokens, where `material` is just
another subtree and is discovered like any other.

```
color.material.alpha.bg
color.material.alpha.bg.gradient.stop.0
color.material.alpha.bg.gradient.stop.100
color.material.alpha.border
dimension.material.alpha.blur
boxShadow.material.alpha
```

Then one emitter.

```ts
import { extendMirrorConfig } from '@maas/mirror'

export default extendMirrorConfig({
  source: { type: 'git', repo: 'magicasaservice/mirror-tokens' },
  utilities: {
    emitters: [
      {
        role: 'material',
        values: {
          blur: 'dimension.material.*.blur',
          shadow: 'boxShadow.material.*',
          bg: 'color.material.*.bg',
          stop0: 'color.material.*.bg.gradient.stop.0',
          stop100: 'color.material.*.bg.gradient.stop.100',
          border: 'color.material.*.border',
        },
        families: [
          {
            prefix: 'material',
            properties: {
              position: 'relative',
              'border-radius': 'inherit',
              'backdrop-filter': 'blur({blur})',
              'box-shadow': '{shadow}',
              background:
                'linear-gradient(180deg, {stop0} 0%, {stop100} 100%), {bg}',
            },
            nested: {
              '&::before': {
                content: "''",
                position: 'absolute',
                inset: '0',
                padding: '1px',
                'border-radius': 'inherit',
                background: '{border}',
                mask: 'linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)',
                'mask-composite': 'exclude',
                'pointer-events': 'none',
              },
            },
          },
        ],
      },
    ],
  },
})
```

That emits one `@utility material-*` block plus six theme sub-namespaces, and
`material-alpha` through `material-zeta` all fall out of `color.material.*`.
Adding `material.eta` to the tokens adds the class with no config change.

## Narrowing what Mirror emits

Appending is not always enough. A functional `bg-*` and a static
`bg-surface-low` compile at equal specificity, and Tailwind sorts the
functional one last whatever the import order, so an app that already paints
its own `bg-*` cannot import the preset without the generated family shadowing
its layer.

That is why every emitter is addressable by role. `utilities.overrides` is
keyed by role name and applies to Mirror’s own emitters and to appended ones
alike.

::docs-table
---
columns:
  - label: Override
  - label: Effect
rows:
  - items:
      - label: 'false'
      - label: Drops the role. No families, no theme keys, no class names.
        plaintext: true
  - items:
      - label: values
      - label: Replaces the paths a sub-namespace reads. `false` drops the sub-namespace, and with it every family referencing it.
        plaintext: true
  - items:
      - label: '`include` / `exclude`'
        plaintext: true
      - label: Globs over rung names, applied at discovery.
        plaintext: true
  - items:
      - label: families
      - label: Keyed by the family’s prefix. `false` drops that family; a string renames its prefix.
        plaintext: true
---
::

If your app brings its own colour layer, dropping the role outright is the
answer.

```ts
export default extendMirrorConfig({
  source: { type: 'git', repo: 'magicasaservice/mirror-tokens' },
  utilities: {
    overrides: {
      bg: false,
      fg: false,
      border: false,
    },
  },
})
```

Typography, radius, shadow and the focus ring still generate, while
`--mirror-bg-*`, `--mirror-fg-*` and `--mirror-border-*` are never declared, so
the app’s own `bg-*` blocks are the only ones in the sheet.

If you want Mirror’s colours *and* your own, rename instead of dropping.

```ts
overrides: {
  bg: { families: { bg: 'mirror-bg' } },
}
```

If you only want part of a role, narrow it.

```ts
overrides: {
  bg: { include: ['surface*'] },
  border: { exclude: ['*-hover', '*-active'], families: { divide: false } },
}
```

The narrowing is enforced by the theme namespace rather than by a list of class
names. `--value(--mirror-bg-*)` resolves against the keys in the `@theme`
block, so a rung filtered out at the role level has no key at all and
`bg-primary-solid` stops compiling rather than merely going undocumented. A
filter that leaves nothing behind disables the role with a warning instead of
emitting an empty family.

## Pointing a role at different paths

`values` on an override replaces the paths a sub-namespace reads, which is what
a token tree shaped differently from ours needs. Say yours writes the intents
one segment shallower, without a `component` group in between.

```ts
overrides: {
  bg: { values: { value: 'color.*.bg.**' } },
  fg: { values: { value: 'color.*.fg.**' } },
}
```

The class names are unaffected, because a rung is built from the segments the
wildcards captured rather than from the path they sit at, so
`color.brand.bg.solid.hover` still gets you `bg-brand-solid-hover`.

What an override cannot reach is a family’s `properties`, and with it the
`@path` literals inside them. A role built on literals rather than on discovery,
`focus` being ours, has to be dropped and written again.

```ts
utilities: {
  overrides: { focus: false },
  emitters: [{ role: 'ring', values: {}, families: [/* … */] }],
}
```

Give the replacement a role name of its own, since an override applies to every
emitter with that role, appended ones included. The prefix is what names the
class, so `focus-ring` under a role called `ring` keeps the name your components
already use. [Configuration](/tokens/configuration#reshaping-the-token-tree)
covers the whole custom tree, including which paths are load-bearing.

## Passthroughs

Token subtrees that map cleanly onto a native Tailwind namespace skip the
emitter model entirely, which is how our own [radius and
shadow](/utilities/radius-and-shadow) ship.

```ts
passthrough: [
  { namespace: '--radius', from: 'dimension.surface.radius.**', prefix: 'surface' },
  { namespace: '--shadow', from: 'boxShadow.**' },
]
```

We reach for one whenever Tailwind already owns the property, since arbitrary
values, variants and completion then come for free.

## Helpers

Static classes with no token behind them live in `helpers`, written as name to
body. Your entries merge over Mirror’s single helper, `scrollbar-none`, and
`helpers: false` drops every helper including that one.

```ts
export default extendMirrorConfig({
  source: { type: 'git', repo: 'magicasaservice/mirror-tokens' },
  utilities: {
    helpers: {
      'content-auto': 'content-visibility: auto;',
    },
  },
})
```

Each entry becomes one static `@utility`, so it works with every variant and
needs no token. Keep it for the handful of classes every app writes by hand,
since anything with a size or colour axis belongs in an emitter.

## Limitations

There are two shapes the config cannot express.

The first is a set of rungs that differ in structure rather than in value. A
missing *value* degrades correctly on its own, because an unresolvable
`--value()` drops nothing but its own declaration, while a missing *nested
rule* does not, and a rung without a border token would still get an empty
`::before`. Split those into a second family, which emits static blocks one per
rung as soon as it has either filter, or accept the inert pseudo-element.

The second is anything outside the property and value model, so `@media`,
`@container`, `@keyframes`, sibling selectors and `:has()` are all out of
reach.

For both, the escape hatch is a raw CSS include.

```ts
export default extendMirrorConfig({
  source: { type: 'git', repo: 'magicasaservice/mirror-tokens' },
  utilities: {
    include: ['./app/assets/css/materials.css'],
  },
})
```

The generator concatenates those files unchanged and formats the result, and
nothing in them is discovered, validated or rewritten. Use it for the one-offs,
and move a family into `emitters` as soon as it has more than one rung.

## Further reading

- [Configuration](/tokens/configuration): the token half of the same file.
- [Utilities](/utilities/overview): what the generator ships by default.
