# 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. This plugin adds 26 features to the [Expressive Code](https://expressive-code.com) blocks of [Starlight](https://starlight.astro.build). Use them to enrich code blocks, they're perfect for docs and training. Using an agent? [Point it at the bundled skill](https://ewels.github.io/starlight-codeblocks/agent-skill/). ## Features ### Explain code - [Annotations](https://ewels.github.io/starlight-codeblocks/features/annotations/): Add numbered markers to lines. Each marker opens a note in a popover, so the code stays clean until a reader asks. - [Side annotations](https://ewels.github.io/starlight-codeblocks/features/side-annotations/): Show the notes of an annotated block in a column beside the code, so readers see every note next to its line. - [Footnotes](https://ewels.github.io/starlight-codeblocks/features/footnotes/): Add numbered badges to lines, with the notes in a list under the block, where readers see them all at once. - [Inline callouts](https://ewels.github.io/starlight-codeblocks/features/inline-callouts/): Put a short note in a bubble above a line, with an arrow that points at the word it explains. - [Scrollycoding](https://ewels.github.io/starlight-codeblocks/features/scrollycoding/): Explain a code block in prose steps that scroll past it, while the block stays in view and focuses the lines of each step. - [Code walkthrough](https://ewels.github.io/starlight-codeblocks/features/code-walkthrough/): Step through versions of one code block, and watch the code move from each version to the next, so readers see what changed. ### Draw attention - [Focus](https://ewels.github.io/starlight-codeblocks/features/focus/): Blur the lines outside a range, so that readers look at the lines that you name first. - [Line states](https://ewels.github.io/starlight-codeblocks/features/line-states/): Tint lines as errors, warnings, notes or successes, with an optional message after the code, like the diagnostics in a code editor. - [Code mentions](https://ewels.github.io/starlight-codeblocks/features/code-mentions/): Link a phrase in the prose to lines of the code block below it, so that readers see which lines the text is about. ### Make code easier to read - [Hidden lines](https://ewels.github.io/starlight-codeblocks/features/hidden-lines/): Hide the imports and set-up that readers need to run an example but not to understand it. - [Expandable blocks](https://ewels.github.io/starlight-codeblocks/features/expandable-blocks/): Show the first lines of a long block, with a fade and a button to reveal the rest. - [Visible whitespace](https://ewels.github.io/starlight-codeblocks/features/visible-whitespace/): Show spaces and tabs as faint glyphs, for the blocks where indentation changes the meaning of the code. - [Colourised brackets](https://ewels.github.io/starlight-codeblocks/features/colourised-brackets/): Colour matching brackets by nesting depth, so a dense line of code stays readable. - [Colour swatches](https://ewels.github.io/starlight-codeblocks/features/colour-swatches/): Show a small swatch of each CSS colour next to its value, so readers see the colour without a colour picker. - [File icons](https://ewels.github.io/starlight-codeblocks/features/file-icons/): Show the icon of the file type before the title of a code block, with the icons of the Starlight file tree or a coloured icon set. - [Inline code highlighting](https://ewels.github.io/starlight-codeblocks/features/inline-code-highlighting/): Give inline code in the prose the syntax colours of the code blocks, from a language suffix or a default language for the site. - [Word-level diff](https://ewels.github.io/starlight-codeblocks/features/word-level-diff/): Highlight the words that changed inside each line of a diff, so readers find a small edit in a long line. ### Link code - [Code links](https://ewels.github.io/starlight-codeblocks/features/code-links/): Turn text in code into a link with a card that describes it, from a directive in the comment above. - [API auto-linking](https://ewels.github.io/starlight-codeblocks/features/api-auto-linking/): Link the names in code examples to their reference pages, with a card that shows the signature and a summary. - [Line permalinks](https://ewels.github.io/starlight-codeblocks/features/line-permalinks/): Give a code block line numbers that link to each line, so readers can share a link to the exact lines they mean. Shift-click to select multiple lines. ### Adapt to the reader - [Code tabs](https://ewels.github.io/starlight-codeblocks/features/code-tabs/): Show several code blocks as one, with editor tabs in the title bar, such as the files of a project or the commands for each package manager. - [Fill-in placeholders](https://ewels.github.io/starlight-codeblocks/features/fill-in-placeholders/): Turn placeholders such as YOUR_TOKEN into fields, so readers type their own values into every block and the copied code. ### Copy and run - [Smart shell copy](https://ewels.github.io/starlight-codeblocks/features/smart-shell-copy/): Add a Copy commands button to terminal blocks, which copies the commands without the prompts or the output. - [Open in playground](https://ewels.github.io/starlight-codeblocks/features/open-in-playground/): Add a title bar button that opens the example in an online playground, with the code already filled in. - [Run code](https://ewels.github.io/starlight-codeblocks/features/run-code/): Add a Run code button that runs the example in the browser and shows the output under the block. ## Install Add the package to the site: :::code-tabs{sync="pm"} ```sh label="npm" npm install starlight-codeblocks ``` ```sh label="pnpm" pnpm add starlight-codeblocks ``` ```sh label="Yarn" yarn add starlight-codeblocks ``` ::: Then add `codeblocks()` to the Starlight plugins in `astro.config.mjs`: ```js title="astro.config.mjs" import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; import codeblocks from 'starlight-codeblocks'; // [!code focus ++] Import the plugin export default defineConfig({ integrations: [ starlight({ title: 'My docs', plugins: [codeblocks()], // [!code focus ++] Load the plugin }), ], }); ``` The [getting started](https://ewels.github.io/starlight-codeblocks/getting-started/) page has the full steps. ## Extend and reference - [Extend](https://ewels.github.io/starlight-codeblocks/extend/write-an-api-link-adapter/): your own API link adapters, playgrounds and runtimes. - [Reference](https://ewels.github.io/starlight-codeblocks/reference/options/): every option, attribute, directive and style setting. ## Using with an agent This website is agent-friendly: each page has a Markdown version at the same address with `.md` at the end, such as [getting-started.md](https://ewels.github.io/starlight-codeblocks/getting-started.md). [llms.txt](https://ewels.github.io/starlight-codeblocks/llms.txt) lists every page, and [llms-full.txt](https://ewels.github.io/starlight-codeblocks/llms-full.txt) has every page in one file. Using an agent? [Point it at the bundled skill](https://ewels.github.io/starlight-codeblocks/agent-skill/). The skill tells the agent which feature suits each use, and how to write it. ## Thanks This plugin builds on the excellent [Expressive Code](https://expressive-code.com), which renders every code block in Starlight. The `[!code …]` comment notation comes from the [Shiki transformers](https://shiki.style/packages/transformers) and [VitePress](https://vitepress.dev/). Thank you to both projects. Most code blocks from VitePress work here unchanged. --- # Getting started > Install starlight-codeblocks and add it to a Starlight site. Starlight renders every code block with Expressive Code. The plugin adds its features to those code blocks, so the code blocks you already have keep working. You turn on nothing per page: most features start when a code block uses their attribute or directive. A few, such as file icons and colour swatches, apply to every matching block. ## Requirements - Astro 7 or later. - Starlight 0.42 or later. - Node.js 22.12 or later. ## Install the plugin 1. Add the package to the site. **npm** ```sh npm install starlight-codeblocks ``` **pnpm** ```sh pnpm add starlight-codeblocks ``` **Yarn** ```sh yarn add starlight-codeblocks ``` 2. Open `astro.config.mjs`. 3. Add `codeblocks()` to the `plugins` list of Starlight. ```js title="astro.config.mjs" ins={3,9} import starlight from '@astrojs/starlight'; import { defineConfig } from 'astro/config'; import codeblocks from 'starlight-codeblocks'; export default defineConfig({ integrations: [ starlight({ title: 'My docs', plugins: [codeblocks()], }), ], }); ``` Pick a feature from the sidebar and add its attribute or directive to a code block to turn it on. ## Sites with an `ec.config.mjs` file Some sites keep their Expressive Code options in an `ec.config.mjs` file. If that file has no `plugins` list, the plugin works with no change. If the file has a `plugins` list, Expressive Code uses that list only. Add `pluginCodeblocks()` to it: ```js title="ec.config.mjs" ins={2,5} import { pluginCollapsibleSections } from '@expressive-code/plugin-collapsible-sections'; import { pluginCodeblocks } from 'starlight-codeblocks/expressive-code'; export default { plugins: [pluginCollapsibleSections(), pluginCodeblocks()], }; ``` Keep `codeblocks()` in `astro.config.mjs` as well, and give it the options there. `pluginCodeblocks()` with no argument uses the same options. If you forget this step, the build stops with a message that tells you what to add. ## Sites without Starlight Astro sites that use Expressive Code without Starlight add the preset to the Expressive Code plugins. Give the options to `pluginCodeblocks()`: ```js title="ec.config.mjs" import { pluginCodeblocks } from 'starlight-codeblocks/expressive-code'; export default { plugins: [pluginCodeblocks({ focus: { style: 'dim' } })], }; ``` Code tabs, inline code highlighting and the `` and `` components need Starlight. The features inside code blocks work on every site. Set `tabWidth: 0` in the Expressive Code options, so that tabs reach the code blocks unchanged. Blocks with a **Run code** button need a runtime URL for their language, as in [Without Starlight](https://ewels.github.io/starlight-codeblocks/reference/expressive-code-plugins/#without-starlight). ## Next steps - [Configuration](https://ewels.github.io/starlight-codeblocks/configuration/): Learn how to set options for the whole site, or for a single code block. - [Features](https://ewels.github.io/starlight-codeblocks/#features): Browse all the features, with an example of each. --- # Configuration > Turn features off, change their settings and change their colours with one options object. With no options, `codeblocks()` turns on every feature. Most features change a code block only when the block uses their attribute or directive. A few, such as file icons, colour swatches and word-level diff, apply to every matching block. Pages that use no interactive feature load no JavaScript from the plugin. ## The options object In the astro config file, the plugins `codeblocks()` function takes one object. Each feature has a key in it. Set a key to `false` to turn the feature off, or to an object to change its settings: ```js title="astro.config.mjs" {4-8} starlight({ title: 'My docs', plugins: [ codeblocks({ focus: { style: 'dim' }, expandable: { lines: 20 }, runnable: false, }), ], }); ``` - [Options reference](https://ewels.github.io/starlight-codeblocks/reference/options/): See every option, with its type and its default. ## Options for one block To change a setting for one block only, put the option on the fence line, with the same name as in `codeblocks()`. The fence line is the first line of the code block, with the language. The rest of the site keeps its setting: ````md ```js focus={2} focus.style="dim" lineStates.prefix=false const host = 'localhost' const port = 8080 // [!code warning] Set in production ``` ```` This works for every setting that applies to one block, such as `wordDiff.minSimilarity=0.6` or `runnable.label="Try it"`. A value of the wrong type gives a build warning, and the block uses the site setting. Options for the whole site, such as custom line states, playgrounds and runtimes, stay in `codeblocks()`. ## Errors in the options The plugin checks the options when the site starts. An unknown key or a value of the wrong type stops the build with a message that names the option: ```txt error={1} lineStates.prefix=false starlight-codeblocks: `focus.style` must be 'blur' | 'dim', got "fade". ``` A feature with no settings, such as `callouts`, takes only `false`. To keep the feature on, leave its key out. ## Colours and sizes Every colour and size of the plugin is an Expressive Code style setting. The default colours come from the Expressive Code theme of the site, such as its terminal blue for the accent. So they follow any theme, dark or light. The plugin makes each colour lighter or darker where it needs to, so that the defaults meet the contrast targets on the [accessibility](https://ewels.github.io/starlight-codeblocks/reference/accessibility/) page. To change a setting, add `styleOverrides` to the Expressive Code options of Starlight. The shared settings are in the `codeblocks` group. A string applies to both themes, and a pair gives the dark value and then the light value: ```js title="astro.config.mjs" {3-8} starlight({ expressiveCode: { styleOverrides: { codeblocks: { accent: ['#c792ea', '#7c3aed'], popoverRadius: '4px', }, }, }, plugins: [codeblocks()], }); ``` - [Style settings reference](https://ewels.github.io/starlight-codeblocks/reference/style-settings/): See every style setting, with its defaults. - [Themes](https://ewels.github.io/starlight-codeblocks/reference/themes/): See the colours in eight themes. ## Options with an ec.config.mjs file Some sites keep their Expressive Code options in an `ec.config.mjs` file, next to `astro.config.mjs`. If that file has a `plugins` list, add `pluginCodeblocks()` to it, as in [Sites with an ec.config.mjs file](https://ewels.github.io/starlight-codeblocks/getting-started/#sites-with-an-ecconfigmjs-file). Give `pluginCodeblocks()` no argument. It reads its options from `codeblocks()`, so all options stay in `astro.config.mjs`. `styleOverrides` can go in either file. --- # Comment notation > Mark lines with directives in code comments. The plugin applies each directive and removes it from the code that readers see and copy. The standard way to activate Expressive Code features is with attributes on the fence line, such as `{3}` or `ins={5}`. The fence line is the first line of the code block, with the language. This means you need to be careful when you edit the code in a code block. If you add or remove any lines, the counts change and the attribute can point at the wrong code. To avoid this, starlight-codeblocks also offers configuration via _directives_. A directive sits in a comment on or above the line that it marks. It moves with the code, so you don't need to count line numbers. ````md ```ts title="config.ts" export const config = { // [!code highlight] host: 'localhost', protocol: 'https', port: 3000, // [!code --] port: Number(process.env.PORT ?? 3000), // [!code ++] timeout: 5000, }; ``` ```` _Same example, built instead with attributes:_ ````md ```ts title="config.ts" mark={2} del={4} ins={5} export const config = { host: 'localhost', protocol: 'https', port: 3000, port: Number(process.env.PORT ?? 3000), timeout: 5000, }; ``` ```` Each directive changes its line. Each comment holds only a directive, so the plugin removes the whole comment. The copy button copies the code without the directives. ## Syntax A directive is text in square brackets that starts with `!`. It is written in a comment, using the appropriate comment syntax of the language of that code block. Directives turn on many different features. `` is the name of a directive, such as `focus` or `++`. They all use one of these forms: | Form | Meaning | |---|---| | `[!code ]` | Applies to the line it is on. | | `[!code :N]` | Applies to its line and the next N-1 lines. | | `[!code ]` | Applies each name to the line. | | `[!] ` | Shows `` after the code, for example as a note or a message. The text stops at the next directive or at the end of the comment. | | `[!code ] ` | Applies each name to the line, and shows `` for the first name that can show text. | | `[! //]` | Points at the first match of the literal text on its target line. | | `[\!code ]` | Renders as the literal text `[!code ]`. | For example, `// [!code ++:3]` marks its line and the next two lines as inserted. `// [!code focus ++] Loads the plugin` focuses its line and marks it as inserted, with "Loads the plugin" as the message of the inserted line. Every directive can go at the end of the line it marks, or on its own line above it. The plugin removes each line that holds only directives, so it does not show on the page. - [Directives reference](https://ewels.github.io/starlight-codeblocks/reference/directives/): See every directive and the feature it belongs to. ### Sharing with ordinary comments A directive can share a comment with an ordinary comment. The plugin removes only the directive, and the rest of the comment stays in the code: ````md ```js const host = 'localhost' const port = 8080 // Default port [!code focus] ``` ```` Text after a directive that takes text, such as `[!code error]` or `[!callout]`, is the text of that directive. Put your own comment before the directive. ## Combining directives One code block often needs several features: a focus, a changed line with a message, and a note to explain it. The features work together in one block, and each one keeps its own effect. ### Several names in one directive One `[!code …]` directive can hold several names. Each name applies to the line. The text after the directive goes to the first name that takes text, here `++`. ````md ```js title="astro.config.mjs" import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; import codeblocks from 'starlight-codeblocks'; // [!code focus ++] Import the plugin export default defineConfig({ integrations: [ starlight({ title: 'My docs', plugins: [codeblocks()], // [!code focus ++] Load the plugin }), ], }); ``` ```` The two new lines are green, show their messages, and stay sharp. The other lines are blurred. To give two line states their own messages, use two lines. ### Several directives in one comment One comment can hold several directives. Each directive takes the text up to the next directive. ````md ```py title="retry.py" import time def fetch_with_retry(fetch, retries=3): # [!mention retries] for attempt in range(retries): try: return fetch() except TimeoutError: # [!code warning] Other errors fail at once [!annotate] Only a timeout is worth a retry. time.sleep(2**attempt) # [!code ++] Back off [!mention retries] raise TimeoutError("no response") ``` ```` The `except` line has a warning and an annotation. The `sleep` line is new, and it is part of the [retry loop](#mention:retries) with the first line. ### Attributes and directives together Attributes on the fence line and directives in comments add up. Here the fence line names a placeholder and hides the imports. The comments add a callout, a footnote and an error. ````md ```py title="client.py" placeholder="YOUR_TOKEN" hidden={1-2} import os import requests session = requests.Session() # [!callout /headers/] Sent with every request session.headers["Authorization"] = "Bearer YOUR_TOKEN" # [!ref] The service closes idle connections after 30 s. session.timeout = 30 session.verify = False # [!code error] Never turn off TLS checks ``` ```` Readers can type their token into the field. The copy button copies their token, the hidden imports and none of the notes. ### The kitchen sink This block turns on 15 features at once. It has focus, every line state and a word-level diff. It has annotations, mentions, a callout, a footnote and a code link. On the fence line, it adds a placeholder, hidden lines, visible whitespace, line permalinks, an expandable block and colourised brackets. Its title gets a file icon. Do not publish a block like this. It shows that each feature keeps its own effect. The [health route](#mention:health) and the [app](#mention:app) are mentions. ````md ```js title="server.js" id="kitchen-sink" placeholder="YOUR_API_KEY" hidden={1-2} whitespace="all" expandable={12} brackets import 'dotenv/config'; import express from 'express'; const app = express(); // [!mention app] const PORT = 3000; // [!code --] const PORT = Number(process.env.PORT ?? 3000); // [!code ++] Read the port from the environment app.use(express.json()); // [!code ++] Parse JSON bodies [!annotate] Before any route, or `req.body` is `undefined`. // [!callout /x-api-key/] Checked on every request app.use((req, res, next) => (req.get('x-api-key') === 'YOUR_API_KEY' ? next() : res.sendStatus(401))); // [!ref] Load balancers call this route every 10 s. app.get('/health', (req, res) => { // [!mention health] [!code focus:3] res.json({ ok: true, uptime: process.uptime() }); // [!code success] Always 200 [!mention health] }); // [!link /express.Router/ https://expressjs.com/en/5x/api.html#router] Groups routes under one prefix. const api = express.Router(); // [!mention app] api.get('/users/:id', async (req, res) => { // [!code info] Needs a database const user = await db.users.find(req.params.id); // [!code error] `db` is not defined res.json(user ?? {}); // [!code todo] Return 404 when missing }); // [!code warning] No error handler app.use('/api', api); // [!code highlight] [!annotate] Every route in `api` starts with `/api`. app.listen(PORT, () => console.log('Listening on', PORT)); // [!mention app] ``` ```` ## Behaviour The plugin reads the directives when the site builds. It needs no JavaScript in the browser. - Removing directives: - The plugin removes every directive from the rendered code and from the copied text. - If a comment holds only directives, the plugin removes the whole comment and the whitespace before it. Other text in the comment stays. - In JSX, TSX and MDX, the plugin also reads comments in braces, `{/* [!code focus] */}`, and removes the braces with the comment. - A backslash after the bracket, as in `[\!code focus]`, turns a directive into plain text. The plugin removes the backslash. - Expressive Code markers: - `[!code highlight]`, `[!code ++]` and `[!code --]` give the same result as the `mark`, `ins` and `del` attributes of Expressive Code. Text after them shows as a message, as for [line states](https://ewels.github.io/starlight-codeblocks/features/line-states/). - Text and copy: - Inline code, links and bold in the text of a directive render as HTML. Other Markdown stays as text. - The copy button leaves out messages, annotations, callouts and footnotes. ### Line numbers Line numbers in attributes count the lines that readers see. The plugin removes lines that hold only directives before it counts, so they do not change the numbers. This applies to the attributes of Expressive Code as well, such as `{2}` and `ins={3}`. ### Warnings The build logs a warning with the file, the code block and the line when: - a directive has an unknown name, or the feature it belongs to is off; - the `//` of a directive has no match on its target line; - a directive on its own line has no line below it; - a directive is not in a comment that the block reads, such as `` in an `mdx` block. A directive in a string does not give this warning. The directive then has no effect. An unknown directive, or one outside a comment, stays in the code as written, so you can see it on the page. ## Options ### `notation` Reads directives in code comments. Set it to `false` to turn the feature off. Every comment then renders as written. - Type: `false | object` - Default: On ### `notation.comments` Comment syntax for each language, added to the built-in map. An empty list removes a language. - Type: `Record` - Default: `{}` Each entry in `notation.comments` is a language and a list of comment syntaxes. A syntax with a space in it is a block comment: the opener, then the closer. ```js title="astro.config.mjs" codeblocks({ notation: { comments: { cypher: ['//'], jinja: ['{# #}'], }, }, }); ``` An entry replaces the built-in syntax of that language. A language name or its alias works: an entry for `powershell` also applies to ` ```pwsh `. The built-in map: | Comment syntax | Languages | |---|---| | `//`, `/* */` | `c`, `c#`, `c++`, `cc`, `cjs`, `cpp`, `cs`, `csharp`, `cts`, `dart`, `go`, `groovy`, `h`, `hpp`, `java`, `javascript`, `js`, `json5`, `jsonc`, `kotlin`, `kt`, `kts`, `mjs`, `mts`, `nextflow`, `nf`, `php`, `rs`, `rust`, `scala`, `swift`, `ts`, `typescript` | | `//`, `/* */`, `{/* */}` | `jsx`, `tsx` | | `#` | `bash`, `console`, `docker`, `dockerfile`, `make`, `makefile`, `nix`, `perl`, `pl`, `powershell`, `ps`, `ps1`, `py`, `pycon`, `python`, `r`, `rb`, `ruby`, `sh`, `shell`, `shellscript`, `toml`, `yaml`, `yml`, `zsh` | | `--` | `haskell`, `hs`, `lua`, `sql` | | `` | `html`, `markdown`, `md`, `svg`, `xml` | | `{/* */}` | `mdx` | | ``, `//`, `/* */` | `astro`, `svelte`, `vue` | | `/* */` | `css` | | `/* */`, `//` | `less`, `sass`, `scss` | | `%` | `erl`, `erlang`, `latex`, `tex` | | `;` | `clj`, `clojure`, `ini`, `lisp` | - [Configuration](https://ewels.github.io/starlight-codeblocks/configuration/): Learn how to set options for the whole site, or for a single code block. ## Comment syntax examples Python and shell blocks use `#` comments: ````md ```py title="parse.py" import json data = json.loads(text) # [!code highlight] print(data["name"]) ``` ```` HTML blocks use `` comments. `:2` marks two lines with one directive: ````md ```html title="nav.html" ``` ```` To show a directive as text, for example in a page about this plugin, add a backslash: ````md ```js title="server.js" const port = 8080; // [\!code highlight] ``` ```` ## Limitations - JSON has no comments, so a `json` block cannot use directives. Use `jsonc`, or use attributes. - The plugin skips a comment opener inside a string that opens and closes on the same line, such as `"// [!code ++]"`. A string that continues on the next line, such as a template literal, is not skipped. Escape a directive in it with a backslash. - One comment on each line can hold directives. Text such as `[!NOTE]` outside a comment, as in a Markdown alert, stays in the code. The plugin still reads the comment after it. ## Related - [Focus](https://ewels.github.io/starlight-codeblocks/features/focus/): use `[!code focus]` to blur the lines that do not matter. - [Line states](https://ewels.github.io/starlight-codeblocks/features/line-states/): use `[!code error]` and the other states to tint a line and add a message. --- # Use with an AI agent > Install the starlight-codeblocks agent skill, so an AI coding agent knows each feature, its syntax and when to use it. The plugin comes with an [agent skill](https://github.com/ewels/starlight-codeblocks/tree/main/skills/starlight-codeblocks) for Claude Code, Codex, Cursor, GitHub Copilot and other agents. With the skill, your agent uses the features as it writes new pages. For example, it focuses the lines of a new option, or adds a placeholder for an API token. It also knows when a feature does not help. ## Install the skill Run this command in the folder of your Starlight site. Add `-g` to install it for every project: ```sh npx skills add ewels/starlight-codeblocks ``` The [skills CLI](https://github.com/vercel-labs/skills) installs the skill for each coding agent that you use. Run `npx skills update` to get a new version. An agent without a skills folder can read [`SKILL.md`](https://github.com/ewels/starlight-codeblocks/blob/main/skills/starlight-codeblocks/SKILL.md) on GitHub: - [Agent skill source](https://github.com/ewels/starlight-codeblocks/tree/main/skills/starlight-codeblocks): SKILL.md and the reference files on GitHub. ## Add features to an existing site The skill works well for agents building new sites, but can also retrofit existing content. Ask the agent to add the features to one page, a sidebar group or the whole site. It works through the pages one at a time, and reads each code block in the context of its page. It adds a feature only when the page gives a reason for it. After each page, it tells you what it changed and why. --- # Annotations > Add numbered markers to lines. Each marker opens a note in a popover, so the code stays clean until a reader asks. A comment in a code block explains a line. But it takes space in the code, and readers copy it with the code. An annotation moves the explanation out of the code. The line gets a small numbered marker, and the note opens in a popover when a reader clicks the marker. :::tip[Good for:] Additional helper info that most readers can skip. Takes up almost no additional space until a reader opens a marker. If readers need every note, use [footnotes](https://ewels.github.io/starlight-codeblocks/features/footnotes/) or [side annotations](https://ewels.github.io/starlight-codeblocks/features/side-annotations/). ::: ````md ```yaml title=".github/workflows/test.yml" on: [push, pull_request] jobs: test: runs-on: ubuntu-latest # [!annotate] Uses the Ubuntu GitHub Actions runner. steps: - uses: actions/checkout@v7 - uses: astral-sh/setup-uv@v7 # [!annotate] Installs `uv` and caches its downloads between runs. - run: uv run pytest ``` ```` Each `[!annotate]` comment is gone from the output. A numbered marker is in its place, after the code on the line. Hover over a marker to see its note, or click the marker to keep the note open. Press Escape or click anywhere else to close the notes. ## Syntax | Syntax | Where | |---|---| | `[!annotate] note` | Comment at the end of a line | | `startNoteNumber={N}` | Code block fence line | | `annotations.style="filled"`, `annotations.style="outline"` | Code block fence line | The directive goes at the end of the line it explains. The note is the rest of the comment. It can hold inline code, links and bold text. The markers count from 1, in line order. To continue the numbers of an earlier block, add `startNoteNumber={N}` to the fence line, and the first marker is `N`. `annotations.style` sets the style of the markers for one block. - [Side annotations](https://ewels.github.io/starlight-codeblocks/features/side-annotations/): Use the same syntax to show every note in a column beside the code. ## Examples ### A link in a note A note can hold a link, for a line that needs more explanation than a sentence: ````md ```ts title="retry.ts" export async function retry(task: () => Promise, attempts = 3): Promise { for (let i = 1; ; i++) { try { return await task(); } catch (error) { if (i >= attempts) throw error; // [!annotate] Gives up and passes on the last error. See [Error handling](https://example.com/errors). await new Promise((done) => setTimeout(done, 2 ** i * 100)); // [!annotate] Waits 200 ms, then 400 ms: an exponential backoff. } } } ``` ```` ### The outline style The `outline` style, for one block: ````md ```js annotations.style="outline" const port = 8080 // [!annotate] The default port. Set `PORT` to change it. ``` ```` ## Behaviour - Opening a note: - Hover over a marker to show its note. The mouse cursor can move into the note, for example to click a link. - Click the marker to keep the note open. Click it again to close the note. - Several notes can be open at once. Escape, or a click outside the notes, closes them all. - Where the note opens: - To the right of the marker, over the empty space after the line. A long note wraps at the popover width, 340 px by default. - Under the marker, if the note would cover code or go past the edge of the block or the window. On a phone, most notes open here. - Above the marker, if there is no room under it. - Screen readers: - Each marker is a button with the label "Annotation N". Screen readers read the note directly after it. - Copy and print: - The copy button leaves the markers and the notes out. A manual selection leaves the markers out. - In print, each marker shows its number, and the notes are a numbered list under the block. - Without JavaScript: - The browser's `popover` attribute opens and closes the notes. Each note opens under its marker. - Motion: - The note fades in and out in 160 ms. On hover, the marker changes colour on the same timing, after 80 ms. - With reduced motion, both change at once. ## Options ### `annotations` Adds numbered markers that open a note. Set it to `false` to turn the feature off. - Type: `false | object` - Default: On ### `annotations.style` Filled markers in the accent colour, or outlined markers in the magenta of the theme. Side annotations use it too. A code block can set its own on its fence line. - Type: `'filled' | 'outline'` - Default: `'filled'` The `filled` style draws each marker as a circle filled with the accent colour. The `outline` style draws an outlined circle in the magenta of the theme, and fills it when the note is open. [Footnotes](https://ewels.github.io/starlight-codeblocks/features/footnotes/) have the same two styles. To use outlined markers on the whole site: ```js title="astro.config.mjs" codeblocks({ annotations: { style: 'outline' }, }); ``` With `annotations: false`, the plugin does not know the `[!annotate]` directive. It leaves the comment in the code as written, and logs a build warning. Side annotations are off too. - [Configuration](https://ewels.github.io/starlight-codeblocks/configuration/): Learn how to set options for the whole site, or for a single code block. ## Limitations - The note is part of the source line. Long notes make long lines in the Markdown file. - An annotation applies to one line. For a note about a range of lines, put it on the first line of the range. - Readers see a note only when they open it. If readers need every note to follow the code, use [footnotes](https://ewels.github.io/starlight-codeblocks/features/footnotes/) or [side annotations](https://ewels.github.io/starlight-codeblocks/features/side-annotations/). ## Related - [Inline callouts](https://ewels.github.io/starlight-codeblocks/features/inline-callouts/): a short, always visible note above the line, with an arrow at one name. - [Footnotes](https://ewels.github.io/starlight-codeblocks/features/footnotes/): numbered notes listed under the block, all visible at once. - [Side annotations](https://ewels.github.io/starlight-codeblocks/features/side-annotations/): the same notes in a column beside the code. --- # Side annotations > Show the notes of an annotated block in a column beside the code, so readers see every note next to its line. [Annotations](https://ewels.github.io/starlight-codeblocks/features/annotations/) hide each note in a popover until a reader opens it. For a walkthrough, where readers need every note, that is one click too many. Side annotations use the same `[!annotate]` directive, but show the notes in a column beside the code. :::tip[Good for:] A long block with a short note on many of its lines. Readers see every note at once, each beside its line, and the notes work in Markdown and MDX. To explain the code in paragraphs, one step at a time, use [scrollycoding](https://ewels.github.io/starlight-codeblocks/features/scrollycoding/). ::: ````md ```py title="report.py" annotations="side" import csv import sys from collections import Counter from pathlib import Path def read_rows(path): with path.open() as fh: # [!annotate] Opens the file and closes it when the block ends. return list(csv.DictReader(fh)) # [!annotate] Each row becomes a dict keyed by the header line. def summarise(rows, key): # [!annotate] Counts the rows for each value in the column `key`. return Counter(r[key] for r in rows) def main(): rows = read_rows(Path(sys.argv[1])) counts = summarise(rows, "status") for status, n in counts.items(): print(f"{status:<10} {n:>5}") # [!annotate] Pads the status and count into columns of fixed width. if __name__ == "__main__": main() ``` ```` Each annotated line has a small number, and the note with the same number is in the column on the right. Hover over a note, or move keyboard focus to it, to highlight its line. Hover over a line to highlight its note. ## Syntax | Syntax | Where | |---|---| | `annotations="side"` | Code block fence line | | `[!annotate] note` | Comment at the end of a line | | `codeSide="right"` | Code block fence line | | `startNoteNumber={N}` | Code block fence line | | `annotations.style="filled"`, `annotations.style="outline"` | Code block fence line | Add `annotations="side"` to the fence line (the first line of the code block, with the language) of a block that uses `[!annotate]`. The directive works as on the [annotations](https://ewels.github.io/starlight-codeblocks/features/annotations/) page. The code is in the left column. To put it in the right column, add `codeSide="right"`. `startNoteNumber={N}` numbers the notes from `N`. `annotations.style` sets the style of the numbers for one block, as for [annotations](https://ewels.github.io/starlight-codeblocks/features/annotations/#options). ## Examples ### Code on the right `codeSide="right"` puts the code in the right column and the notes in the left column: ````md ```yaml title="config.yml" annotations="side" codeSide="right" server: port: 8080 # [!annotate] The port that the server listens on. host: 0.0.0.0 # [!annotate] Listens on every network interface. log: level: info # [!annotate] One of `debug`, `info`, `warn` or `error`. ``` ```` ### The outline style `annotations.style="outline"` draws the numbers on the lines as outlined circles in the magenta of the theme: ````md ```py title="greet.py" annotations="side" annotations.style="outline" import sys # [!annotate] Gives access to the command-line arguments. name = sys.argv[1] # [!annotate] The first argument after the file name. print(f"Hello, {name}!") # [!annotate] An f-string puts the value of `name` in the text. ``` ```` ## Behaviour - Layout: - The code and the notes are two columns when the block's container has space for the longest line beside the notes. The plugin measures the lines at build time and gives each block one of three widths: 600, 800 or 1000 px. In a container narrower than that width, such as on a phone, the notes are a numbered list under the block. - At 600 px, the code column shows about 42 characters on a line. At 800 px it shows about 66, and at 1000 px about 90. A line with a number needs 3 characters more, and the first line needs 4 more for the copy button. - The notes column is at least 12rem wide. - Scrolling: - The notes column starts level with the top of the block. It sticks below the site header while the block scrolls past. - If the notes column is taller than the space below the header, it scrolls with the page instead. - Highlighting: - Hovering over a note, or focusing it with the Tab key, highlights its line. Hovering over a line highlights its note. - A number on a line changes colour when the mouse cursor is on it, the same as an annotation marker. - Clicking a note or its number keeps the highlight, as for [footnotes](https://ewels.github.io/starlight-codeblocks/features/footnotes/). A second click clears it. Several can stay highlighted. A click anywhere else clears them all. - With keyboard focus on a note, Enter or Space keeps or clears its highlight. - Screen readers: - The numbers on the lines are hidden from screen readers. Screen readers read the notes as a list after the code. - Copy: - The copy button leaves the numbers and the notes out. - Without JavaScript: - The layout works, but notes and lines do not highlight each other. - Motion: - Under reduced motion, the highlight changes at once. ## Options Side annotations have no options of their own. They use the `style` option of [annotations](https://ewels.github.io/starlight-codeblocks/features/annotations/#options), and a block can set its own with `annotations.style="outline"`. `annotations: false` turns them off together with annotations: ```js title="astro.config.mjs" codeblocks({ annotations: false, }); ``` - [Configuration](https://ewels.github.io/starlight-codeblocks/configuration/): Learn how to set options for the whole site, or for a single code block. ## Make space for long lines Starlight's content column is 45rem (720 px) wide on each page that has a sidebar. So on a default page, a block with lines of more than about 42 characters shows the notes as a list under the code. On a page without a table of contents, the column has free space on each side. A block that needs 800 or 1000 px spreads over that space, so it gets the columns in a wide window. The text stays in the content column. [See the demo on a page without a table of contents](https://ewels.github.io/starlight-codeblocks/features/side-annotations/wide/) To use this, turn off the table of contents of the page: ```md title="src/content/docs/guides/walkthrough.md" --- title: Walkthrough tableOfContents: false --- ``` - The block spreads only when it is directly on the page. A block in tabs, an aside, a list or a component stays in its column. - The block spreads by the same amount on each side, and not more than it needs for the columns. - If the window is too narrow for the columns, the block stays in the content column and shows the list. - A block that fits the content column does not spread. ## Limitations - The columns need a container of 600 px or more. Blocks with long lines need a wider container, or a page without a table of contents. Smaller windows and phones get the list under the block. - The widths are estimates for Starlight's default code font. A larger code font can make a line scroll in the code column. - Lines of more than about 90 characters scroll inside the code column at every width. - Each note is next to its number, not next to its line. Notes in the column follow each other from the top of the block. ## Related - [Annotations](https://ewels.github.io/starlight-codeblocks/features/annotations/): the same notes in popovers, for blocks where most readers do not need every note. - [Footnotes](https://ewels.github.io/starlight-codeblocks/features/footnotes/): the notes in a list under the block at every width, with badges that link the two. - [Scrollycoding](https://ewels.github.io/starlight-codeblocks/features/scrollycoding/): prose steps that scroll past the block, which focuses the lines of each step. --- # 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. --- # Inline callouts > Put a short note in a bubble above a line, with an arrow that points at the word it explains. A code block shows the code, and the prose around it explains it. When the explanation is about one name on one line, a reader must find that name themselves. An inline callout puts the note in a bubble directly above the line, with an arrow that points down at the name. :::tip[Good for:] A note of one short sentence about one name on a line. The bubble is always visible, so keep to one or two callouts in a block. For a longer note, use [annotations](https://ewels.github.io/starlight-codeblocks/features/annotations/) or [footnotes](https://ewels.github.io/starlight-codeblocks/features/footnotes/). ::: ````md ```js const controller = new AbortController(); // [!callout /signal/] Lets `controller.abort()` cancel the request. const res = await fetch(url, { signal: controller.signal }); const data = await res.json(); ``` ```` The comment line with `[!callout /signal/]` is gone from the output. The bubble takes its place, and its arrow points at the middle of the first `signal` on the line below. ## Syntax | Syntax | Where | |---|---| | `[!callout /text/] note` | Comment on or above the line | | `[!callout] note` | Comment on or above the line | The directive goes at the end of the line it explains, or on its own line directly above it. `/text/` is literal text, not a regular expression. The arrow points at the first match on that line. Without `/text/`, the arrow points at the first character that is not a space. The note can hold inline code, links and bold text. ## Examples ### A callout for the whole line A callout without `/text/` points at the start of the line. This suits a note about the whole statement: ````md ```py import sqlite3 # [!callout] Creates the file if it does not exist yet. db = sqlite3.connect("notes.db") db.execute("CREATE TABLE IF NOT EXISTS notes (body TEXT)") ``` ```` ### Two callouts on one line Two callouts above one line explain two parts of it. The bubbles stack in source order: ````md ```ts // [!callout /retries/] How many times to try again after the first attempt. // [!callout /backoffMs/] The wait before each retry, in milliseconds. const client = createClient({ retries: 3, backoffMs: 250 }); ``` ```` ## Behaviour - Where the bubble shows: - The plugin removes the directive, and its line if the line holds nothing else. The bubble is above the target line. - The bubble starts 40 px to the left of the arrow, but never closer than 8 px to the edge of the block. - The bubble is at most 60 characters wide, or 90% of the block. Longer notes wrap inside the bubble, so the block does not get wider. - On a narrow screen, the target text can be past the right edge of the block. The bubble and its arrow then stay at the right edge, where the reader can see them. - Two callout lines above one line give two bubbles, in the order you wrote them. - Highlights: - If the lines above and below the callout have the same highlight, the callout has it too, so the highlight does not break. This works for marked, inserted and deleted lines and for [line states](https://ewels.github.io/starlight-codeblocks/features/line-states/). - Screen readers: - Screen readers read the bubble before its line. The bubble has the `note` role. - Copy: - The copy button leaves callouts out. A manual selection of the code leaves them out too. - To select the text of a bubble, start the selection in the bubble. - Without JavaScript: - Callouts are plain HTML and CSS. They work without JavaScript, and they have no animation. Without JavaScript, the text of a bubble cannot be selected. ## Options Inline callouts have no options. Turn the feature off for the whole site with `callouts: false`: ```js title="astro.config.mjs" codeblocks({ callouts: false, }); ``` With `callouts: false`, the plugin does not know the `[!callout]` directive. It leaves the directive line 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 - The plugin counts columns in characters. A tab counts to the next multiple of 2 columns, which is the `tab-size` the plugin sets on code blocks. If your site sets another `tab-size`, arrows on lines with tabs after other characters point at the wrong place. - With Expressive Code's `wrap` attribute, a long line can wrap below its callout. The arrow then points at the first row of the line. - A callout explains one line. For a note about several lines, use [annotations](https://ewels.github.io/starlight-codeblocks/features/annotations/) or [footnotes](https://ewels.github.io/starlight-codeblocks/features/footnotes/). ## Related - [Annotations](https://ewels.github.io/starlight-codeblocks/features/annotations/): numbered markers that open a note only when the reader asks for it. - [Footnotes](https://ewels.github.io/starlight-codeblocks/features/footnotes/): numbered notes listed under the block, all visible at once. --- # Scrollycoding > Explain a code block in prose steps that scroll past it, while the block stays in view and focuses the lines of each step. A walkthrough of a file often puts the whole block first, then explains it in paragraphs below. Readers scroll back up to find each line that a paragraph names. Scrollycoding keeps the block next to the prose. As each step scrolls past the middle of the block, the block focuses the lines of that step. :::tip[Good for:] A tutorial that explains a block in paragraphs, one step at a time. To show versions of a file with only a short label for each step, use a [code walkthrough](https://ewels.github.io/starlight-codeblocks/features/code-walkthrough/). For a short note on each of many lines, all visible at once, use [side annotations](https://ewels.github.io/starlight-codeblocks/features/side-annotations/). ::: ````md ```js title="server.js" import express from 'express'; const app = express(); app.use(express.json()); app.get('/health', (req, res) => { res.json({ ok: true }); }); app.listen(3000); ``` Import Express. Nothing else is needed for a small server. Create the app object that holds routes and middleware. Parse JSON request bodies before any route sees them. Answer health checks with a small JSON payload. Start listening on port 3000. ```` Scroll down the page. The block stays below the site header, and each step that reaches the middle of the block changes the focus. On a narrow screen, each step has its own copy of the block, focused for that step. ## Syntax | Syntax | Where | |---|---| | `` ... `` | Around the code blocks and the steps, in an MDX file | | `` ... `` | After the code block, one for each step | | `focus=""` | On `` | | `mark=""` | On `` | | A code block between two steps | After a `` | | `codeSide="left"` | On `` | Import both components at the top of the MDX file: ```mdx import { Scrollycoding, Step } from 'starlight-codeblocks/components'; ``` Put one code block first, then the steps. Leave an empty line after `` and before ``. `focus` and `mark` take a [range](https://ewels.github.io/starlight-codeblocks/comment-notation/#line-numbers) without braces, such as `4` or `1, 6-8`. A step can contain any Markdown, such as links, inline code or several paragraphs. Text outside the steps fails the build: put it in a step, or before or after ``. To change the code during the walkthrough, put a new version of the code block between two steps. The steps after it show the new version, and their `focus` and `mark` ranges count its lines. See [Change the code between steps](#change-the-code-between-steps). ## Examples ### Mark a line in a step A step can mark a line as well as focus a range. Here, the second step keeps the whole function in focus and marks the line that changes the total: ````md ```py title="cart.py" def total(items, discount=0): price = sum(i.price for i in items) return price * (1 - discount) ``` The function takes the items and an optional discount, from 0 to 1. The last line applies the discount to the sum. ```` ### Change the code between steps A code block between two steps is a new version of the code. Here, the third step adds a line, and the block animates to the new version when that step becomes active: ````md ```js title="server.js" const app = express(); app.listen(3000); ``` Create the app object that holds routes and middleware. Start listening on port 3000. ```js title="server.js" const app = express(); app.use(express.json()); app.listen(3000); ``` Parse JSON request bodies before any route sees them. The server still listens on port 3000. ```` ### Code on the left `codeSide="left"` puts the block in the left column and the steps in the right column: ````md ```py title="cart.py" def total(items, discount=0): price = sum(i.price for i in items) return price * (1 - discount) ``` The function takes the items and an optional discount, from 0 to 1. Add up the price of each item. Apply the discount to the sum. ```` ## Behaviour - Layout: - On a wide screen, the steps are a column on the left, and the block is on the right. With `codeSide="left"`, the block is on the left and the steps are on the right. The block stays below the site header while the steps scroll with the page. - The two columns need space for the longest line of the block beside a text column of 12rem. The plugin measures the lines at build time and gives the block one of three widths: 600, 800 or 1000 px. In a narrower container, the page shows the narrow layout. The widths are the same as for [side annotations](https://ewels.github.io/starlight-codeblocks/features/side-annotations/#make-space-for-long-lines). - On a narrow screen, each step shows its own copy of the block below its text, in the version of that step. - Scrolling through the steps: - The step that crosses the middle of the block is the active step. It shows at full opacity, and the other steps fade. - When the active step changes, the block changes its focused and marked lines, with the 250 ms transition of [focus](https://ewels.github.io/starlight-codeblocks/features/focus/). - When the active step has a new version of the code, the code moves to the new version as in a [code walkthrough](https://ewels.github.io/starlight-codeblocks/features/code-walkthrough/). Code that is in both versions moves, and new lines fade in with a green tint. - The block stays blurred when the mouse cursor is over it, because the mouse cursor often rests there while the reader scrolls. Move keyboard focus into the block to make every line sharp. - Copy: - The copy button copies the whole block, in every step. - Without JavaScript: - The page shows the narrow layout at every width. - Motion: - When the reader's system asks for reduced motion, the focus, the fade and the code change without a transition. ## Options Scrollycoding has no options. To turn the feature off for the whole site, set `scrollycoding` to `false`: ```js title="astro.config.mjs" codeblocks({ scrollycoding: false, }); ``` With `scrollycoding: false`, `` shows the narrow layout at every width, with no script. With `walkthrough: false`, a new version of the code shows without the animation. - [Configuration](https://ewels.github.io/starlight-codeblocks/configuration/): Learn how to set options for the whole site, or for a single code block. ## Make space for long lines A block with lines of more than about 41 characters needs more than 600 px for the two columns. On a page without a table of contents, the content column has free space on each side. A `` that needs 800 or 1000 px spreads over that space, as [side annotations](https://ewels.github.io/starlight-codeblocks/features/side-annotations/#make-space-for-long-lines) do. [See the demo on a page without a table of contents](https://ewels.github.io/starlight-codeblocks/features/scrollycoding/wide/) To use this, turn off the table of contents of the page with `tableOfContents: false` in its front matter. The component spreads only when it is directly on the page, not in tabs, an aside or another component. ## Limitations - `` works in MDX files only, because Markdown files cannot contain components. - During the animation to a new version, the block shows the syntax colours only. The focus shows again when the animation ends. - The widths are estimates for Starlight's default code font. A larger code font can make a line scroll in the code column. - The narrow layout repeats the block for each step, so a long block with many steps makes a long page. - The component replaces the focus of the block. A `focus` attribute on the fence line (the first line of the code block, with the language) has no effect. A mark on the fence line, such as `{2}`, shows in every step. The steps focus their lines with `focus: false` too. ## Related - [Focus](https://ewels.github.io/starlight-codeblocks/features/focus/): one block with one focus range, with no prose steps. - [Code walkthrough](https://ewels.github.io/starlight-codeblocks/features/code-walkthrough/): versions of a block, with buttons to step through them and no prose between the steps. - [Side annotations](https://ewels.github.io/starlight-codeblocks/features/side-annotations/): notes for many lines in a column next to the block, all visible at once. --- # Code walkthrough > Step through versions of one code block, and watch the code move from each version to the next, so readers see what changed. A tutorial often shows the same file several times, with a few lines more each time. Readers must compare the blocks line by line to find the change. A code walkthrough puts the versions in one block with numbered steps in the title bar. When the reader changes step, unchanged code moves to its new place, and new code fades in. :::tip[Good for:] A file that grows over a few steps, where each step needs only a short label. The versions take the space of one block, and readers step through them with buttons. To explain each step in paragraphs next to the code, use [scrollycoding](https://ewels.github.io/starlight-codeblocks/features/scrollycoding/). ::: ````md ```js title="server.js" step="Create the app" const app = express(); app.listen(3000); ``` ```js title="server.js" step="Parse JSON bodies" const app = express(); app.use(express.json()); app.listen(3000); ``` ```js title="server.js" step="Add a health route" const app = express(); app.use(express.json()); app.get('/health', (req, res) => { res.json({ ok: true }); }); app.listen(3000); ``` ```` Click **Next**, and `app.listen(3000);` moves down while the new line fades in. Each numbered step shows one version of the file. The label of the current step shows next to the numbers. ## Syntax | Syntax | Where | |---|---| | `` ... `` | Around the code blocks, in an MDX file | | `step="