---
title: "Footnotes"
description: "Add numbered badges to lines, with the notes in a list under the block, where readers see them all at once."
url: "https://ewels.github.io/starlight-codeblocks/features/footnotes/"
markdown: "https://ewels.github.io/starlight-codeblocks/features/footnotes.md"
section: "Explain code"
site: "starlight-codeblocks documentation"
context: "This page is from the documentation of starlight-codeblocks. A Starlight plugin that adds focus, line states, annotations, API auto-linking and 22 more features to the code blocks of a site."
index: "https://ewels.github.io/starlight-codeblocks/llms.txt"
---

# Footnotes

> Add numbered badges to lines, with the notes in a list under the block, where readers see them all at once.

A code block often comes with a list of notes in the prose after it, where each note names the line it is about. Footnotes put that list inside the block. Each line with a note gets a numbered badge, and the notes are a numbered list under the code.

:::tip[Good for:]
A short block where readers need every note. The list stays close to the lines and works the same on a phone and a desktop. For a long block, use [side annotations](https://ewels.github.io/starlight-codeblocks/features/side-annotations/).
:::

````md
```py title="app.py"
from flask import Flask

# [!ref] Creates the application object.
app = Flask(__name__)

# [!ref] Runs this function for `GET /health`.
@app.get("/health")
def health():
    return {"ok": True}
```
````

The two `[!ref]` comment lines are gone from the output. Their lines have the badges 1 and 2, and the notes are in the list under the code. Click a badge to highlight its line and its note together. Click a note to do the same from the list.

## Syntax

| Syntax | Where |
|---|---|
| `[!ref] note` | Comment on or above the line |
| `footnotes="sticky"` | Code block fence line |
| `footnotes="static"` | Code block fence line |
| `footnotes.sticky=true`, `footnotes.sticky=false` | Code block fence line |
| `startNoteNumber={N}` | Code block fence line |
| `footnotes.style="filled"`, `footnotes.style="outline"` | Code block fence line |

The directive goes at the end of the line it explains, or on its own line above it. The note can hold inline code, links and bold text. `footnotes="sticky"` keeps the list in view while the block scrolls past. `footnotes="static"` turns the sticky list off for one block, if the site turns it on for every block. `footnotes.sticky=true` and `footnotes.sticky=false` do the same, with the name of the option. `startNoteNumber={N}` numbers the footnotes from `N`, to continue the numbers of an earlier block.

`footnotes.style` sets the style of the badges for one block.

## Examples

### A sticky list

A sticky list suits a long block, where the notes would otherwise be far from the lines they are about. Scroll past this block to see the list stay at the bottom of the window:

````md
```py title="count.py" footnotes="sticky"
import argparse
import logging
from pathlib import Path

# [!ref] A logger named after the module, so output shows where it came from.
log = logging.getLogger(__name__)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(description="Count lines in files.")
    parser.add_argument("paths", nargs="+", type=Path)
    # [!ref] `-v` can be repeated: `-vv` turns on debug output.
    parser.add_argument("-v", "--verbose", action="count", default=0)
    return parser.parse_args()


def count_lines(path: Path) -> int:
    with path.open() as fh:
        return sum(1 for _ in fh)


def main() -> None:
    args = parse_args()
    logging.basicConfig(level=logging.WARNING - 10 * args.verbose)
    for path in args.paths:
        log.debug("Reading %s", path)
        print(f"{count_lines(path):>8}  {path}")


# [!ref] Runs only when the file runs as a script, not when another module imports it.
if __name__ == "__main__":
    main()
```
````

### The filled style

The `filled` style, for one block:

````md
```py footnotes.style="filled"
# [!ref] Creates the application object.
app = Flask(__name__)
```
````

## Behaviour

- Numbers and placement:
  - Badges are numbered from 1 in each block, or from the `startNoteNumber` value, in line order.
  - With `footnotes="sticky"`, the list sticks to the bottom of the window while any part of the block is on screen.
- Highlighting:
  - Hovering over a badge or a note with a mouse highlights the line and the note until the mouse cursor leaves. The highlight fades in after 80 ms, so it does not flash while the mouse cursor passes over. The page does not scroll.
  - Clicking a badge highlights its whole line and its note, and the highlight stays. Clicking the badge or the note again clears it. Several footnotes can be highlighted at once. If the note is not on screen, the page scrolls to it.
  - Clicking a note highlights it and its line. If the line is not on screen, the page scrolls to it.
  - The note gets the same tint and bar as its line.
  - Clicking anywhere else clears every highlight.
- Keyboard and screen readers:
  - Each badge is a link with the label "Footnote N", and its note is its accessible description. The number in front of each note is a link back to its line, with the label "Footnote N, for line L".
  - From the keyboard, a badge always moves focus to its note, and the number moves focus back to the badge.
- Copy:
  - The copy button leaves the badges and the notes out. A manual selection of the code leaves the badges out.
- Without JavaScript:
  - The badges and the numbers are plain links to each other, so they still work, without the highlight.
- Motion:
  - With reduced motion, the highlights change at once, and the page jumps to the line or the note instead of scrolling smoothly.

## Options

### `footnotes`

Adds numbered badges to lines, with the notes in a list under the block. Set it to `false` to turn the feature off.

- Type: `false | object`
- Default: On

### `footnotes.sticky`

Keep the list of footnotes in view while the block is on screen. A code block can set its own on its fence line.

- Type: `boolean`
- Default: `false`

### `footnotes.style`

Filled badges in the accent colour, or outlined badges in the magenta of the theme. A code block can set its own on its fence line.

- Type: `'filled' | 'outline'`
- Default: `'outline'`

The `outline` style draws an outlined badge in the magenta of the theme, and fills it when its line is highlighted. The `filled` style draws each badge filled with the accent colour, the same as an [annotation](https://ewels.github.io/starlight-codeblocks/features/annotations/) marker.

```js title="astro.config.mjs"
codeblocks({
  footnotes: { sticky: true, style: 'filled' },
});
```

With `footnotes: false`, the plugin does not know the `[!ref]` directive. It leaves the comment in the code as written, and logs a build warning.

- [Configuration](https://ewels.github.io/starlight-codeblocks/configuration/): Learn how to set options for the whole site, or for a single code block.

## Limitations

- A footnote applies to one line. For a note about a range of lines, put it above the first line of the range.
- A sticky list covers the bottom of the block while it is on screen. Keep the notes short, so the list stays small.

## Related

- [Annotations](https://ewels.github.io/starlight-codeblocks/features/annotations/): numbered markers that open a note only when the reader asks for it.
- [Side annotations](https://ewels.github.io/starlight-codeblocks/features/side-annotations/): the notes in a column beside the code, on wide screens.
- [Inline callouts](https://ewels.github.io/starlight-codeblocks/features/inline-callouts/): a short note above the line, with an arrow at one name.
