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

# Styling Rules: Select Markdown Nodes and Set Presentation

> Style any Markdown node with the rules array — select by type, depth, language, position, and more, then set color, fonts, borders, images, tables, and lists. Plus heading numbering and the PDF outline.

The `rules` array is how you style a Blink PDF document: surgical presentation rules over standard Markdown nodes. Each rule **selects** a set of nodes and **sets** presentation properties on them.

<Note>
  The `rules` schema is large — 18 selector axes and 60+ settable properties. This page covers the model and the common cases; the **API Reference → Render** request body is generated from the server's OpenAPI contract and is always authoritative for every axis, property, enum, and constraint. Use [`POST /v1/render/validate`](/api-reference/overview#validate-before-render) to check a rule set before spending a render, and [`GET /v1/style-targets`](/api-reference/overview) for the machine-readable vocabulary.
</Note>

## The shape of a rule

<ParamField body="rules" type="object[]">
  Up to 256 presentation rules. Each rule is an object with:

  <Expandable title="rule fields">
    <ParamField body="select" type="object" required>
      A Markdown-native node selector. All present axes are AND-ed together; enumerable axes (`type`, `depth`, `markerFormat`, `kind`, `language`) accept an array meaning OR. An empty object `{}` matches every node.
    </ParamField>

    <ParamField body="set" type="object" required>
      The properties applied to matched nodes. Validity depends on the node category (block vs. inline); incompatible combinations are reported by `POST /v1/render/validate`.
    </ParamField>

    <ParamField body="priority" type="number">
      Orders the cascade when multiple rules match the same node. Higher priority wins; later rules break ties.
    </ParamField>
  </Expandable>
</ParamField>

```json theme={null}
{
  "markdown": "# Heading\n\nBody copy",
  "rules": [
    { "select": { "type": "heading", "depth": 1 }, "set": { "color": "#1a73e8", "align": "center" } }
  ]
}
```

## Selecting nodes

`select` chooses which nodes a rule touches. The selectable node types are `heading`, `paragraph`, `list`, `listItem`, `blockquote`, `table`, `codeBlock`, `thematicBreak`, `image`, plus the inline types `strong`, `emphasis`, `delete`, `mark`, `subscript`, `superscript`, `link`, and `inlineCode`.

Common selector axes:

| Axis                                          | Selects                                                                                                                                          |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`                                        | Node type (array = OR).                                                                                                                          |
| `depth`                                       | 1-based depth: heading level (1–6), or list/blockquote nesting level.                                                                            |
| `text`                                        | Heading text — exact string or `{ "contains": "..." }`.                                                                                          |
| `url`                                         | Link/image URL — exact, `{ endsWith }`, or `{ contains }`.                                                                                       |
| `alt`                                         | Image alt text — exact or `{ contains }`.                                                                                                        |
| `language`                                    | Code-block language (case-insensitive, alias-aware; array = OR).                                                                                 |
| `kind`                                        | Alert/callout kind (array = OR).                                                                                                                 |
| `ordered` / `markerFormat` / `start` / `task` | List and list-item facets.                                                                                                                       |
| `column` / `row`                              | Table column (index or alignment) and body row (negatives count from the end).                                                                   |
| `nth` / `first` / `last`                      | Positional selection — `nth` is 1-based (negatives from the end) or `{ step, offset }`; `first` and `last` are sugar for `nth: 1` and `nth: -1`. |

An empty `select: {}` matches every node — useful for a document-wide default that more specific rules override.

## Setting presentation

`set` applies properties to matched nodes. Compatibility depends on whether the matched node is a block or inline run; `POST /v1/render/validate` flags incompatible combinations. Frequently used properties, grouped by purpose:

### Text and color

| Property                                                  | Effect                                                                                                                                                                              |
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `font`                                                    | Font slot as `<role>.<variant>` — role is `body`, `heading`, or a `typography.named` role; variant is `regular`, `italic`, `bold`, or `boldItalic` (e.g. `heading.bold`).           |
| `family`                                                  | Switch the typeface itself — a hosted key, a declared Google Fonts name, or a named role (see [Fonts & Typography](/configuration/fonts-layouts)).                                  |
| `size`, `color`, `lineHeight`, `letterSpacing`, `opacity` | Type size (pt), `#RGB`/`#RRGGBB` (or `inherit` for color), line height, tracking, and opacity.                                                                                      |
| `align`                                                   | `left`/`right`/`center`/`justify` for text; `start`/`center`/`end` for block images.                                                                                                |
| `decoration`, `decorationStyle`, `decorationColor`        | Underline/line-through styling.                                                                                                                                                     |
| `sizeScale`                                               | Optical scale as a fraction of the surrounding run (`0.4`–`1` on the rules path). Inline code is clamped to about `0.85`–`0.88` at render; subscript/superscript use the full band. |
| `riseFraction`                                            | Subscript/superscript only: baseline offset as a fraction of base size (not PDF points — contrast with code `baselineShift`).                                                       |
| `spaceBefore`, `spaceAfter`                               | Vertical spacing around a block, in points.                                                                                                                                         |

### Boxes and breaks

`background`, `border`, `borderLeft`, `borderRadius`, and `padding` draw boxes around blocks (a `borderLeft` is the classic blockquote accent). `pageBreakBefore`, `keepWithNext`, `minLinesAfter`, and `policy` control pagination — keep a heading with its first lines, or force a section onto a new page.

### Lists

`marker`, `orderedMarker`, `unorderedMarker`, and `markerByDepth` choose counters and bullets (builtin formats like `decimal` or `upper-roman`, builtin bullets like `disc`/`square`/`dash`/`arrow`, or any literal string). `indent`, `gutter`, and `spacing` (`tight`/`loose`) tune layout.

### Code blocks

`syntaxHighlight` turns on token coloring, `theme` picks a palette (`crisp-studio-light`, `charcoal-midnight`), and `syntaxColors` overrides individual token roles. `lineNumbers`, `lineNumberColor`, and `languageLabel` add gutters and a language tag.

### Tables

`borders` (`none`/`grid`/`outer`/`header` or a per-edge object), `headerBackground`, `stripe` (zebra rows), `widths`, and `repeatHeaderOnPageBreak`. Pagination and overflow within a table are controlled by:

* `rowSplit` — whether a tall row may split across a page break.
* `cell` — per-cell `valign`, `textWrap` (`normal`/`break-word`/`anywhere`), and `padding`, plus block-axis overflow: `cell.blockOverflow` (`clip`/`shrink`/`split`/`ellipsis`) with a `cell.maxHeight` ceiling. `maxHeight` is **required** for `clip` and `shrink` to have any effect; without it both are no-ops.

### Images

`width`, `maxWidth`, `height`, `fit` (`none`/`contain`/`cover`/`fill`), `shadow`, `mask` (clip to a shape, rect, path, or alpha image), and `overlay` (stamp an image or text on top). Box treatments (`border`, `borderRadius`, `background`) apply to images too.

<Note>
  To target images by position or exact URL without a `select` rule, use the top-level [`imageOverrides`](/configuration/document-options#the-imageoverrides-field) field — it takes the same image `style` vocabulary and merges over `styles.image` for matching images only.
</Note>

## Example: image card with a border and shadow

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.blinkpdf.io/v1/render \
    --request POST \
    --header 'Accept: application/pdf' \
    --header 'Content-Type: application/json' \
    --header 'x-api-key: bp_xxxxxxxxxxxxxxxxxxxx' \
    --data '{
      "markdown": "# Brand\n\n![Logo](https://example.com/logo.png)",
      "metadata": { "title": "Image styles demo" },
      "rules": [
        {
          "select": { "type": "image" },
          "set": {
            "align": "center",
            "maxWidth": 400,
            "spaceBefore": 12,
            "spaceAfter": 12,
            "border": { "width": 1, "color": "#cccccc" },
            "borderRadius": 8,
            "shadow": { "offsetX": 0, "offsetY": 4, "blur": 8, "color": "#000000", "opacity": 0.15 }
          }
        }
      ]
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.blinkpdf.io/v1/render", {
    method: "POST",
    headers: {
      "x-api-key": "bp_xxxxxxxxxxxxxxxxxxxx",
      "Accept": "application/pdf",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      markdown: "# Brand\n\n![Logo](https://example.com/logo.png)",
      metadata: { title: "Image styles demo" },
      rules: [
        {
          select: { type: "image" },
          set: {
            align: "center",
            maxWidth: 400,
            spaceBefore: 12,
            spaceAfter: 12,
            border: { width: 1, color: "#cccccc" },
            borderRadius: 8,
            shadow: { offsetX: 0, offsetY: 4, blur: 8, color: "#000000", opacity: 0.15 },
          },
        },
      ],
    }),
  });

  const pdf = await response.arrayBuffer();
  ```

  ```python Python theme={null}
  import httpx

  response = httpx.post(
      "https://api.blinkpdf.io/v1/render",
      headers={
          "x-api-key": "bp_xxxxxxxxxxxxxxxxxxxx",
          "Accept": "application/pdf",
          "Content-Type": "application/json",
      },
      json={
          "markdown": "# Brand\n\n![Logo](https://example.com/logo.png)",
          "metadata": {"title": "Image styles demo"},
          "rules": [
              {
                  "select": {"type": "image"},
                  "set": {
                      "align": "center",
                      "maxWidth": 400,
                      "spaceBefore": 12,
                      "spaceAfter": 12,
                      "border": {"width": 1, "color": "#cccccc"},
                      "borderRadius": 8,
                      "shadow": {"offsetX": 0, "offsetY": 4, "blur": 8, "color": "#000000", "opacity": 0.15},
                  },
              }
          ],
      },
  )

  with open("brand.pdf", "wb") as f:
      f.write(response.content)
  ```
</CodeGroup>

## Heading numbering

The `numbering` object adds automatic section numbers to headings. It is opt-in: counters reset on the next deeper heading and propagate through the document.

<ParamField body="numbering" type="object">
  Auto-numbering policy. Requires `enabled: true`.

  <Expandable title="numbering fields">
    <ParamField body="numbering.enabled" type="boolean" required>
      Turn heading auto-numbering on or off. Passing `all`, `byDepth`, `depthRange`, or `trailingSeparator` with `enabled: false` returns `400` — those fields configure an active policy.
    </ParamField>

    <ParamField body="numbering.depthRange" type="number[]">
      Inclusive `[min, max]` heading depths (1–6) that receive numbering.
    </ParamField>

    <ParamField body="numbering.all" type="object">
      A single format applied to every numbered depth: `format` (`decimal`, `decimal-leading-zero`, `upper-roman`, `lower-roman`, `upper-alpha`, `lower-alpha`), `prefix`, `suffix`, and `separator`.
    </ParamField>

    <ParamField body="numbering.byDepth" type="object">
      Per-depth overrides keyed by depth `1`–`6`, each with the same `format`/`prefix`/`suffix`/`separator` shape as `all`.
    </ParamField>

    <ParamField body="numbering.trailingSeparator" type="string">
      Text appended after the full counter, before the heading text (e.g. `" — "`).
    </ParamField>
  </Expandable>
</ParamField>

```json theme={null}
{
  "markdown": "# Introduction\n\nBody.\n\n## Background\n\nMore.\n\n# Method\n\nText.",
  "numbering": {
    "enabled": true,
    "trailingSeparator": " — ",
    "byDepth": { "1": { "format": "upper-roman" } }
  }
}
```

## PDF outline (bookmarks)

The `outline` object controls the PDF's navigation bookmarks. When omitted, an outline is emitted with the default depth range `[1, 6]` for any document containing headings.

<ParamField body="outline" type="object">
  PDF outline (bookmarks).

  <Expandable title="outline fields">
    <ParamField body="outline.enabled" type="boolean">
      Whether to generate the outline. Set `false` to suppress it entirely. Passing `depthRange` together with `enabled: false` returns `400` — choose one intent.
    </ParamField>

    <ParamField body="outline.depthRange" type="number[]">
      Inclusive `[min, max]` heading depths (1–6) included in the outline.
    </ParamField>
  </Expandable>
</ParamField>

<Tip>
  To drop a single heading from the outline without changing the depth range, add a rule: `{ "select": { "type": "heading", "text": { "contains": "Appendix" } }, "set": { "outline": false } }`.
</Tip>
