Annotations Add numbered markers to lines. Each marker opens a note in a popover, so the code stays clean until a reader asks.
Read docs: Annotations runs-on : ubuntu-latest 1 1 Uses the Ubuntu GitHub Actions runner.
- uses : actions/checkout@v7
- uses : astral-sh/setup-uv@v7 2 2 Installs uv and caches its downloads between runs.
Uses the Ubuntu GitHub Actions runner. Installs uv and caches its downloads between runs. Side annotations Show the notes of an annotated block in a column beside the code, so readers see every note next to its line.
Read docs: Side annotations from collections import Counter
return list ( csv. DictReader ( fh )) 2
def summarise ( rows , key ) : 3
return Counter ( r [ key ] for r in rows )
rows = read_rows ( Path ( sys.argv [ 1 ]))
counts = summarise ( rows , " status " )
for status, n in counts. items ():
print ( f " {status :<10 } {n :>5 } " ) 4
if __name__ == " __main__ " :
1 Note 1, for line 8: Opens the file and closes it when the block ends.2 Note 2, for line 9: Each row becomes a dict keyed by the header line.3 Note 3, for line 12: Counts the rows for each value in the column key.4 Note 4, for line 20: Pads the status and count into columns of fixed width.Footnotes Add numbered badges to lines, with the notes in a list under the block, where readers see them all at once.
Read docs: Footnotes Inline callouts Put a short note in a bubble above a line, with an arrow that points at the word it explains.
Read docs: Inline callouts const controller = new AbortController ();
Lets controller.abort() cancel the request.
const res = await fetch ( url , { signal: controller . signal } );
const data = await res . json ();
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.
Read docs: Scrollycoding 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.
Read docs: Code walkthrough ‹ Previous Step 1 of 3 Next ›
‹ Previous Step 2 of 3 Next ›
app . get ( ' /health ' , ( req , res ) => {
‹ Previous Step 3 of 3 Next ›
Focus Blur the lines outside a range, so that readers look at the lines that you name first.
Read docs: Focus import { defineConfig } from ' ./lib.js ' ;
export default defineConfig ({
Line states Tint lines as errors, warnings, notes or successes, with an optional message after the code, like the diagnostics in a code editor.
Read docs: Line states Error: for name in sys.argv[ 1 :] Error SyntaxError: expected ':'
Warning: count = len ( sys.argv ) Warning Includes the script name
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.
Read docs: Code mentions The base case stops the recursion. The recursive step calls the function again with a smaller number.
return n * factorial ( n - 1 )
Hidden lines Hide the imports and set-up that readers need to run an example but not to understand it.
Read docs: Hidden lines 3 hidden lines config = json. loads ( Path ( " config.json " ) . read_text ())
for key, value in config. items ():
2 hidden lines print ( f " {key} = {value} " )
from collections import Counter
def read_rows ( path : Path ) -> list[ dict ]:
return list ( csv. DictReader ( fh ))
def summarise ( rows : list[ dict ] ) -> Counter:
return Counter ( row [ " status " ] for row in rows )
rows = read_rows ( Path ( sys.argv [ 1 ]))
for status, n in counts. most_common ():
print ( f " {status :<10 } {n :>5 } " )
if __name__ == " __main__ " :
Show all 24 lines
Visible whitespace Show spaces and tabs as faint glyphs, for the blocks where indentation changes the meaning of the code.
Read docs: Visible whitespace cargo test # Recipes must start with a tab
const title = document . title ;
const slug = encodeURIComponent (
kebab ( trim ( title . toLowerCase ( ) ) )
Colour swatches Show a small swatch of each CSS colour next to its value, so readers see the colour without a colour picker.
Read docs: Colour swatches background : rebeccapurple ;
border : 1 px solid rgb ( 102 51 153 / 50 % ) ;
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.
Read docs: File icons The default vscode-icons:
export const greet = ( name ) => console . log ( ' Hello ' , name );
Material Icon Theme, with fileIcons.set="material":
print ( " Hello from Python " )
The Seti icons of the Starlight file tree, with fileIcons.set="seti":
{ "name" : " my-site " , "type" : " module " }
Catppuccin, with fileIcons.set="catppuccin":
CMD [ "node" , "src/index.js" ]
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.
Read docs: Inline code highlighting
In JavaScript, [] + {} is " [object Object] " .
In Python, from __future__ import braces raises SyntaxError : not a chance .
In CSS, .modal { z-index : 99999 !important } is a cry for help.
In a shell, rm -rf node_modules fixes most things.
Word-level diff Highlight the words that changed inside each line of a diff, so readers find a small edit in a long line.
Read docs: Word-level diff const timeout = options . timeout ?? 5000 ;
const client = createClient ( {
retries: options . retries ,
Code links Turn text in code into a link with a card that describes it, from a directive in the comment above.
Read docs: Code links y = np. sin ( 2 * np.pi * x )
API auto-linking Link the names in code examples to their reference pages, with a card that shows the signature and a summary.
Read docs: API auto-linking 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.
Read docs: Line permalinks 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.
Read docs: Code tabs - uses : actions/checkout@v5
name = sys.argv[ 1 ] if len ( sys.argv ) > 1 else " world "
const name = process . argv [ 2 ] ?? ' world ' ;
console . log ( ` Hello, ${ name } ! ` );
Fill-in placeholders Turn placeholders such as YOUR_TOKEN into fields, so readers type their own values into every block and the copied code.
Read docs: Fill-in placeholders from example import Client
Smart shell copy Add a Copy commands button to terminal blocks, which copies the commands without the prompts or the output.
Read docs: Smart shell copy Resolved 1 package in 180ms
Installed 1 executable: ruff
Found 3 errors (3 fixed, 0 remaining).
Open in playground Add a title bar button that opens the example in an online playground, with the code already filled in.
Read docs: Open in playground type Status = ' queued ' | ' running ' | ' done ' ;
function label ( s : Status ) : string {
return s === ' done ' ? ' Finished ' : s;
console . log ( label ( ' done ' ));
Run code Add a Run code button that runs the example in the browser and shows the output under the block.
Read docs: Run code from statistics import mean, stdev
reads = [ 1520 , 1610 , 1480 , 1575 ]
print ( f "mean { mean ( reads ) :.0f } , sd { stdev ( reads ) :.1f } " )
Run code
Previousfeature
Nextfeature
Pause
Play
Add the package to the site:
npm install starlight-codeblocks
pnpm add starlight-codeblocks
yarn add starlight-codeblocks
Then add codeblocks() to the Starlight plugins in astro.config.mjs:
import { defineConfig } from ' astro/config ' ;
import starlight from ' @astrojs/starlight ' ;
import codeblocks from ' starlight-codeblocks ' ; Import the plugin export default defineConfig ({
The getting started page has the full steps.
Extend : your own API link adapters, playgrounds and runtimes.
Reference : every option, attribute, directive and style setting.
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 .
llms.txt lists every page, and llms-full.txt has every page in one file.
Using an agent? Point it at the bundled skill . The skill tells the agent which feature suits each use, and how to write it.
This plugin builds on the excellent Expressive Code , which renders every code block in Starlight.
The [!code …] comment notation comes from the Shiki transformers and VitePress . Thank you to both projects. Most code blocks from VitePress work here unchanged.