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

# Styling

Everything the chart draws — marks, grid lines, tick marks, the panel border, the graph background,
axis and tick and data-label type — is painted from a **stylesheet** on the spec. You add one by
piping a `styles(...)` part in, the same way you pipe `config(...)`.

```tsx theme={null}
import {
  createSpec,
  pipe,
  geom,
  scale,
  styles,
  style,
  token,
} from '@graphysdk/react';

const spec = pipe(
  createSpec({ x: 'month', y: 'sales' }),
  geom.bar(),
  scale.x(),
  scale.y(),
  styles({
    tokens: { brand: { light: '#0B5FFF', dark: '#6AA1FF' } },
    defaults: [style.geom({ color: token('brand') })],
    overrides: [
      style.geom({ color: '#E5484D' }, { where: { variable: 'sales', lt: 0 } }),
    ],
  })
);
```

Because it lives on the spec, a stylesheet is serializable and travels with the chart — the same as
mappings, scales and config.

<Note>
  Theme tokens (`themeOverrides` on `GraphProvider`) are a separate surface
  covering the HTML chrome *around* the plot: legend, tooltip, headline, footer.
  See [Theming](/sdk-next/rendering/theming).
</Note>

## The four keys

<ParamField path="defaults" type="StyleRule[]">
  Applied where no mapped aesthetic decided the value — the look when nothing
  else speaks.
</ParamField>

<ParamField path="overrides" type="StyleRule[]">
  Applied over whatever a mapping decided.
</ParamField>

<ParamField path="tokens" type="Record<string, StyleTokenValue>">
  Named colours that entries reference with `token('name')`.
</ParamField>

<ParamField path="extends" type="Stylesheet[]">
  Composes other stylesheets underneath this one. Tokens merge name by name,
  lists concatenate, later wins. This is how a house style ships as a reusable
  preset.
</ParamField>

Piping several `styles(...)` parts stacks them in order, each sitting above everything piped before
it. Within a single list, order is specificity: the last matching entry that declares a property wins.

## How a value is resolved

Each property resolves through three tiers, in order:

| Tier         | Source                                                                            |
| ------------ | --------------------------------------------------------------------------------- |
| **override** | a `overrides` entry whose conditions match                                        |
| **data**     | the encoding — a mapped aesthetic (`color`, `size`, `alpha`, …) through its scale |
| **default**  | a `defaults` entry, with the engine's built-in stylesheet behind it               |

The practical consequence: **`defaults` never fight your mappings, and `overrides` always do.** To
recolour a series that is mapped to `color`, you need an `overrides` entry — a `defaults` entry loses
to the scale.

Entries scoped to a state (`{ state: 'hovered' | 'dimmed' }`) sit above the whole stateless cascade.

## Targets

Call `style.<target>(declarations, options?)`. Geom targets accept conditions; chrome targets are
chart-scoped and take declarations only.

| Target                  | Partitions                                                            | Declarations                                                                                       |
| ----------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `style.geom`            | `.bar` `.line` `.area` `.point` `.rule`, plus `{ layer }`             | `color`, `alpha`, `saturation`                                                                     |
| `style.geom.bar`        |                                                                       | ↑ plus `borderRadius`, `borderColor`, `borderWidth`                                                |
| `style.geom.line`       |                                                                       | ↑ plus `strokeWidth`, `lineType`, `fillAlpha`                                                      |
| `style.geom.area`       |                                                                       | ↑ plus `strokeWidth`, `lineType`, `strokeAlpha`                                                    |
| `style.geom.point`      |                                                                       | ↑ plus `size`, `borderColor`, `borderWidth`                                                        |
| `style.geom.rule`       | `.label`                                                              | `color`, `strokeWidth`, `lineType`                                                                 |
| `style.geom.rule.label` |                                                                       | `fontFamily`, `fontSize`, `fontWeight`, `lineHeight`                                               |
| `style.gridLine`        | `.x` `.y`                                                             | `color`, `strokeWidth`, `lineType`                                                                 |
| `style.tickLine`        | `.x` `.y`                                                             | `color`, `strokeWidth`, `lineType`, `length`                                                       |
| `style.axisLabel`       | `.x` `.y`                                                             | `fontFamily`, `fontSize`, `fontWeight`, `lineHeight`, `textColor`                                  |
| `style.tickLabel`       | `.x` `.y`                                                             | ↑ plus `offset`                                                                                    |
| `style.dataLabel`       | `.observation` / `.category`, each `.inside` `.outside`; `.aggregate` | ↑ plus `paddingInline`, `paddingBlock`, `background`, `borderColor`, `borderWidth`, `borderRadius` |
| `style.panelBorder`     | `.top` `.right` `.bottom` `.left`                                     | `color`, `strokeWidth`, `lineType`; `borderRadius` on the bare builder                             |
| `style.graph`           |                                                                       | `background`, `borderColor`, `borderWidth`, `borderRadius`                                         |

Three things worth knowing up front:

* A bar's `borderRadius` is a **token**: `'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full'`,
  defaulting to `'sm'`. On `dataLabel`, `panelBorder` and `style.graph` it is a plain pixel number.
* **Hide a panel-border edge with `strokeWidth: 0`.**
* Stack totals (`.aggregate`) always sit outside the mark, so they take no `.inside` / `.outside`.

## Colours

Any colour-valued property takes one of three forms:

```tsx theme={null}
style.geom({ color: '#E5484D' }); // literal
style.geom({ color: { light: '#E5484D', dark: '#FF6369' } }); // one per scheme
style.geom({ color: token('alert') }); // token reference
```

`{ light, dark }` and `token(...)` resolve against the provider's
[`colorScheme`](/sdk-next/rendering/theming). Prefer them to literals — a literal looks the same in
both schemes.

## Conditions

Geom entries take a `where` predicate (the same language as [highlights](/sdk-next/emphasis/highlights)),
a `state`, and a `layer`:

```tsx theme={null}
styles({
  overrides: [
    style.geom(
      { color: token('alert') },
      { where: { variable: 'sales', lt: 0 } }
    ),
    style.geom.line({ strokeWidth: 4 }, { layer: 'total' }),
    style.geom({ alpha: 0.15 }, { state: 'dimmed' }),
  ],
});
```

`layer` scopes an entry to one authored layer `id` — the way to style a single series of a
[combo chart](/sdk-next/graph-types/combo) without touching the others.

## Re-skinning through tokens

The engine's built-in stylesheet sits behind every chart, and its defaults are written in terms of
tokens. Redefining a built-in token name restyles the default it backs, with no entries at all:

```tsx theme={null}
styles({
  tokens: {
    geomColor: '#1D1D1B',
    gridLineColor: '#E9E9E9',
    graphBackground: { light: '#FFFFFF', dark: '#141414' },
  },
});
```

| Token                  | Backs                                               |
| ---------------------- | --------------------------------------------------- |
| `geomColor`            | every mark's fill when nothing is mapped to `color` |
| `ruleColor`            | reference, goal and average lines                   |
| `pointBorderColor`     | point outlines                                      |
| `hoverAffordanceColor` | the hovered outline on bars and points              |
| `gridLineColor`        | grid lines and the panel border                     |
| `tickLineColor`        | tick marks                                          |
| `graphBackground`      | the graph plate                                     |
| `textPrimary`          | axis labels and data labels                         |
| `textSecondary`        | tick labels                                         |

The built-in defaults these produce: bars `borderRadius: 'sm'` with a 1px border; lines and areas
`strokeWidth: 2`, `lineType: 'solid'`; areas `alpha: 0.3`; points `size: 8`; rules dashed; grid lines
dashed; the panel border dashed with `borderRadius: 8`; tick labels offset 10px; the `dimmed` state
`alpha: 0.4`.

## Presets with `extends`

`extends` composes stylesheets, which is how a house style becomes reusable:

```tsx theme={null}
const houseStyle = {
  tokens: { geomColor: '#1D1D1B', gridLineColor: '#C9C6BE' },
  defaults: [
    style.gridLine({ lineType: 'solid' }),
    style.panelBorder({ strokeWidth: 0 }),
    style.tickLabel({ fontSize: 12 }),
  ],
};

const spec = pipe(
  createSpec({ x: 'month', y: 'sales' }),
  geom.bar(),
  scale.x(),
  scale.y(),
  styles({
    extends: [houseStyle],
    defaults: [style.geom.bar({ borderRadius: 'full' })],
  })
);
```

## Two token namespaces

`textPrimary`, `textSecondary` and `gridLineColor` name a stylesheet token **and** a theme token.
They are different values in different namespaces: the stylesheet tokens above drive the plot, while
the `themeOverrides` keys of the same name drive the chrome around it. If setting one by name has no
visible effect, check which namespace you reached for.

## When an entry is invalid

An entry the engine can't use is reported as an `INVALID_STYLE_RULE` warning and skipped — the chart
still renders. Compile a spec headlessly to see warnings before wiring it into React.

## Custom geoms

A [custom geom renderer](/sdk-next/extending/geom-renderers) reads paint through the value accessors
on its render input (`getColor`, `getAlpha`, `getSize`, …), which expose the **data** tier. To resolve
the full cascade, build a resolver over the layer:

```tsx theme={null}
import { createStyleResolver } from '@graphysdk/viz-engine';

const fill = createStyleResolver({ colorScheme })
  .geomReaders(layer)
  .get('color', observation);
```

Supply `colorScheme` yourself — it defaults to `'light'`, and no exported hook carries the chart's
active scheme into a renderer.
