# CodeGroup `{% codeGroup %}` wraps a run of fenced code blocks and shows them as **language / file tabs**, each with its own copy button. Reach for it when the same thing is shown several ways — a request in `curl`, Python, and JavaScript; a config in YAML and TOML; a file across a few languages — so the reader picks the tab they care about instead of scrolling past the rest. Every fenced block inside becomes one tab. The tab label comes from the block's language (`json` → **JSON**), or from an explicit `title="…"` on the fence when you want a filename or a custom name. The code is highlighted at build time with the same highlighter the site's `` ``` `` fenced blocks use, so a tab looks exactly like an ordinary code block — just grouped. Use it as `{% codeGroup %}` in Markdown, or call it from Python logic (loops, snippets) via `component('aardvark', 'codeGroup', …)`. Close it with `{% endCodeGroup %}`. A code group is the same tab widget as [**Tabs**](/components/navigation/tabs/) — same [variants](#variants), same sliding underline, same crossfade — specialised for code. Reach for **CodeGroup** when the tabs are the *same thing in different languages or files*: you get automatic language labels, a copy / download button per tab, build-time highlighting, and a language choice that [**syncs across every group on the page and persists**](#syncing-the-language-across-groups). Reach for [**Tabs**](/components/navigation/tabs/) when the panels are *arbitrary Markdown* — prose, lists, nested components, mixed content — that you label yourself and that should stay independent. ## By language Give each way its own fenced block and the languages become the tabs: ```bash curl https://api.example.com/v1/users \ -H "Authorization: Bearer $TOKEN" ``` ```python import requests requests.get( "https://api.example.com/v1/users", headers={"Authorization": f"Bearer {token}"}, ) ``` ```javascript await fetch("https://api.example.com/v1/users", { headers: { Authorization: `Bearer ${token}` }, }); ```
Source: Markdown ~~~aardvark {% codeGroup %} ```bash curl https://api.example.com/v1/users \ -H "Authorization: Bearer $TOKEN" ``` ```python import requests requests.get( "https://api.example.com/v1/users", headers={"Authorization": f"Bearer {token}"}, ) ``` ```javascript await fetch("https://api.example.com/v1/users", { headers: { Authorization: `Bearer ${token}` }, }); ``` {% endCodeGroup %} ~~~ ## Named tabs with `title` Add `title="…"` to a fence to label the tab yourself — a filename is the common case. The language still drives the highlighting; only the label changes: ```json title="package.json" { "name": "my-app", "version": "1.0.0" } ``` ```toml title="pyproject.toml" [project] name = "my-app" version = "1.0.0" ```
Source: Markdown ~~~aardvark {% codeGroup %} ```json title="package.json" { "name": "my-app", "version": "1.0.0" } ``` ```toml title="pyproject.toml" [project] name = "my-app" version = "1.0.0" ``` {% endCodeGroup %} ~~~ ## Choosing the opening language Every code group on a page opens on the **same language** (see [Syncing the language across groups](#syncing-the-language-across-groups) below) — by default the first code group's first tab. To make a page open on a specific language, set `defaultValue` (a tab's label) on the **first** code group; it seeds the language every group then opens on: ~~~aardvark {% codeGroup defaultValue="Python" %} ```bash echo "shell is first in source…" ``` ```python print("…but the page opens on Python") ``` {% endCodeGroup %} ~~~ `defaultValue` only sets the first, pre-choice paint: once a reader picks a language it's remembered (below) and that wins. On a *later* group it has no visible effect — the page's shared language has already applied — so reach for it on the first group only. ## Syncing the language across groups Code groups on a page share **one language choice**, from the very first paint: they all open on the same language, and picking **Python** in any group switches every other group that has a Python tab to it at once. The choice sticks, too — the next page you open starts on Python. It's remembered per-reader in the browser, so a `curl`-first reader and a Python-first reader each read the docs in the language they picked, across the whole site. Try it — click a language in either group and watch the other follow: ```bash curl https://api.example.com/v1/pets ``` ```python requests.get("https://api.example.com/v1/pets") ``` ```javascript await fetch("https://api.example.com/v1/pets"); ``` ```bash curl https://api.example.com/v1/pets/1 -X DELETE ``` ```python requests.delete("https://api.example.com/v1/pets/1") ``` ```javascript await fetch("https://api.example.com/v1/pets/1", { method: "DELETE" }); ``` The match is by the **tab label** — the language name, or a `title="…"` if you set one. A group that doesn't have the picked language just stays on whatever it's showing. ## Variants A code group is the same tab widget as [Tabs](/components/navigation/tabs/), so it takes the same `variant`. It defaults to **`pills`** (the filled buttons above); pass `variant="default"` (or `"outline"`) for the **sliding underline** instead — the same animated indicator `{% tabs %}` uses: ```bash npm install my-app ``` ```bash title="pnpm" pnpm add my-app ``` ```bash title="yarn" yarn add my-app ``` ## Attributes | Attribute | Valid values | Description | | --- | --- | --- | | *(body)* | Fenced code blocks | Each `` ``` `` (or `~~~`) fenced block becomes one tab, in source order. Written between `{% codeGroup %}` and `{% endCodeGroup %}`. | | `defaultValue` | A tab label | On the **first** code group of a page, the language every group opens on; on a later group it only shows before the shared language applies. Overridden once a reader picks a language (remembered site-wide). Defaults to the first tab. Match the label — the language name (e.g. `JavaScript`) or a fence's `title`. | | `variant` | `pills`, `default`, `outline` | The tab style, shared with [Tabs](/components/navigation/tabs/). Defaults to `pills`; `default` / `outline` give the sliding underline. | | `attr` | `{…}` | Raw HTML attributes forwarded onto the widget's root element (see below). | Per-tab, on the fence itself: | Fence syntax | Description | | --- | --- | | ` ```lang ` | The language — drives both syntax highlighting and, unless overridden, the tab label (`json` → **JSON**). A fence with no language becomes a plain **Text** tab. | | ` ```lang title="name" ` | An explicit tab label, typically a filename. The language still drives highlighting. | Use a `~~~` fence (tildes) for a block whose own code contains a ` ``` ` run — the tab body is taken verbatim. ## CSS Selectors Each code group carries `data-aardvark-island="CodeGroup"` on its wrapper, and the rendered Mantine `Tabs` exposes its parts as `mantine-Tabs-*` classes. The per-tab code reuses the site's shared `.aardvark-code-block` / `.aardvark-code-actions` markup, so it styles identically to an on-page fenced block. ```css [data-aardvark-island="CodeGroup"] { /* style every code group on the page */ } .aardvark-code-group .mantine-Tabs-tab { /* a language / file tab */ } .aardvark-code-group .aardvark-code-block { /* the active tab's code block */ } ``` ## Injecting Attributes `attr={…}` forwards raw HTML attributes straight onto the rendered root element. ```bash npm install my-app ``` ```bash title="yarn" yarn add my-app ```
Source: Markdown ~~~aardvark {% codeGroup attr={'id': 'install-group'} %} ```bash npm install my-app ``` ```bash title="yarn" yarn add my-app ``` {% endCodeGroup %} ~~~