The code blocks to tab between. Each child becomes one tab, in source order.
Code Groups
CodeGroup folds several fenced blocks into a single tabbed panel, and the
title from each block's metastring becomes its tab label. The strip is a true
tablist under ARIA, the Accessible Rich Internet Applications standard: arrow
keys move between tabs and wrap around at either end, Home jumps to the first
tab, End to the last, and focus follows the selection.
Quick Start
Wrap two fenced blocks to get two tabs. Leave a blank line above and below each fence so MDX treats them as separate blocks.
npm install next react react-dompnpm add next react react-dom<CodeGroup>
```bash title="npm"
npm install next react react-dom
```
```bash title="pnpm"
pnpm add next react react-dom
```
</CodeGroup>Adding Options
Nothing ties a group to one language. Mix fences to show the same task three ways, and each panel keeps the highlighting of its own fence. When the labels outgrow the row, the strip scrolls sideways instead of wrapping.
const greeting = 'Hello, World!'
console.log(greeting)greeting = 'Hello, World!'
print(greeting)package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}<CodeGroup>
```javascript title="JavaScript"
const greeting = 'Hello, World!'
console.log(greeting)
```
```python title="Python"
greeting = 'Hello, World!'
print(greeting)
```
```go title="Go"
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
```
</CodeGroup>Advanced
Every metastring option keeps working inside a group, so each tab can carry its own line highlighting, starting line number, word pattern, or collapse cap.
export async function loader() {
const session = await getSession()
if (!session) return redirect('/login')
return json({ user: session.user })
}export function useSession() {
const { data } = useSWR('/api/session', fetcher)
return data ?? null
}<CodeGroup>
```typescript title="Server" {2-3} /session/
export async function loader() {
const session = await getSession()
if (!session) return redirect('/login')
return json({ user: session.user })
}
```
```typescript title="Client" showLineNumbers=40
export function useSession() {
const { data } = useSWR('/api/session', fetcher)
return data ?? null
}
```
```json title="Response" expandable maxLines=4
{
"user": {
"id": "usr_01H",
"name": "Placeholder User",
"roles": ["reader"]
},
"expiresAt": "2030-01-01T00:00:00.000Z"
}
```
</CodeGroup>Options
CodeGroup takes no configuration of its own. Every control lives on the blocks
inside it.
childrenReactNoderequiredCodeGroup resolves the tab label from the first of these that carries a value:
titlefrom the block's metastring, recommended and the only one set directly.- The block's
language. - The block's
aria-label, with a trailing " code block" removed. Tab N, counting from 1.
How a group behaves
- Give every block in a group a distinct title. React uses the labels as keys,
so two tabs called
npmcollide. - A group holding one block renders as a plain code block with no tab strip, so wrapping a single fence costs nothing.
- An empty group renders nothing at all.
- The tab strip is horizontally scrollable, which keeps six or seven package managers usable on a phone.
- Keyboard support follows the ARIA tablist pattern:
ArrowLeftandArrowRightstep through the tabs and wrap at both ends,Homeselects the first,Endselects the last, and the newly selected tab takes focus. Only the active tab is in the tab order, soTabmoves past the whole strip in one press. - Panels stay mounted and hidden rather than unmounted, so a collapsed block on an inactive tab keeps its state when the tab is selected again.
- For anything richer than code (prose, images, nested components), reach for Tabs instead.