Code Block
- Stable
- WCAG 2.2 evidence
- RTL
A block of code with a header and a copy button. It shows plain code, with line numbers
drawn by a CSS counter (not selectable, not copied), emphasised lines, a file name and language, or several samples in tabs.
It has no syntax highlighter (owner decision D7): draw highlighted nodes yourself with renderLine, using the Figma token
colours in codeTokenStyles. Presentational shell for React Server Components; only the copy button and the tab strip are
client islands. Accessibility: the code area scrolls sideways inside the block (so the page never scrolls at 320 px, WCAG 1.4.10), is focusable
with the keyboard and named (2.1.1, 4.1.2), and keeps its focus ring inside the block (2.4.7). Code is always left to right
(1.3.2). The copy button is a real button; the result is shown on the button and announced (4.1.3). Tabs follow the
WAI-ARIA tabs pattern. Consumer duties: check the contrast of your own token colours on bg/sunken; say in the text around the block what an
emphasised line is about; translate the labels; keep secrets out of samples.
import { CodeBlock } from "@nexera-ui/react";- 10
- examples
- 18
- props
- 2
- live controls
- 1
- platform
- 11
- WCAG criteria
- 1
- block uses it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { CodeBlock } from "@nexera-ui/react";
<CodeBlock />Examples 9
The same examples as Storybook, rendered live. Open Code to copy one.
Plain code
Without renderLine the code is plain text in the primary colour.
import { CodeBlock } from "@nexera-ui/react";
const SAMPLE = [
"import { Acme } from '@acme/hrms-sdk';",
"",
"const acme = new Acme({ apiKey: process.env.ACME_API_KEY });",
"const people = await acme.employees.list({",
" department: 'Engineering', limit: 50",
"});",
'// people.data[0].name === "Ayesha Khan"',
].join("\n");
export function PlainCode() {
return <CodeBlock filename="employees.ts" language="TypeScript" code={SAMPLE} />;
}
Without line numbers
Figma Show line numbers off: the lines start 16 px in. The counter is CSS, so numbers are never selected or copied.
import { type ReactNode } from "react";
import { CodeBlock, codeTokenStyles } from "@nexera-ui/react";
type Token = NonNullable<Parameters<typeof codeTokenStyles>[0]>["token"];
const SAMPLE = [
"import { Acme } from '@acme/hrms-sdk';",
"",
"const acme = new Acme({ apiKey: process.env.ACME_API_KEY });",
"const people = await acme.employees.list({",
" department: 'Engineering', limit: 50",
"});",
'// people.data[0].name === "Ayesha Khan"',
].join("\n");
const FIGMA_TOKENS: Record<number, readonly (readonly [Token, string])[]> = {
1: [
["keyword", "import"],
["plain", " { Acme } "],
["keyword", "from"],
["string", " '@acme/hrms-sdk'"],
["plain", ";"],
],
3: [
["keyword", "const"],
["plain", " acme "],
["plain", "="],
["keyword", " new"],
["function", " Acme"],
["plain", "({ apiKey: process.env."],
["constant", "ACME_API_KEY"],
["plain", " });"],
],
4: [
["keyword", "const"],
["plain", " people "],
["keyword", "= await"],
["plain", " acme.employees."],
["function", "list"],
["plain", "({"],
],
5: [
["plain", " department: "],
["string", "'Engineering'"],
["plain", ", limit: "],
["number", "50"],
],
6: [["plain", "});"]],
7: [["comment", '// people.data[0].name === "Ayesha Khan"']],
};
const figmaLine: CodeLineRenderer = (line, lineNumber): ReactNode => {
const tokens = FIGMA_TOKENS[lineNumber];
if (!tokens) return line;
return tokens.map(([token, text], index) => (
<span key={index} className={codeTokenStyles({ token })}>
{text}
</span>
));
};
export function WithoutLineNumbers() {
return (
<CodeBlock
lineNumbers={false}
filename="employees.ts"
language="TypeScript"
code={SAMPLE}
highlightedLines={[4, 5, 6]}
renderLine={figmaLine}
/>
);
}
Shell command
A shell command: no file name or language, a named code area ("Install command") and the copy button.
import { CodeBlock } from "@nexera-ui/react";
export function ShellCommand() {
return (
<CodeBlock code="npm install @acme/hrms-sdk" codeLabel="Install command" lineNumbers={false} />
);
}
Pinned themes
theme pins the block to the light or dark colours whatever the page uses, as the Figma Theme variants do.
import { type ReactNode } from "react";
import { CodeBlock, codeTokenStyles } from "@nexera-ui/react";
type Token = NonNullable<Parameters<typeof codeTokenStyles>[0]>["token"];
const SAMPLE = [
"import { Acme } from '@acme/hrms-sdk';",
"",
"const acme = new Acme({ apiKey: process.env.ACME_API_KEY });",
"const people = await acme.employees.list({",
" department: 'Engineering', limit: 50",
"});",
'// people.data[0].name === "Ayesha Khan"',
].join("\n");
const FIGMA_TOKENS: Record<number, readonly (readonly [Token, string])[]> = {
1: [
["keyword", "import"],
["plain", " { Acme } "],
["keyword", "from"],
["string", " '@acme/hrms-sdk'"],
["plain", ";"],
],
3: [
["keyword", "const"],
["plain", " acme "],
["plain", "="],
["keyword", " new"],
["function", " Acme"],
["plain", "({ apiKey: process.env."],
["constant", "ACME_API_KEY"],
["plain", " });"],
],
4: [
["keyword", "const"],
["plain", " people "],
["keyword", "= await"],
["plain", " acme.employees."],
["function", "list"],
["plain", "({"],
],
5: [
["plain", " department: "],
["string", "'Engineering'"],
["plain", ", limit: "],
["number", "50"],
],
6: [["plain", "});"]],
7: [["comment", '// people.data[0].name === "Ayesha Khan"']],
};
const figmaLine: CodeLineRenderer = (line, lineNumber): ReactNode => {
const tokens = FIGMA_TOKENS[lineNumber];
if (!tokens) return line;
return tokens.map(([token, text], index) => (
<span key={index} className={codeTokenStyles({ token })}>
{text}
</span>
));
};
export function PinnedThemes() {
return (
<div className="flex flex-col gap-6">
{(["light", "dark"] as const).map((theme) => (
<CodeBlock
key={theme}
theme={theme}
filename={`${theme}.ts`}
language="TypeScript"
code={SAMPLE}
highlightedLines={[4, 5, 6]}
renderLine={figmaLine}
/>
))}
</div>
);
}
Tabbed
Tabs follow the WAI-ARIA tabs pattern: arrow keys, Home and End move between tabs and the copy button copies the selected sample.
import { CodeBlock } from "@nexera-ui/react";
const CURL = [
"curl https://api.acme.com/v1/employees \\",
" -H 'Authorization: Bearer $ACME_API_KEY' \\",
" -d department=Engineering -d limit=50",
].join("\n");
const NODE = [
"const acme = new Acme({ apiKey: process.env.ACME_API_KEY });",
"const people = await acme.employees.list({",
" department: 'Engineering', limit: 50",
"});",
].join("\n");
const PYTHON = [
"acme = Acme(api_key=os.environ['ACME_API_KEY'])",
"people = acme.employees.list(",
" department='Engineering', limit=50",
")",
].join("\n");
const tabs = [
{ id: "curl", label: "cURL", code: CURL },
{ id: "node", label: "Node", code: NODE, language: "typescript" },
{ id: "python", label: "Python", code: PYTHON, language: "python" },
] as const satisfies readonly CodeBlockTab[];
export function Tabbed() {
return <CodeBlock type="tabbed" tabsLabel="Code samples" tabs={tabs} defaultSelectedKey="node" />;
}
Controlled tabs
Controlled tabs: the parent owns selectedKey and hears onSelectionChange (a function, so this needs a client component).
import { useState } from "react";
import { CodeBlock } from "@nexera-ui/react";
const CURL = [
"curl https://api.acme.com/v1/employees \\",
" -H 'Authorization: Bearer $ACME_API_KEY' \\",
" -d department=Engineering -d limit=50",
].join("\n");
const NODE = [
"const acme = new Acme({ apiKey: process.env.ACME_API_KEY });",
"const people = await acme.employees.list({",
" department: 'Engineering', limit: 50",
"});",
].join("\n");
const PYTHON = [
"acme = Acme(api_key=os.environ['ACME_API_KEY'])",
"people = acme.employees.list(",
" department='Engineering', limit=50",
")",
].join("\n");
const tabs = [
{ id: "curl", label: "cURL", code: CURL },
{ id: "node", label: "Node", code: NODE, language: "typescript" },
{ id: "python", label: "Python", code: PYTHON, language: "python" },
] as const satisfies readonly CodeBlockTab[];
export function ControlledTabs() {
const [key, setKey] = useState("python");
return (
<div className="flex flex-col gap-3">
<p className="text-body-small text-secondary m-0">Selected sample: {key}</p>
<CodeBlock
type="tabbed"
tabsLabel="Code samples"
tabs={tabs}
selectedKey={key}
onSelectionChange={setKey}
/>
</div>
);
}
Long lines
Long lines scroll sideways inside the block, never the page, even at 320 px. The code area is focusable: Tab to it and use the arrow keys. Its focus ring is drawn inside the block.
import { CodeBlock } from "@nexera-ui/react";
export function LongLines() {
return (
<div className="max-w-xs">
<CodeBlock
filename="payroll-export.ts"
language="TypeScript"
highlightedLines={[2]}
code={[
"const payrollExportDestinationBucketNameForProduction = 'acme-hrms-payroll-exports-eu-west-1-production-2026';",
"await acme.payroll.runs.export({ runId, format: 'csv', destination: payrollExportDestinationBucketNameForProduction, notify: ['payroll@acme.com'] });",
].join("\n")}
/>
</div>
);
}
Translated labels
Every visible text of the block is a prop, so it can be translated (German here). The announcements are translated too.
import { CodeBlock } from "@nexera-ui/react";
const SAMPLE = [
"import { Acme } from '@acme/hrms-sdk';",
"",
"const acme = new Acme({ apiKey: process.env.ACME_API_KEY });",
"const people = await acme.employees.list({",
" department: 'Engineering', limit: 50",
"});",
'// people.data[0].name === "Ayesha Khan"',
].join("\n");
export function TranslatedLabels() {
return (
<div lang="de">
<CodeBlock
code={SAMPLE}
codeLabel="Beispielcode"
copyLabel="Kopieren"
copiedLabel="Kopiert"
copiedMessage="In die Zwischenablage kopiert"
copyFailedMessage="Kopieren nicht möglich"
/>
</div>
);
}
Right to left
Right-to-left page: the header mirrors (the copy button moves to the left) while the code, its line numbers and the file name stay left to right.
import { type ReactNode } from "react";
import { CodeBlock, codeTokenStyles } from "@nexera-ui/react";
type Token = NonNullable<Parameters<typeof codeTokenStyles>[0]>["token"];
const SAMPLE = [
"import { Acme } from '@acme/hrms-sdk';",
"",
"const acme = new Acme({ apiKey: process.env.ACME_API_KEY });",
"const people = await acme.employees.list({",
" department: 'Engineering', limit: 50",
"});",
'// people.data[0].name === "Ayesha Khan"',
].join("\n");
const FIGMA_TOKENS: Record<number, readonly (readonly [Token, string])[]> = {
1: [
["keyword", "import"],
["plain", " { Acme } "],
["keyword", "from"],
["string", " '@acme/hrms-sdk'"],
["plain", ";"],
],
3: [
["keyword", "const"],
["plain", " acme "],
["plain", "="],
["keyword", " new"],
["function", " Acme"],
["plain", "({ apiKey: process.env."],
["constant", "ACME_API_KEY"],
["plain", " });"],
],
4: [
["keyword", "const"],
["plain", " people "],
["keyword", "= await"],
["plain", " acme.employees."],
["function", "list"],
["plain", "({"],
],
5: [
["plain", " department: "],
["string", "'Engineering'"],
["plain", ", limit: "],
["number", "50"],
],
6: [["plain", "});"]],
7: [["comment", '// people.data[0].name === "Ayesha Khan"']],
};
const figmaLine: CodeLineRenderer = (line, lineNumber): ReactNode => {
const tokens = FIGMA_TOKENS[lineNumber];
if (!tokens) return line;
return tokens.map(([token, text], index) => (
<span key={index} className={codeTokenStyles({ token })}>
{text}
</span>
));
};
const CURL = [
"curl https://api.acme.com/v1/employees \\",
" -H 'Authorization: Bearer $ACME_API_KEY' \\",
" -d department=Engineering -d limit=50",
].join("\n");
const NODE = [
"const acme = new Acme({ apiKey: process.env.ACME_API_KEY });",
"const people = await acme.employees.list({",
" department: 'Engineering', limit: 50",
"});",
].join("\n");
const PYTHON = [
"acme = Acme(api_key=os.environ['ACME_API_KEY'])",
"people = acme.employees.list(",
" department='Engineering', limit=50",
")",
].join("\n");
const tabs = [
{ id: "curl", label: "cURL", code: CURL },
{ id: "node", label: "Node", code: NODE, language: "typescript" },
{ id: "python", label: "Python", code: PYTHON, language: "python" },
] as const satisfies readonly CodeBlockTab[];
export function RightToLeft() {
return (
<div dir="rtl" lang="ar" className="flex flex-col gap-6">
<CodeBlock
filename="employees.ts"
language="TypeScript"
code={SAMPLE}
highlightedLines={[4, 5, 6]}
renderLine={figmaLine}
copyLabel="نسخ"
copiedLabel="تم النسخ"
copiedMessage="تم النسخ إلى الحافظة"
/>
<CodeBlock
type="tabbed"
tabsLabel="نماذج الشيفرة"
tabs={tabs}
copyLabel="نسخ"
copiedLabel="تم النسخ"
copiedMessage="تم النسخ إلى الحافظة"
/>
</div>
);
}
Props 18
Press "Try it" on a card to load that prop into the playground.
18 props shown
themeNexera"light" | "dark"Pins the block to the light or the dark colours whatever the page uses. Without it the block follows the
page theme, which is the Figma Light look on a light page; Figma has Light as the default variant.
lineNumbersNexerabooleanShows a line number before every line. The numbers are drawn by a CSS counter, so they cannot be selected or copied and are not part of the code.
codeLabelNexerastringAccessible name of the scrollable code area, which is a focusable group: the file name when there is one, otherwise this
text. Tabbed blocks name each area with its tab label. Translate it for your locale.
copyLabelNexerastringLabel of the copy button. Translate it for your locale.
copiedLabelNexerastringLabel of the copy button for 2 seconds after the code was copied. Translate it for your locale.
copiedMessageNexerastringMessage announced to assistive technology after the code was copied (WCAG 4.1.3). Translate it for your locale.
copyFailedMessageNexerastringMessage announced (assertively) when the browser does not allow copying, for example on a page that is not served over HTTPS. The code can still be selected with the keyboard. Translate it for your locale.
typeNexera"single-file" | "tabbed"What the block holds: single-file is one code sample with a header showing its file name and language.
Several samples chosen with tabs. The copy button copies the selected one.
codeNexerastringThe code as plain text. It is what the copy button copies, and what is drawn unless
renderLine draws it differently. Line breaks (\n) start new lines; one trailing line break is ignored.
Not used by a tabbed block: each tab has its own.
filenameNexerastringFile name in the header, with a file icon. The header has no name when it is left out. Not used by a tabbed block.
languageNexerastringLanguage chip in the header. Shown when provided.
Not used by a tabbed block: each tab has its own language.
highlightedLinesNexerareadonly number[]Lines to emphasise (1-based): Figma fills lines 4 to 6 of its sample with nav/active-bg. They are emphasis only, so also mention them in the text around the block.
Not used by a tabbed block: each tab has its own.
renderLineNexeraCodeLineRendererDraws each line, so you can supply highlighted nodes without a highlighter built into the library (owner decision D7: no
syntax-highlighting dependency). See {@link CodeLineRenderer}. Runs where CodeBlock runs (on the server in a Server
Component), so it may use any highlighter your app has.
Not used by a tabbed block: each tab has its own.
tabsNexerareadonly [CodeBlockTab, ...CodeBlockTab[]]Not used by a single file. The samples, at least one (see {@link CodeBlockTab}).
tabsLabelNexerastringNot used by a single file. Accessible name of the tab list ("Code samples"). Translate it for your locale (WCAG 4.1.2).
selectedKeyNexerastringNot used by a single file.
Id of the selected tab (controlled). Needs a client component, because it comes with onSelectionChange.
defaultSelectedKeyNexerastringNot used by a single file. Id of the tab selected at first (uncontrolled).
onSelectionChangeNexera(id: string) => voidNot used by a single file. Called with the id of the tab the user selected. A function cannot be passed from a Server Component: use it from a client component.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
5 direct · 6 supporting- 1.1.1Non-text ContentLevel A · tested directly
- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.3.2Meaningful SequenceLevel A · supporting test
- 1.4.1Use of ColorLevel A · supporting test
- 1.4.10ReflowLevel AA · supporting test
- 2.1.1KeyboardLevel A · tested directly
- 2.4.7Focus VisibleLevel AA · supporting test
- 2.4.11Focus Not Obscured (Minimum)Level AA · supporting test
- 2.5.8Target Size (Minimum)Level AA · supporting test
- 4.1.2Name, Role, ValueLevel A · tested directly
- 4.1.3Status MessagesLevel AA · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
data-hovereddata-presseddata-selected
<CodeBlock className="data-hovered:opacity-90 shadow-sm" />Used in blocks
Related components
- AccordionItemOne collapsible section of an FAQ or settings page.
- AreaChartA stacked area chart: layers piled up over time with the title, range switch, actions and legend of the card.
- AvailabilityRowOne person's availability across a window of the day: an avatar with initials and the name, and a timeline with the busy periods drawn as bars.
- AvatarA person or organisation shown as a photo, initials or an icon.
- AvatarGroupA row of overlapping avatars with a "+N" chip for the rest.
- AvatarLabelAn avatar with a name and an optional subtitle beside it, for a person in a list, a header or a mention.
- BadgeShort status or category label: `Tone` x `Variant` x `Size`, with an optional dot, leading icon and remove button.
- BadgeGroupA pill that pairs a small badge with a short message, for an announcement or a status line.