This is the abridged developer documentation for Starlight Quiz
# Starlight Quiz
> Interactive quizzes for Astro and Starlight, authored in markdown.
**Starlight Quiz** adds interactive, self-marking quizzes to your Astro and Starlight documentation. ## Features [Section titled “Features”](#features) * ✨ **Simple markdown syntax** — write quizzes with GitHub-flavoured task lists. No new syntax to learn. * 🎯 **Multiple quiz types** — single choice, multiple choice and fill-in-the-blank. * ⚡ **Instant feedback** — per-answer feedback and visual correct/incorrect indicators. * 📝 **Rich explanations** — reveal a markdown content section after answering: code, tables and images. * 📊 **Progress & results** — an aggregate results panel with score tiers and confetti. * 💾 **Results saved** — answers persist to the browser’s local storage, surviving reloads and rebuilds. * 🔀 **Author controls** — shuffle answers, auto-number questions and set site-wide defaults. * 🌐 **Internationalisation** — 13 languages out of the box, shared with mkdocs-quiz. * 🧩 **Works anywhere** — a zero-config Starlight plugin or standalone in any Astro project. * ♿ **Accessible** — real fieldsets, `aria-live` feedback and keyboard-safe auto-submit. * 📤 **LMS export** — export to QTI 1.2/2.1 for Canvas, Moodle and Blackboard. * 🖥️ **Terminal runner** — take quizzes in your terminal, from a local build or a deployed site. Try it: Which of these does Starlight Quiz support out of the box? * [x] Single-choice questions * [x] Multiple-choice questions * [x] Fill-in-the-blank questions * [ ] Mind reading > Not yet, but everything else here is real. When more than one answer is correct, the quiz automatically switches to checkboxes. Here’s a cat for getting this far:  ## Where next? [Section titled “Where next?”](#where-next) [Quick start](/starlight-quiz/guides/quick-start/)Install the plugin and write your first quiz. [Multiple choice](/starlight-quiz/guides/multiple-choice/)Radio buttons, checkboxes and answer syntax. [Fill-in-the-blank](/starlight-quiz/guides/fill-in-the-blank/)Text-input questions. [Configuration](/starlight-quiz/guides/configuration/)Every plugin option and component prop. [Live demo](/starlight-quiz/demo/)Every question type on one page. ## Reading these docs with an AI assistant [Section titled “Reading these docs with an AI assistant”](#reading-these-docs-with-an-ai-assistant) Every page has **Copy Markdown** and **Open in…** buttons beside its title, and serves its own source at the same URL with `.md` appended. The whole site is also available as [`llms.txt`](https://ewels.github.io/starlight-quiz/llms.txt) (an index), [`llms-full.txt`](https://ewels.github.io/starlight-quiz/llms-full.txt) (everything) and [`llms-small.txt`](https://ewels.github.io/starlight-quiz/llms-small.txt) (trimmed for smaller context windows). ## Credits [Section titled “Credits”](#credits) **Starlight Quiz** is written by [Phil Ewels](https://github.com/ewels). It is a port of [mkdocs-quiz](https://github.com/ewels/mkdocs-quiz), which was originally written by [Sebastian Jörz](https://github.com/skyface753) before being rewritten by [Phil Ewels](https://github.com/ewels).
# Advanced formatting
> Code blocks, tables, lists and images inside quizzes.
Because a quiz is just markdown, the question, the answers and the content section can all contain rich markdown: code blocks, tables, lists, images and inline formatting. ## Code in the question [Section titled “Code in the question”](#code-in-the-question) * Preview What does this snippet log?
```js
const xs = [1, 2, 3];
console.log(xs.map((x) => x * 2));
```
* [ ] `[1, 2, 3]` * [x] `[2, 4, 6]` * [ ] `[1, 4, 9]` `Array.prototype.map` returns a new array with the callback applied to each element. * Source quiz.mdx
````mdx
What does this snippet log?
```js
const xs = [1, 2, 3];
console.log(xs.map((x) => x * 2));
```
- [ ] `[1, 2, 3]`
- [x] `[2, 4, 6]`
- [ ] `[1, 4, 9]`
`Array.prototype.map` returns a new array with the callback applied to each element.
````
## Code block features [Section titled “Code block features”](#code-block-features) Code blocks in a quiz are rendered by [Expressive Code](https://expressive-code.com/), so the full meta-string syntax works inside a quiz too: a filename `title`, highlighted lines (`{2}`), and `ins`/`del` diff markers: * Preview This refactor introduced a bug. Which highlighted change causes it? cart.js
```diff
function total(items) {
let sum = 0;
for (const item of items) {
- sum += item;
+ sum += item.price;
}
return sum;
}
```
* [x] The inserted line should read `item.price`, not the removed `item` * [ ] The highlighted initialiser * [ ] The loop header * Source quiz.mdx
````mdx
This refactor introduced a bug. Which highlighted change causes it?
```js title="cart.js" {2} del={4} ins={5}
function total(items) {
let sum = 0;
for (const item of items) {
sum += item;
sum += item.price;
}
return sum;
}
```
- [x] The inserted line should read `item.price`, not the removed `item`
- [ ] The highlighted initialiser
- [ ] The loop header
````
You can also give a marker a [label on its own line](https://expressive-code.com/key-features/text-markers/#adding-long-labels-on-their-own-lines) with the `{"label":line}` syntax, handy for walking through an answer in the content section:
````md
```js {"This runs first, the accumulator starts at zero:":1}
let sum = 0;
```
````
## Rich content section [Section titled “Rich content section”](#rich-content-section) The content section revealed after submitting can hold headings, lists, tables and more: * Preview Which status code means “Not Found”? * [ ] 200 * [ ] 301 * [x] 404 * [ ] 500 ### Common status codes [Section titled “Common status codes”](#common-status-codes) | Code | Meaning | | ---- | --------------------- | | 200 | OK | | 301 | Moved Permanently | | 404 | Not Found | | 500 | Internal Server Error | * Source quiz.mdx
```mdx
Which status code means "Not Found"?
- [ ] 200
- [ ] 301
- [x] 404
- [ ] 500
### Common status codes
| Code | Meaning |
| ---- | --------------------- |
| 200 | OK |
| 301 | Moved Permanently |
| 404 | Not Found |
| 500 | Internal Server Error |
```
## Formatting in answers [Section titled “Formatting in answers”](#formatting-in-answers) Answers themselves accept inline markdown: `code`, **bold**, *italic* and links: * Preview Which command installs the package? * [x] `npm install starlight-quiz` * [ ] `npm start starlight-quiz` * [ ] `npm run starlight-quiz` * Source quiz.mdx
```mdx
Which command installs the package?
- [x] `npm install starlight-quiz`
- [ ] `npm start starlight-quiz`
- [ ] `npm run starlight-quiz`
```
# Auto-numbering
> Prefix each quiz with a numbered "Question N" heading.
Turn on auto-numbering to prefix every quiz with a **“Question N”** heading, numbered in order down the page. It’s off by default. ## Enable it everywhere [Section titled “Enable it everywhere”](#enable-it-everywhere) Set it once as a [site-wide default](/starlight-quiz/guides/configuration/#quizdefaults) so every quiz is numbered: astro.config.mjs
```js
starlightQuiz({
quizDefaults: { autoNumber: true },
});
```
## Per quiz [Section titled “Per quiz”](#per-quiz) Or opt a single quiz in (or out) with the `autoNumber` prop, which overrides the site default: * Preview What does HTTP stand for? * [x] HyperText Transfer Protocol * [ ] High Throughput Transfer Path * Source quiz.mdx
```mdx
What does HTTP stand for?
- [x] HyperText Transfer Protocol
- [ ] High Throughput Transfer Path
```
The count continues across the page, so a second numbered quiz becomes **Question 2**: Which port does HTTPS use by default? * [x] 443 * [ ] 80 The number is the quiz’s position among the auto-numbered quizzes on the page, computed in the browser, so it stays correct even with [shuffled answers](/starlight-quiz/guides/shuffle-answers/) or after an Astro view transition. The heading is a `.sl-quiz-number` element you can [restyle](/starlight-quiz/guides/custom-css/#styling-individual-elements).
# Terminal runner
> Take your quizzes in the terminal, straight from a build or a deployed site.
`starlight-quiz` ships a small command-line tool that lets you **take the quizzes from your site in the terminal**, without a browser. It is handy for quickly checking your own questions while writing them, reviewing a topic from the command line, or running a quiz over a deployed site. The same manifest also powers [QTI export](/starlight-quiz/guides/qti-export/). ## The quiz manifest [Section titled “The quiz manifest”](#the-quiz-manifest) The CLI never parses your MDX — it reads the rendered output. A single page is scraped straight from its HTML, while a whole site is read from a **quiz manifest**: a `quiz-manifest.json` file listing every quiz on your site, with each question, its answers and which are correct. The plugin writes the manifest by default, so there is usually nothing to set up. (Opt out or rename it with the [`manifest`](/starlight-quiz/guides/configuration/#manifest) option.) The manifest is built from your rendered pages, so it is available wherever the site runs: * On a deployed site it sits at the root alongside your pages (e.g. `https://your-docs.example/quiz-manifest.json`), so the CLI can fetch it straight from the URL. * The dev server serves it too, generated on the fly at the same path (e.g. `http://localhost:4321/quiz-manifest.json`), so you can point the CLI at your local dev site. * A production build writes it to disk as a static file in your output directory. ## Take a quiz [Section titled “Take a quiz”](#take-a-quiz) Point `run` at the **full URL of a page** to take just that page’s quizzes — scraped straight from the rendered HTML, no manifest required:
```sh
npx starlight-quiz run https://ewels.github.io/starlight-quiz/guides/multiple-choice/
```
To take **every quiz on the site at once**, point it at the manifest instead — its URL, a local build directory, or the JSON file directly:
```sh
npx starlight-quiz run https://ewels.github.io/starlight-quiz/quiz-manifest.json
npx starlight-quiz run ./dist
```
Each question is printed in turn; you type the number(s) of your choice (or the text for a fill-in-the-blank) and press Enter. The runner grades each answer, reveals the correct one when you are wrong, and prints a final score with a tier message: npx starlight-quiz run https\://ewels.github.io/starlight-quiz/guides/multiple-choice/
```text
Question 1
──────────
What is the capital of France?
1. London
2. Paris
3. Berlin
Your answer: 3
Correct answer: Paris
✗ Incorrect.
Question 2
──────────
Which of these are even numbers?
1. 2
2. 3
3. 4
4. 5
Your answers (e.g. 1,3): 1,3
✓ Correct!
…
Score: 3/4 (75%)
Great job! You really know your stuff!
```
### Shuffling [Section titled “Shuffling”](#shuffling) Add `--shuffle` to randomise the quiz order and the answer order within each quiz, or `--shuffle-answers` to randomise only the answers:
```sh
npx starlight-quiz run https://ewels.github.io/starlight-quiz/quiz-manifest.json --shuffle
```
## History [Section titled “History”](#history) Every run is recorded to `~/.local/share/starlight-quiz/history.json` (honouring `XDG_DATA_HOME`). Review or clear it:
```sh
npx starlight-quiz history # a table of past runs
npx starlight-quiz history --json # the raw JSON
npx starlight-quiz history --yaml # the same as YAML
npx starlight-quiz history --clear # wipe it
```
npx starlight-quiz history
```text
Date Source Result Score
──────────────────────── ────────────────────────────────────── ────── ─────
2026-06-29T22:15:29.682Z https://ewels.github.io/starlight-quiz 3/4 75%
```
## Options [Section titled “Options”](#options) | Command | Argument / flag | Description | | --------- | ------------------------------- | ---------------------------------------------------------------------------- | | `run` | `` | A page URL (scraped), a manifest URL or file, or a directory containing one. | | `run` | `--shuffle` | Randomise quiz and answer order. | | `run` | `--shuffle-answers` | Randomise only the answer order within each quiz. | | `run` | `--filename` | Manifest filename for a local directory (default `quiz-manifest.json`). | | `history` | `--json` · `--yaml` · `--clear` | Print past runs as JSON · as YAML · delete the saved history. | To turn the same quizzes into an LMS import instead, see [QTI export](/starlight-quiz/guides/qti-export/).
# Configuration
> Every plugin option and component prop.
Starlight Quiz needs no configuration to get started: `plugins: [starlightQuiz()]` is enough. This page lists everything you *can* change. ## Plugin options [Section titled “Plugin options”](#plugin-options) The plugin takes a single options object: astro.config.mjs
```js
starlightQuiz({
injectStyles: true, // append the bundled theme CSS to Starlight (default true)
});
```
### `injectStyles` [Section titled “injectStyles”](#injectstyles) **Type:** `boolean` · **Default:** `true` Appends the bundled theme to Starlight’s `customCss`. Set to `false` if you want to provide all quiz styling yourself; import `starlight-quiz/styles` manually, or write your own. ### `progressTracker` [Section titled “progressTracker”](#progresstracker) **Type:** `boolean` · **Default:** `true` Shows an aggregate progress widget (answered + correct) in the table of contents, by overriding Starlight’s `TableOfContents` and `MobileTableOfContents`. It hides itself on pages without quizzes. Set to `false` to disable. ### `progressPosition` [Section titled “progressPosition”](#progressposition) **Type:** `'top' | 'bottom'` · **Default:** `'top'` Whether the progress widget sits above (`'top'`) or below (`'bottom'`) the on-this-page links in the table of contents. ### `manifest` [Section titled “manifest”](#manifest) **Type:** `boolean | string` · **Default:** `true` Emit a structured JSON manifest of every quiz to the build output (`quiz-manifest.json`). Enabled by default; set to `false` to opt out, or pass a string to choose a different filename. The manifest is parsed from the built HTML, so it captures all quizzes without touching MDX, and is the source consumed by the QTI exporter and the terminal runner. ### `validate` [Section titled “validate”](#validate) **Type:** `boolean` · **Default:** `true` Fails the build if a quiz has a malformed answer. `- [x]`, `- [ ]` and `- [X]` are recognised as answers, and `- []` (no inner space) is accepted as an unchecked answer to match mkdocs-quiz. Any other marker (a typo like `[o]` or a smart bracket pasted from an editor) is rendered by markdown as plain text and would silently never be an answer. Rather than dropping it, the build stops with an error naming the page, the quiz and the offending text. Set to `false` to ignore such items instead. ### `quizDefaults` [Section titled “quizDefaults”](#quizdefaults) **Type:** `Partial<{ autoSubmit; disableAfterSubmit; showCorrect; shuffle; confetti; autoNumber }>` · **Default:** all `true` except `shuffle` and `autoNumber` Site-wide defaults for quiz behaviour, applied to every `` and `` that doesn’t set the matching prop itself, so you can change behaviour everywhere without editing each quiz. An explicit prop on a component always wins. astro.config.mjs
```js
starlightQuiz({
quizDefaults: {
confetti: false, // no confetti anywhere
autoSubmit: false, // always show a Submit button
},
});
```
## `` props [Section titled “\ props”](#quiz-props) Each behaviour prop falls back to the [`quizDefaults`](#quizdefaults) plugin option, then to the built-in default below. | Prop | Type | Default | Description | | -------------------- | --------- | ------- | ---------------------------------------------------------------------------------------------------------- | | `id` | `string` | — | Stable id for persistence (falls back to a hash of `title`). | | `title` | `string` | — | Heading shown above the quiz. | | `shuffle` | `boolean` | `false` | Shuffle answer order on load. | | `autoSubmit` | `boolean` | `true` | Single-choice: grade on click/tap. `false` shows a Submit button. | | `disableAfterSubmit` | `boolean` | `true` | Lock after submitting; `false` shows a reset button. | | `showCorrect` | `boolean` | `true` | Reveal the correct answer(s) after a wrong submission. | | `autoNumber` | `boolean` | `false` | Prefix with a numbered “Question N” heading. See [Auto-numbering](/starlight-quiz/guides/auto-numbering/). | ### `autoSubmit` [Section titled “autoSubmit”](#autosubmit) **Type:** `boolean` · **Default:** `true` Single-choice quizzes grade as soon as an answer is clicked or tapped, with no Submit button. Keyboard users arrowing through the options are **not** locked in. Only an explicit click, tap or Space activation submits. Set `false` to show a Submit button instead. Multiple-choice and fill-in-the-blank quizzes always use a Submit button. ### `disableAfterSubmit` [Section titled “disableAfterSubmit”](#disableaftersubmit) **Type:** `boolean` · **Default:** `true` After submitting, the quiz is locked and cannot be changed. Set `false` to show a **Try again** reset button so readers can retry. ### `showCorrect` [Section titled “showCorrect”](#showcorrect) **Type:** `boolean` · **Default:** `true` When a reader submits a wrong answer, the correct answer(s) are highlighted. Set `false` to tell them only whether they were right or wrong, without revealing the answer. ### Label overrides [Section titled “Label overrides”](#label-overrides) Every UI string can be overridden per quiz, useful outside Starlight or for custom wording: `submitLabel`, `resetLabel`, `correctLabel`, `incorrectLabel` and `tryAgainLabel`.
```mdx
…
```
## `` props [Section titled “\ props”](#quizresults-props) | Prop | Type | Default | Description | | --------------- | --------- | ------- | ------------------------------------------------------------------------------------------- | | `confetti` | `boolean` | `true` | Fire confetti on a first-time completion (score ≥ 10%). Pass `confetti={false}` to disable. | | `titleLabel` | `string` | — | Override the completed-panel title. | | `progressLabel` | `string` | — | Override the in-progress “Quiz Progress” heading. | | `answeredLabel` | `string` | — | Override the phrase after the `answered / total` count. | | `correctLabel` | `string` | — | Override the word after the correct count. | | `resetLabel` | `string` | — | Override the reset-all button label. | ## `` props [Section titled “\ props”](#quizintro-props) See [Intro panel](/starlight-quiz/guides/intro-panel/) for what this component does. The intro message is best set by passing content in the **default slot** (`…`), which accepts markdown. The `textLabel` prop is a plain-string fallback for vanilla Astro and i18n overrides. | Prop | Type | Default | Description | | ------------ | -------- | ------- | ------------------------------------------------------------ | | `textLabel` | `string` | — | Plain-string intro text (the default slot takes precedence). | | `resetLabel` | `string` | — | Override the reset-all button label. | ## `` props [Section titled “\ props”](#quizprogress-props) Under the plugin, the progress widget is injected into the table of contents for you. You can also import and place `` yourself, useful in [vanilla Astro](/starlight-quiz/guides/vanilla-astro/) or to control where it appears. It hides itself on pages with no quizzes. (The compact badge shown in the mobile ToC bar is an internal variant of this widget and isn’t a separate public component.) | Prop | Type | Default | Description | | --------------- | --------- | ------- | ------------------------------------------------------------------------ | | `showHeading` | `boolean` | `true` | Render the “Quiz Progress” heading (set `false` for compact placements). | | `headingLabel` | `string` | — | Override the heading. | | `answeredLabel` | `string` | — | Override the word after the answered count. | | `correctLabel` | `string` | — | Override the word after the correct count. | | `resetLabel` | `string` | — | Override the reset button label. | ## Custom styling [Section titled “Custom styling”](#custom-styling) Quizzes are themed with CSS custom properties (`--slq-accent`, `--slq-radius` and more) that default to your Starlight colour tokens, so they follow your site theme out of the box, and every element carries a stable class name you can target. See [Custom CSS](/starlight-quiz/guides/custom-css/) for the full variable reference and class hooks. ## Internationalisation [Section titled “Internationalisation”](#internationalisation) Starlight Quiz ships translations for 13 languages and resolves each label as: explicit prop → Starlight translation for the current locale → bundled English. See [Translations](/starlight-quiz/guides/translations/) for the full list and how to add a language.
# Custom CSS
> Theme quizzes with CSS variables and style individual parts via stable class names.
Quizzes ship with a small theme that follows your site colours out of the box. You can retune it with a handful of CSS custom properties, or target individual elements directly through stable class names. ## How the styles are layered [Section titled “How the styles are layered”](#how-the-styles-are-layered) All of the bundled CSS lives in a low-priority cascade layer:
```css
@layer starlight-quiz {
/* every bundled rule */
}
```
Unlayered CSS always beats layered CSS, regardless of specificity. So any rule **you** write (which is unlayered unless you opt into a layer) wins over the bundled styles without needing `!important`, even a low-specificity selector like `:root`. This is what makes the variable overrides below work. ## Theme variables [Section titled “Theme variables”](#theme-variables) The theme is driven by these `--slq-*` custom properties. Each one falls back to the matching Starlight colour token, and then to a built-in default for plain Astro projects (with a dark-mode variant via `prefers-color-scheme`): | Variable | Purpose | Falls back to | | -------------------- | ---------------------------------------- | ----------------------- | | `--slq-accent` | Radio bullet, progress-bar fill, focus | `--sl-color-accent` | | `--slq-text` | Body text | `--sl-color-text` | | `--slq-muted` | Secondary / muted text | `--sl-color-gray-3` | | `--slq-bg` | Quiz background | `--sl-color-bg` | | `--slq-surface` | Panel surface (results, progress, intro) | `--sl-color-gray-6` | | `--slq-border` | Borders | `--sl-color-gray-5` | | `--slq-correct` | Correct-answer text / marker | `--sl-color-green` | | `--slq-correct-bg` | Correct-answer background | `--sl-color-green-low` | | `--slq-correct-high` | Correct-answer emphasis | `--sl-color-green-high` | | `--slq-wrong` | Wrong-answer text / marker | `--sl-color-red` | | `--slq-wrong-bg` | Wrong-answer background | `--sl-color-red-low` | | `--slq-wrong-high` | Wrong-answer emphasis | `--sl-color-red-high` | | `--slq-radius` | Corner radius | `0.5rem` | Because they resolve to Starlight’s own tokens, quizzes inherit your site theme (including dark mode) with no configuration. The variables are declared on the quiz, results, progress and intro roots, so they cascade to everything inside each component. ## Overriding the variables [Section titled “Overriding the variables”](#overriding-the-variables) Set any of them in your own stylesheet. On `:root` they apply site-wide: custom.css
```css
:root {
--slq-accent: #490086;
--slq-radius: 0.25rem;
}
```
In a Starlight project, add the file to `customCss` in `astro.config.mjs`; in plain Astro, import it wherever you import `starlight-quiz/styles`. To restyle only some quizzes, scope the override to a wrapper or to the quiz root: custom.css
```css
/* Only quizzes inside an element with this class */
.exam .sl-quiz {
--slq-accent: #b91c1c;
--slq-radius: 0;
}
```
## Styling individual elements [Section titled “Styling individual elements”](#styling-individual-elements) For anything the variables don’t cover, target the elements directly. Every part of a quiz carries a stable `sl-quiz-*` class, and stateful parts gain a `--correct` / `--wrong` modifier after submitting. The most useful hooks: | Class | Element | | ------------------------------------- | ---------------------------------------------------------- | | `.sl-quiz` | The quiz container (the bordered card) | | `.sl-quiz-number` | The auto-numbered “Question N” label | | `.sl-quiz-title` | The quiz title heading | | `.sl-quiz-question` | The question text | | `.sl-quiz-fieldset` | The group wrapping the answers | | `.sl-quiz-answer` | A single answer (gains `--correct` / `--wrong`) | | `.sl-quiz-answer-feedback` | Per-answer feedback text | | `.sl-quiz-blank` | A fill-in-the-blank input (gains `--correct` / `--wrong`) | | `.sl-quiz-feedback` | The overall result message (gains `--correct` / `--wrong`) | | `.sl-quiz-corrections` | The correct answers shown after a wrong submission | | `.sl-quiz-content` | The explanation section revealed after submitting | | `.sl-quiz-actions` · `.sl-quiz-reset` | The button row · the reset button | | `.sl-quiz-results` | The results panel (tier modifier `--excellent` … `--fail`) | | `.sl-quiz-progress` | The progress widget (`--mobile` in the mobile ToC bar) | | `.sl-quiz-intro` | The intro panel | For example, to square off answer rows and thicken the correct-answer border: custom.css
```css
.sl-quiz-answer {
border-radius: 0;
}
.sl-quiz-answer--correct {
border-inline-start: 3px solid var(--slq-correct);
}
```
Don’t style `.sl-quiz-source` Each quiz keeps its original rendered markdown in a hidden `.sl-quiz-source` element that the component reads at runtime. It is an implementation detail, not a styling target. Class names other than those listed above may change between releases.
# Fill-in-the-blank
> Text-input questions where the reader types the answer.
Fill-in-the-blank questions let the reader type their answer. Wrap the expected answer in double square brackets: * Preview The capital of France is \[\[Paris]]. * Source quiz.mdx
```mdx
The capital of France is [[Paris]].
```
Answers are **case-insensitive** and surrounding whitespace is trimmed, so “Paris”, “ paris “ and “PARIS” all pass. ## Multiple blanks [Section titled “Multiple blanks”](#multiple-blanks) A question can have several blanks; every one must be correct: * Preview Water is made of hydrogen and \[\[oxygen]], with the chemical formula \[\[H2O]]. * Source quiz.mdx
```mdx
Water is made of hydrogen and [[oxygen]], with the chemical formula [[H2O]].
```
## Content section [Section titled “Content section”](#content-section) Because there is no answer list to mark the boundary, a fill-in-the-blank quiz uses a horizontal rule (`---`) to separate the question from a content section that is revealed after submitting: * Preview The chemical symbol for gold is \[\[Au]]. *** “Au” comes from the Latin *aurum*, meaning “shining dawn”. * Source quiz.mdx
```mdx
The chemical symbol for gold is [[Au]].
---
"Au" comes from the Latin _aurum_, meaning "shining dawn".
```
# Intro panel
> A short panel telling readers their answers are saved, with a reset button.
Drop a `` component near the top of a page to reassure readers that their answers are saved between visits, and to give them a one-click button to clear every quiz on the page. It is the equivalent of mkdocs-quiz’s `` placeholder.
```mdx
import { Quiz, QuizIntro } from 'starlight-quiz/components';
…
```
## Try it [Section titled “Try it”](#try-it) * Preview Quiz answers on this page are saved to your browser's local storage and persist between visits. Reset all answers Where are your quiz answers stored? * [x] The browser’s local storage * [ ] A remote database * [ ] Nowhere, they’re lost on reload * Source quiz.mdx
```mdx
Where are your quiz answers stored?
- [x] The browser's local storage
- [ ] A remote database
- [ ] Nowhere, they're lost on reload
```
Answer the quiz, then press **Reset all answers** to clear it. ## Reset behaviour [Section titled “Reset behaviour”](#reset-behaviour) The button clears the saved progress for **every** quiz on the current page (the same action as the [results panel](/starlight-quiz/guides/results-screen/) reset). On pages with the [progress sidebar](/starlight-quiz/guides/progress-tracking/), that widget already offers a reset link, so an intro panel is optional. ## Customising the text [Section titled “Customising the text”](#customising-the-text) Pass your own message as the component’s children to override the default. Markdown works here, so you can add emphasis or a link:
```mdx
Your answers are **saved locally**, clear them any time.
```
The message and the button label are also translatable (13 languages out of the box). Outside Starlight (where markdown children aren’t available in a plain `.astro` file), set them with props instead:
```astro
```
A slot, when given, wins over `textLabel`, which in turn wins over the translated default. See [Configuration](/starlight-quiz/guides/configuration/) for the full list of component props.
# Multiple choice
> Single-choice and multiple-choice questions, answer syntax and per-answer feedback.
Choice questions are written as a markdown task list. The number of correct answers decides the question type. You never set it explicitly. ## Single choice (radio buttons) [Section titled “Single choice (radio buttons)”](#single-choice-radio-buttons) Exactly one correct answer renders as radio buttons. Single-choice quizzes [auto-submit](/starlight-quiz/guides/configuration/#autosubmit) on selection by default: * Preview What is the capital of France? * [ ] London * [x] Paris * [ ] Berlin * Source quiz.mdx
```mdx
What is the capital of France?
- [ ] London
- [x] Paris
- [ ] Berlin
```
## Multiple choice (checkboxes) [Section titled “Multiple choice (checkboxes)”](#multiple-choice-checkboxes) Recognised markers `[x]`, `[ ]` and `[X]` are recognised, and `[]` (no inner space) counts as an unchecked answer. A mistyped marker (`[o]` or a “smart” bracket pasted from an editor) isn’t a checkbox in markdown, so it would silently never be an answer. The build fails with an error pointing at it (disable with the [`validate`](/starlight-quiz/guides/configuration/#validate) option). Also don’t mix `-` and `*` bullets within one quiz, as that splits the answer list. More than one correct answer renders as checkboxes with a Submit button. The reader must select **all** correct answers and **only** the correct answers: * Preview Which of these are even numbers? * [x] 2 * [ ] 3 * [x] 4 * [ ] 5 * Source quiz.mdx
```mdx
Which of these are even numbers?
- [x] 2
- [ ] 3
- [x] 4
- [ ] 5
```
## Per-answer feedback [Section titled “Per-answer feedback”](#per-answer-feedback) Add a blockquote (`>`) indented underneath an answer to show feedback specific to that choice after submitting. Each feedback box is badged with the answer it belongs to, so it is always clear which choice the note responds to. ### Single choice [Section titled “Single choice”](#single-choice) Pick an answer to reveal its feedback: * Preview Which language runs natively in the browser? * [x] JavaScript > Correct. JavaScript is the language of the web. * [ ] Python > Python needs a server or a runtime like Pyodide to run in the browser. * [ ] C++ * Source quiz.mdx
```mdx
Which language runs natively in the browser?
- [x] JavaScript
> Correct. JavaScript is the language of the web.
- [ ] Python
> Python needs a server or a runtime like Pyodide to run in the browser.
- [ ] C++
```
### Multiple choice [Section titled “Multiple choice”](#multiple-choice) When more than one selected answer carries feedback, every box is shown together — the badges keep them apart. Select a few options below before submitting: * Preview Which of these are programming languages? * [x] Python > Yes — a general-purpose programming language. * [x] Rust > Yes — a systems programming language. * [ ] HTML > No — HTML is a markup language for structuring content. * [ ] HTTP > No — HTTP is a protocol for transferring data over the web. * Source quiz.mdx
```mdx
Which of these are programming languages?
- [x] Python
> Yes — a general-purpose programming language.
- [x] Rust
> Yes — a systems programming language.
- [ ] HTML
> No — HTML is a markup language for structuring content.
- [ ] HTTP
> No — HTTP is a protocol for transferring data over the web.
```
## Content section [Section titled “Content section”](#content-section) Any markdown after the answers becomes a content section that is revealed once the quiz is submitted, handy for explanations. See [Advanced formatting](/starlight-quiz/guides/advanced-formatting/) for richer content. * Preview What is Astro? * [x] A web framework * [ ] A database * [ ] A text editor **Astro** is a web framework for building content-driven websites, with an islands architecture for shipping less JavaScript. * Source quiz.mdx
```mdx
What is Astro?
- [x] A web framework
- [ ] A database
- [ ] A text editor
**Astro** is a web framework for building content-driven websites, with an
islands architecture for shipping less JavaScript.
```
# Progress tracking
> How quiz progress is saved and restored.
Every quiz records whether it has been answered and whether the answer was correct. That progress is saved to the browser’s `localStorage`, so it survives reloads and revisits. ## How it is stored [Section titled “How it is stored”](#how-it-is-stored) * Progress is keyed by the **page path** plus each quiz’s **id**. * A quiz’s id comes from its `id` prop, falling back to a stable hash of its `title`. Because the key is stable rather than positional, progress survives reordering quizzes and rebuilding the site. * The whole page’s progress is stored under a single key, validated on load, and discarded if it is ever corrupted or unexpectedly large. A bad write can never break a page. ## Try it [Section titled “Try it”](#try-it) Answer this quiz, then reload the page, and your answer is restored automatically: * Preview Will this answer still be here after a reload? * [x] Yes * [ ] No It is saved to local storage and restored when the page loads. * Source quiz.mdx
```mdx
Will this answer still be here after a reload?
- [x] Yes
- [ ] No
It is saved to local storage and restored when the page loads.
```
## Aggregate progress [Section titled “Aggregate progress”](#aggregate-progress) When used as a Starlight plugin, a compact progress widget appears in the table of contents (right sidebar on desktop, and the mobile ToC), showing how many quizzes on the page are answered and how many are correct. On narrow screens (where there is no right sidebar) a full-width version with a reset link is rendered at the foot of the page instead. The widget hides itself on pages without quizzes, and you can turn it off with the [`progressTracker`](/starlight-quiz/guides/configuration/#plugin-options) plugin option. To place it somewhere specific (or to use it [without Starlight](/starlight-quiz/guides/vanilla-astro/)), import and drop in the [``](/starlight-quiz/guides/configuration/#quizprogress-props) component yourself. A [``](/starlight-quiz/guides/results-screen/) panel shows the same progress in-page and lets the reader reset every quiz at once. Both read from a central tracker that also broadcasts window events, so you can build your own progress UI on top of it. ## Build your own progress UI [Section titled “Build your own progress UI”](#build-your-own-progress-ui) The tracker dispatches two events on `window` whenever progress changes: | Event | Detail | | -------------------------- | ----------------------------------------------------------------------- | | `starlight-quiz:progress` | `{ total, answered, correct, percentage, score }` (counts plus 0–100 %) | | `starlight-quiz:reset-all` | *(none)*, fired when every quiz on the page is reset | `percentage` is `answered / total`; `score` is `correct / total`. Both events are typed on `WindowEventMap`, so TypeScript knows `event.detail` in editors. Listen for them to drive a custom widget:
```js
window.addEventListener('starlight-quiz:progress', (event) => {
const { answered, total, correct, percentage, score } = event.detail;
console.log(`Answered ${answered}/${total} (${percentage}%), score ${score}%`);
document.querySelector('#my-progress')?.style.setProperty('--done', String(percentage));
});
window.addEventListener('starlight-quiz:reset-all', () => {
console.log('All quizzes on this page were reset');
});
```
The `progress` event fires whenever progress changes, including as each quiz registers on load (restoring saved answers), so a listener gets the current totals without special-casing the initial render.
# QTI export
> Export your quizzes to QTI for import into Canvas, Moodle, Blackboard and other LMS platforms.
Already writing quizzes in your docs? You can reuse them in a Learning Management System (LMS) instead of rebuilding them by hand. The `export-qti` command turns every quiz on your site into [QTI](https://www.imsglobal.org/question/index.html) (Question & Test Interoperability), the standard interchange format that Canvas, Moodle, Blackboard, D2L Brightspace and most other platforms can import. ## The quiz manifest [Section titled “The quiz manifest”](#the-quiz-manifest) Like the [terminal runner](/starlight-quiz/guides/cli/), the exporter reads a **quiz manifest**, never your MDX. The manifest is a single `quiz-manifest.json` file describing every quiz on your site (question, answers, which are correct, page path), and the plugin writes it by default. See the [CLI guide](/starlight-quiz/guides/cli/#the-quiz-manifest) for what the manifest is and where it lives, and [Configuration](/starlight-quiz/guides/configuration/#manifest) for the option. ## Export [Section titled “Export”](#export) Point `export-qti` at your site’s manifest — its URL or a local build directory — and choose an output directory:
```sh
# QTI 2.1 (the default)
npx starlight-quiz export-qti https://ewels.github.io/starlight-quiz/quiz-manifest.json --out ./qti
```
output
```text
Wrote 28 file(s) to ./qti/
```
That writes one XML assessment item per quiz, plus an `imsmanifest.xml` index, into the output directory: ./qti/
```text
imsmanifest.xml ← the package index
demo-single.xml ← one item per quiz, named by its id
demo-multiple.xml
blank-single.xml
…
```
Single-choice, multiple-choice and fill-in-the-blank questions are all supported. ## Import into your LMS [Section titled “Import into your LMS”](#import-into-your-lms) The output directory is an unzipped **IMS Content Package**. To import it: 1. **Zip the output directory** so `imsmanifest.xml` sits at the root of the archive. 2. In your LMS, import it as a QTI / IMS content package (for example Canvas: *Settings → Import Course Content → QTI .zip file*). ## Options [Section titled “Options”](#options) | Flag | Description | | ------------ | ---------------------------------------------------------------------------- | | `` | A manifest URL or file, a directory containing one, or a page URL (scraped). | | `--out` | Output directory (default `qti`). | | `--version` | `2.1` (default) or `1.2`. | | `--filename` | Manifest filename to look for (default `quiz-manifest.json`). |
# Quick start
> Install the Starlight Quiz plugin and write your first quiz.
## Install [Section titled “Install”](#install)
```sh
npm install starlight-quiz
```
## Add the plugin [Section titled “Add the plugin”](#add-the-plugin) Register the plugin in your Starlight configuration. It wires up the styles and translations for you. astro.config.mjs
```js
import starlight from '@astrojs/starlight';
import starlightQuiz from 'starlight-quiz';
import { defineConfig } from 'astro/config';
export default defineConfig({
integrations: [
starlight({
title: 'My docs',
plugins: [starlightQuiz()],
}),
],
});
```
## Write a quiz [Section titled “Write a quiz”](#write-a-quiz) Quizzes are written with markdown inside an imported `` component. The basic structure is a question, a [task list](https://github.github.com/gfm/#task-list-items-extension-) of answers, and an optional content section. A ticked box (`[x]`) is a correct answer; an empty box (`[ ]`) is incorrect: quiz.mdx
```mdx
import { Quiz } from 'starlight-quiz/components';
Question text goes here.
- [x] Correct answer
- [ ] Incorrect answer
- [ ] Another incorrect answer
Optional content, revealed after the answer is submitted.
```
This renders as: Question text goes here. * [x] Correct answer * [ ] Incorrect answer * [ ] Another incorrect answer Optional content, revealed after the answer is submitted. ## Multiple correct answers [Section titled “Multiple correct answers”](#multiple-correct-answers) With **one** correct answer you get radio buttons. With **more than one**, the quiz switches to checkboxes automatically: quiz.mdx
```mdx
Which of these are programming languages?
- [x] Python
- [ ] HTML
- [x] JavaScript
- [ ] CSS
Python and JavaScript are programming languages, while HTML and CSS are
markup/styling languages.
```
This renders as: Which of these are programming languages? * [x] Python * [ ] HTML * [x] JavaScript * [ ] CSS Python and JavaScript are programming languages, while HTML and CSS are markup/styling languages. All correct answers (and only the correct answers) must be selected to get the question right. ## Fill-in-the-blank [Section titled “Fill-in-the-blank”](#fill-in-the-blank) For questions where readers type the answer, wrap the expected answer in double square brackets: quiz.mdx
```mdx
The capital of France is [[Paris]].
```
This renders as: The capital of France is \[\[Paris]]. Answers are case-insensitive, so “Paris”, “paris” and “PARIS” are all accepted. ## Next steps [Section titled “Next steps”](#next-steps) * **[Multiple choice](/starlight-quiz/guides/multiple-choice/)**: radio buttons, checkboxes and per-answer feedback. * **[Fill-in-the-blank](/starlight-quiz/guides/fill-in-the-blank/)**: multiple blanks and content sections. * **[Advanced formatting](/starlight-quiz/guides/advanced-formatting/)**: code, tables and images in quizzes. * **[Progress tracking](/starlight-quiz/guides/progress-tracking/)**: how quiz progress is saved. * **[Configuration](/starlight-quiz/guides/configuration/)**: all available options.
# Results screen
> Add an aggregate score panel with score tiers and confetti.
Drop a `` component onto a page to show aggregate progress across every quiz on it. As readers answer, the panel tracks how many quizzes are done; once they have all been answered it reveals a score with an encouraging message.
```mdx
import { Quiz, QuizResults } from 'starlight-quiz/components';
……
```
## Try it [Section titled “Try it”](#try-it) Answer both quizzes to reveal the score panel below. * Preview What does HTML stand for? * [x] HyperText Markup Language * [ ] Hyperlink Text Mode Language * [ ] Home Tool Markup Language Which language styles a web page? * [x] CSS * [ ] SQL * [ ] JSON Quiz Progress 0 / 0 questions answered (0%) 0 correct Your score 0% 0 / 0 correct Reset all answers * Source quiz.mdx
```mdx
What does HTML stand for?
- [x] HyperText Markup Language
- [ ] Hyperlink Text Mode Language
- [ ] Home Tool Markup Language
Which language styles a web page?
- [x] CSS
- [ ] SQL
- [ ] JSON
```
## Score tiers [Section titled “Score tiers”](#score-tiers) The message shown depends on the percentage of quizzes answered correctly: | Score | Message | | -------- | ------------------------------------------ | | 90–100 % | Outstanding! You aced it! | | 75–89 % | Great job! You really know your stuff! | | 60–74 % | Good effort! Keep learning! | | 40–59 % | Not bad, but there’s room for improvement! | | 0–39 % | Better luck next time! Keep trying! | ## Confetti [Section titled “Confetti”](#confetti) Confetti fires on a first-time completion (when the score is at least 10 %) **by default**. Pass `confetti={false}` to turn it off:
```mdx
```
The confetti library is loaded with a dynamic import, so it adds nothing to your bundle unless it actually fires. Confetti is also skipped for readers who set **`prefers-reduced-motion`**. ## Resetting [Section titled “Resetting”](#resetting) The results panel includes a **Reset all answers** button that clears every quiz on the page. Each quiz also keeps its own reset button when [`disableAfterSubmit`](/starlight-quiz/guides/configuration/#disableaftersubmit) is `false`.
# Shuffle answers
> Randomise the order of answers on every page load.
Set the `shuffle` prop to randomise the order of a choice quiz’s answers. The shuffle happens in the browser on every load, so readers see a different order each visit, which helps stop people memorising answer positions rather than the content. * Preview Which is the largest planet in the Solar System? * [x] Jupiter * [ ] Earth * [ ] Mars * [ ] Mercury * Source quiz.mdx
```mdx
Which is the largest planet in the Solar System?
- [x] Jupiter
- [ ] Earth
- [ ] Mars
- [ ] Mercury
```
Reload the page a few times to see the order change. Correct-answer tracking and saved progress are both preserved regardless of display order.
# Translations
> How quiz labels are localised under Starlight, the built-in locales, and overriding text.
Every user-facing string (buttons, feedback, the results panel, the score messages) is translatable. Under Starlight the right locale is chosen automatically; outside Starlight you pass labels as props. ## A quick primer on Starlight i18n [Section titled “A quick primer on Starlight i18n”](#a-quick-primer-on-starlight-i18n) If you have not set up a multilingual Starlight site before, here is the short version. You declare your languages in the Starlight config: astro.config.mjs
```js
starlight({
defaultLocale: 'en',
locales: {
en: { label: 'English' },
fr: { label: 'Français', lang: 'fr' },
},
plugins: [starlightQuiz()],
});
```
Your content then lives in a folder per language (`src/content/docs/en/…`, `src/content/docs/fr/…`), and Starlight serves each under its own route (`/fr/…`). For UI strings (navigation, the search box, and plugin strings like the quiz buttons), Starlight keeps a translation table per locale and exposes the current one to components. Starlight Quiz hooks into exactly that mechanism, so **a quiz on a French page renders its buttons and messages in French with no extra work**. For the full picture, see Starlight’s [i18n guide](https://starlight.astro.build/guides/i18n/). ## Built-in locales [Section titled “Built-in locales”](#built-in-locales) Starlight Quiz ships translations for **13 languages**, shared verbatim with its sibling [mkdocs-quiz](https://github.com/ewels/mkdocs-quiz) via the same gettext `.po` files: | Code | Language | Code | Language | | ---- | ---------------- | ------- | ---------------------- | | `en` | English (source) | `ja` | Japanese | | `de` | German | `ko` | Korean | | `eo` | Esperanto | `no` | Norwegian | | `es` | Spanish | `pt-br` | Portuguese (Brazilian) | | `fr` | French | `sv` | Swedish | | `hi` | Hindi | `zh` | Chinese (Simplified) | | `id` | Indonesian | | | When the plugin is installed, these are injected into Starlight’s i18n. A quiz on a page served under a configured locale (e.g. `/fr/…`) picks up that locale’s strings automatically. Strings a locale hasn’t translated fall back to English. ## How a label is resolved [Section titled “How a label is resolved”](#how-a-label-is-resolved) Each label is resolved in this order (the first that exists wins): 1. An **explicit prop** (or, for ``, slot content) on the component. 2. The **Starlight translation** for the current locale. 3. The bundled **English** default. This is what lets the same component work translated inside Starlight and with explicit labels in a plain Astro project. ## See it translated [Section titled “See it translated”](#see-it-translated) Here is a quiz and a results panel with their labels in French. On a real French page you would not pass any of these props; Starlight would supply them from the locale. They are set here only because this page is served in English: Quelle est la capitale de la France ? * [ ] Londres * [x] Paris * [ ] Berlin Paris est la capitale de la France depuis le Moyen Âge. Progression 0 / 0 répondues (0%) 0 correctes Quiz terminé ! 0% 0 / 0 correctes Réinitialiser toutes les réponses ## Overriding text per quiz [Section titled “Overriding text per quiz”](#overriding-text-per-quiz) Any string can be overridden per instance, handy for custom wording or when you’re outside Starlight. See the [``](/starlight-quiz/guides/configuration/#quiz-props), [``](/starlight-quiz/guides/configuration/#quizresults-props) and [``](/starlight-quiz/guides/configuration/#quizintro-props) prop tables for every label. quiz.mdx
```mdx
Is the sky blue?
- [x] Yes
- [ ] No
```
Is the sky blue? * [x] Yes * [ ] No ## Override the bundled wording [Section titled “Override the bundled wording”](#override-the-bundled-wording) To change a label everywhere on your site (rather than per quiz), override the key in **your own** Starlight UI translations. The plugin injects its strings as defaults, and Starlight lets your site’s translations take precedence, so any `starlightQuiz.*` key you define wins. Add the keys to your i18n data, per locale: src/content/i18n/en.json
```json
{
"starlightQuiz.submit": "Check answer",
"starlightQuiz.results.title": "Your result"
}
```
Every key lives under the `starlightQuiz.` namespace (e.g. `starlightQuiz.submit`, `starlightQuiz.correct`, `starlightQuiz.results.excellent`, `starlightQuiz.questionNumber`). This is the equivalent of supplying your own translation file, and it works for the bundled locales as well as any you add. See Starlight’s [i18n guide](https://starlight.astro.build/guides/i18n/#translate-starlights-ui) for where these files live. ## Outside Starlight [Section titled “Outside Starlight”](#outside-starlight) In a plain Astro project there is no translation function, so only steps 1 and 3 above apply: pass label props for any text you want changed, otherwise you get English. See [Astro installation](/starlight-quiz/guides/vanilla-astro/). ## Adding or updating a language [Section titled “Adding or updating a language”](#adding-or-updating-a-language) The translations are shared with mkdocs-quiz through gettext `.po` files. To add a language or improve an existing one, see [Contributing translations](/starlight-quiz/guides/contributing-translations/).
# Astro installation
> Use the quiz components in any Astro project, without Starlight, and wire up the pieces the plugin would normally inject for you.
The quiz components have no hard dependency on Starlight: the Starlight-specific code lives only in the plugin (`starlight-quiz`) and is never imported by `starlight-quiz/components`. So you can use `` and friends in any Astro project. The trade-off is that the [Starlight plugin](/starlight-quiz/guides/quick-start/) does a lot of wiring for you. Without it, you do that wiring yourself. Here is the whole list, and the rest of this page walks through each one. | The Starlight plugin… | Without it, you… | | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | Injects the theme CSS | [import the stylesheet](#1-import-the-styles) once yourself | | Injects 13 locales and picks one per page | [pass label props](#4-translate-the-labels) (or get English) | | Adds the progress widget to the table of contents and page footer | [place ``](#3-show-aggregate-progress-optional) where you want it | | Applies site-wide `quizDefaults` via middleware | [set props per component](#5-site-wide-defaults-optional) (or add your own middleware) | | Emits the [quiz manifest](/starlight-quiz/guides/cli/#the-quiz-manifest) and [validates](/starlight-quiz/guides/configuration/#validate) quizzes at build time | don’t get these — they are [plugin-only](#what-you-dont-get) | ## 1. Import the styles [Section titled “1. Import the styles”](#1-import-the-styles) Install the package, then import the stylesheet once (for example in a shared layout) so every quiz is styled:
```sh
npm install starlight-quiz
```
src/layouts/Layout.astro
```astro
---
import 'starlight-quiz/styles';
---
```
The theme follows [CSS variables](/starlight-quiz/guides/custom-css/) that fall back to sensible light/dark defaults when Starlight’s colour tokens aren’t present, so quizzes look right on a plain Astro site too. ## 2. Author quizzes in MDX [Section titled “2. Author quizzes in MDX”](#2-author-quizzes-in-mdx) Quizzes are written as markdown (a task list, `[[blanks]]`, blockquote feedback). That markdown only renders in `.md` / `.mdx` files, **not** in a plain `.astro` file, so author your quizzes on an MDX page: src/pages/quiz.mdx
```mdx
---
layout: ../layouts/Layout.astro
---
import { Quiz } from 'starlight-quiz/components';
Is the sky blue?
- [x] Yes
- [ ] No
```
Not in `.astro` files Inside a plain `.astro` component the text between `` tags is treated as HTML, so `- [x] Yes` renders literally instead of becoming a checkbox. Use an `.mdx` page (or the MDX integration’s components) for the task-list shorthand. ## 3. Show aggregate progress (optional) [Section titled “3. Show aggregate progress (optional)”](#3-show-aggregate-progress-optional) Under Starlight, the progress widget is injected into the table of contents and the page footer automatically. There is no equivalent to hook into here, so drop [``](/starlight-quiz/guides/configuration/#quizprogress-props) wherever you want it. It hides itself on pages with no quizzes: src/pages/quiz.mdx
```mdx
import { Quiz, QuizProgress, QuizResults } from 'starlight-quiz/components';
……
```
[``](/starlight-quiz/guides/results-screen/) (the score panel) and [``](/starlight-quiz/guides/intro-panel/) work the same way, with no Starlight needed. ## 4. Translate the labels [Section titled “4. Translate the labels”](#4-translate-the-labels) With Starlight, every label is translated automatically through Starlight’s i18n. Without it there is no translation function, so each label falls back to its bundled English default unless you pass a prop:
```astro
…
```
The precedence is: an explicit prop wins, then Starlight’s translation (absent here), then the bundled English default. Every overridable label is listed in [Configuration](/starlight-quiz/guides/configuration/), and [Translations](/starlight-quiz/guides/translations/) explains the full resolution order. ## 5. Site-wide defaults (optional) [Section titled “5. Site-wide defaults (optional)”](#5-site-wide-defaults-optional) The plugin’s [`quizDefaults`](/starlight-quiz/guides/configuration/#quizdefaults) option (turn confetti off everywhere, switch to manual submit, and so on) is delivered to the components through `Astro.locals`. In a plain Astro project the simplest approach is to set the relevant prop on each component. If you want true site-wide defaults, add a small middleware that publishes them, and every quiz will pick them up: src/middleware.ts
```ts
import { defineMiddleware } from 'astro:middleware';
export const onRequest = defineMiddleware((context, next) => {
context.locals.starlightQuiz = { defaults: { confetti: false, autoSubmit: false } };
return next();
});
```
An explicit prop on a component still overrides these. ## What you don’t get [Section titled “What you don’t get”](#what-you-dont-get) The [quiz manifest](/starlight-quiz/guides/cli/#the-quiz-manifest) and build-time [validation](/starlight-quiz/guides/configuration/#validate) are wired up by the Starlight plugin’s build integration, so they are not available in a plain Astro project. That means the [terminal runner](/starlight-quiz/guides/cli/) and [QTI export](/starlight-quiz/guides/qti-export/), which read the manifest, need the plugin (or a manifest produced some other way). Everything else, including view transitions, works without Starlight: the components are self-initialising custom elements that re-initialise on navigation.
# Changelog
> Release notes for every published version of Starlight Quiz.
All notable changes to `starlight-quiz` are recorded here. New work is added under **Unreleased** and rolled into a dated version section when a release is cut. ## Unreleased [Section titled “Unreleased”](#unreleased) ## **Version 1.0.1** (2026-08-14) [Section titled “Version 1.0.1 (2026-08-14)”](#version-101-2026-08-14) Metadata-only release — no code, behaviour or API changes. * Expanded the package keywords so the package is shelved under a category on [astro.build/integrations](https://astro.build/integrations) instead of showing as “Uncategorized”. ## **Version 1.0.0** (2026-06-30) [Section titled “Version 1.0.0 (2026-06-30)”](#version-100-2026-06-30) First public release. ### Features [Section titled “Features”](#features) * Markdown-authored quizzes through a `` component: single-choice (radio), multiple-choice (checkbox) and fill-in-the-blank questions, written as GitHub task lists with `[[answer]]` blanks. * Per-answer feedback blockquotes — each badged with the answer it responds to and tinted by whether that answer was correct, so multiple feedbacks stay distinguishable — plus a post-submit content section that accepts full markdown, including code blocks (rendered by Expressive Code), tables and images. * `` aggregate score panel: a live text summary while quizzes are in progress (“*N / M* questions answered”, “*N* correct”), and on completion a prominent tier-coloured score tile with score tiers and confetti (skipped for readers who set `prefers-reduced-motion`). Submitting the last quiz on a page scrolls the panel into view. * `` panel with a markdown default slot and a one-click reset for every quiz on the page. * Progress tracking persisted to `localStorage`, a table-of-contents progress widget (`progressPosition: 'top' | 'bottom'`) with a split correct/incorrect bar, and `window` events for building your own progress UI. * Site-wide behaviour defaults via the `quizDefaults` plugin option, with per-quiz props always taking precedence. * Optional auto-numbering (“Question N” headings) and per-load answer shuffling. * Build-time validation of quiz markers, and a JSON quiz manifest emitted by default and served by the dev server too (opt out with `manifest: false`). * A CLI to take quizzes in the terminal — point `run` at the full URL of a page (scraped directly) or at a whole-site manifest, with `--shuffle` / `--shuffle-answers` and a `history` command — plus QTI 1.2 / 2.1 export for LMS import. * Translations for 13 languages, shared verbatim with the sibling [mkdocs-quiz](https://github.com/ewels/mkdocs-quiz) plugin. * Usable as a Starlight plugin or as standalone components in any Astro project.
# Contributing
> Develop, test, translate and release Starlight Quiz.
Contributions are welcome. The repository is a pnpm workspace with the published package in `packages/starlight-quiz` and this documentation site (which doubles as the test fixture) in `docs`. ## Set up [Section titled “Set up”](#set-up) Node ≥ 22.12 and pnpm are required.
```sh
git clone https://github.com/ewels/starlight-quiz
cd starlight-quiz
pnpm install
pnpm dev # run the docs site locally
```
## Checks [Section titled “Checks”](#checks) | Task | Command | | ----------------------------- | --------------------------- | | Unit tests (Vitest) | `pnpm test` | | End-to-end tests (Playwright) | `pnpm test:e2e` | | Type-check | `pnpm typecheck` | | Lint · format | `pnpm lint` · `pnpm format` | | Everything (as CI runs it) | `prek run --all-files` | `prek install` wires the git pre-commit hook (prettier → eslint → typecheck); CI runs the same `prek run --all-files`. ## Translations [Section titled “Translations”](#translations) Translations are gettext `.po` files in `packages/starlight-quiz/locales/`, shared verbatim with [mkdocs-quiz](https://github.com/ewels/mkdocs-quiz) so both plugins stay in sync. See [Contributing translations](/starlight-quiz/guides/contributing-translations/) for how to add or improve a language. ## Releases [Section titled “Releases”](#releases) Notable changes are added under the **Unreleased** heading at the top of [`CHANGELOG.md`](https://github.com/ewels/starlight-quiz/blob/main/CHANGELOG.md), in the same PR as the change. To cut a release: 1. Move the **Unreleased** entries into a new `## **Version X.Y.Z** (YYYY-MM-DD)` section, and bump `version` in `packages/starlight-quiz/package.json` to match. 2. Commit and push to `main`. 3. Publish a [GitHub release](https://github.com/ewels/starlight-quiz/releases/new) with a tag matching the version (e.g. `v1.0.0`). Publishing the release triggers the [Release workflow](https://github.com/ewels/starlight-quiz/blob/main/.github/workflows/release.yml), which publishes to npm via trusted publishing (no token to manage). It first checks that the release tag matches the package version, so a mismatch fails the run rather than shipping the wrong version.
# Contributing translations
> Add a new language or improve an existing translation for Starlight Quiz.
Community translations are very welcome. This page explains how to add a new language or improve an existing one. ## How translations are stored [Section titled “How translations are stored”](#how-translations-are-stored) Starlight Quiz shares its translations **verbatim with [mkdocs-quiz](https://github.com/ewels/mkdocs-quiz)** so the two sibling plugins always read the same way. The source of truth is a set of standard gettext `.po` files:
```plaintext
packages/starlight-quiz/locales/
de.po eo.po es.po fr.po hi.po id.po
ja.po ko.po no.po pt-BR.po sv.po zh.po
```
There is no `en.po`: English is the source text, used as the fallback when a string is untranslated. A build step, `gen:i18n`, compiles these `.po` files into `translations.ts` (the table the plugin injects into Starlight). Two kinds of string feed into it: * **Shared strings** (buttons, feedback, score messages) are pulled from the `.po` files by matching the mkdocs-quiz source text. * A few **Starlight-only strings** with no mkdocs counterpart (the intro-panel text and a couple of results-panel labels) live in a hand-maintained `CURATED` map in `packages/starlight-quiz/scripts/build-i18n.ts`. ## Add or improve a language [Section titled “Add or improve a language”](#add-or-improve-a-language) 1. **Edit the `.po` file** for the locale under `packages/starlight-quiz/locales/` (create `.po` from an existing one if the language is new). Fill in each `msgstr`:
```po
msgid "Submit"
msgstr "Vérifier"
msgid "Question {n}"
msgstr "Question {n}"
```
2. *(Optional)* For full coverage, add the Starlight-only strings for your locale to the `CURATED` map in `scripts/build-i18n.ts`. Locales missing from `CURATED` simply fall back to English for those few keys. 3. **Regenerate** the runtime table:
```sh
pnpm --filter starlight-quiz gen:i18n
```
It rewrites `translations.ts` and prints a coverage summary, a quick way to see what is still missing:
```text
Coverage (translated / 21 strings):
de 20/21 95%
es 20/21 95%
fr 19/21 90%
…
```
4. **Commit** the regenerated `translations.ts` alongside your `.po` change (and `build-i18n.ts` if you edited `CURATED`). 5. **Open a pull request.** Because the `.po` files are shared with mkdocs-quiz, please mention if the same change should land there too, so the two stay in sync. ## Test it locally [Section titled “Test it locally”](#test-it-locally) Configure a locale in the docs site (or your own Starlight site) and view a quiz under it: astro.config.mjs
```js
starlight({
locales: { en: { label: 'English' }, fr: { label: 'Français', lang: 'fr' } },
plugins: [starlightQuiz()],
});
```
A quiz on a page served under `/fr/…` should now show your translated buttons and messages. See [Translations](/starlight-quiz/guides/translations/) for the reader-facing side and how labels resolve. ## Guidelines [Section titled “Guidelines”](#guidelines) * **Preserve placeholders.** Keep `{n}` intact: `"Question {n}"` → `"Question {n}"`, never `"Question numéro"`. * **Keep it concise.** Buttons and labels need to fit; the intro text has more room. * **Match tone.** Feedback and score messages should stay friendly and encouraging. * **Use UTF-8** for the `.po` file. * **Language codes** follow Starlight’s: a 2-letter code, with a region suffix where needed. Note mkdocs-quiz’s `pt-BR.po` is lower-cased to `pt-br` in the generated Starlight table.