Skip to content

Code block

Mono code on Row Mist with a title bar and copy, plus inline code.

Status
Stable
Category
Content
Adoption
Not used yet
import { CodeBlock } from "@oration/canon/components/code-block";
packages/canon/src/components/code-block.tsx

Quick start

Send the key as a bearer token. Every endpoint returns JSON.

Create a supplier
curl https://api.oration.app/v1/suppliers \
  -H "Authorization: Bearer $ORATION_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Northwind Freight",
    "remittance_email": "ar@northwindfreight.com"
  }'
import { Button } from "@oration/canon/components/button";import { CodeBlock } from "@oration/canon/components/code-block";import { toast } from "@oration/canon/components/toast";import * as React from "react";export function Hero() {    const code = `curl https://api.oration.app/v1/suppliers \\  -H "Authorization: Bearer $ORATION_API_KEY" \\  -H "Content-Type: application/json" \\  -d '{    "name": "Northwind Freight",    "remittance_email": "ar@northwindfreight.com"  }'`;    const headingId = React.useId();    return (        <section            aria-labelledby={headingId}            className="flex w-full max-w-xl flex-col gap-4"        >            <div className="flex flex-wrap items-end justify-between gap-3">                <div>                    <h2                        id={headingId}                        className="text-sm font-semibold text-foreground"                    >                        Quick start                    </h2>                    <p className="mt-1 text-13 text-muted-foreground">                        Send the key as a bearer token. Every endpoint returns                        JSON.                    </p>                </div>                <Button                    type="button"                    variant="outline"                    size="sm"                    onClick={() =>                        toast.add({                            title: "API reference",                            description:                                "Opens docs.oration.app in the full product.",                        })                    }                >                    View API reference                </Button>            </div>            <CodeBlock title="Create a supplier" code={code} />        </section>    );}

Usage#

Code block shows a snippet or payload people copy or read exactly: 12px Geist Mono on Row Mist, under a 36px title bar with a copy button. InlineCode is its inline sibling for a key, ID or variable inside a sentence. Both exist for The Machine Mono Rule: mono is only for strings a machine produced or will parse. The mistake runs the other way too, putting amounts, counts or labels in InlineCode because they look technical.

When to use

  • For a request people run, such as a curl command in the API quick start.
  • For a request preamble people read line by line: a method, URL and headers.
  • For a value they paste somewhere else in full, such as a generated URL.
  • Use InlineCode for a machine string inside a sentence: vendor_id, {{vendor_id}}, YOUR_PUBLIC_KEY, a slug to type.

When not to use

  • For a JSON payload people inspect: a webhook body, an API response, an audit log event, a tool's input and output. It needs folding, search and path copy. Use JSON viewer
  • For the same snippet in several languages. Use Code snippet tabs
  • For one secret or ID on a line with a copy button. Use Copy row
  • For a before-and-after of text or fields. Use Diff
  • For a template variable people insert into a prompt. Use Variable chip
  • For JSON people edit. Use an editor or a structured builder. Use JSON schema builder
  • For keyboard shortcuts. Use Kbd

The Machine Mono Rule

Geist Mono is only for strings a machine produced or will parse: record and run IDs, API keys, tokens, codes, DNS records, template variables, timecodes. Figures, labels, keyboard keys and headings stay in Geist Sans.

The Tabular Figures Rule

Amounts and counts beside code stay in Geist Sans with tabular figures. $58,902.14 is a figure, not code.

Anatomy#

Response
{
  "status": "paid",
  "amount": 18420.00
}
  1. Surface. Row Mist (bg-surface) with 10px corners and the hairline lift.
  2. Title. 12px medium Slate Meta in a 36px bar with a hairline bottom. Defaults to Code.
  3. Copy button. A 24px ghost Copy button named Copy {title}. The icon swaps to a green check for 1.6s after copying.
  4. Code. <pre><code> in 12px Geist Mono at a 20px line height, 12px padding, scrolling sideways when a line is long.

Examples#

A request

A method, URL and headers, titled for what they are, above the body in a JSON viewer. The endpoint in the sentence above is InlineCode.

Delivered to https://erp.cedarline.io/hooks in 212 ms.

Request
POST https://erp.cedarline.io/hooks
Content-Type: application/json
Oration-Signature: t=1759086131,v1=5f2c…
Request body4 keys
"id": "evt_7Qm2Lx",
"type": "payment_run.sent",
"created_at": "2026-09-25T19:02:11Z",
"data": {
"run_id": "run_0925_card",
"invoices": 41,
"amount": 58902.14,
"currency": "USD"

Arrow keys move between rows and open or close them. C copies a value, P copies its path.

import { CodeBlock, InlineCode } from "@oration/canon/components/code-block";import { JsonViewer } from "@oration/canon/components/json-viewer";export function Request() {    const preamble = `POST https://erp.cedarline.io/hooksContent-Type: application/jsonOration-Signature: t=1759086131,v1=5f2c…`;    return (        <div className="flex w-full max-w-md flex-col gap-2">            <p className="text-13 text-muted-foreground">                Delivered to{" "}                <InlineCode>https://erp.cedarline.io/hooks</InlineCode> in 212                ms.            </p>            <CodeBlock title="Request" code={preamble} />            <JsonViewer                title="Request body"                value={{                    id: "evt_7Qm2Lx",                    type: "payment_run.sent",                    created_at: "2026-09-25T19:02:11Z",                    data: {                        run_id: "run_0925_card",                        invoices: 41,                        amount: 58902.14,                        currency: "USD",                    },                }}            />        </div>    );}

Long lines

Lines never wrap. The code scrolls sideways inside the block, so a URL or token copies exactly as shown.

Widget URL
https://cdn.oration.app/widget.js?key=pk_live_7f3a9c2e41b84d0f9e6a&agent=agt_where_is_my_payment&theme=auto
import { CodeBlock } from "@oration/canon/components/code-block";export function LongLines() {    const code =        "https://cdn.oration.app/widget.js?key=pk_live_7f3a9c2e41b84d0f9e6a&agent=agt_where_is_my_payment&theme=auto";    return (        <CodeBlock title="Widget URL" code={code} className="w-full max-w-md" />    );}

Inline code

InlineCode marks machine strings in a sentence at 0.85em. A placeholder people must replace can take a Caution Amber tint.

Pass the supplier ID as vendor_id and the agent reads it as {{vendor_id}} in the prompt.

Replace YOUR_PUBLIC_KEY with an active live key.

import { InlineCode } from "@oration/canon/components/code-block";export function Inline() {    return (        <div className="flex max-w-md flex-col gap-3 text-sm text-foreground">            <p>                Pass the supplier ID as <InlineCode>vendor_id</InlineCode> and                the agent reads it as <InlineCode>{"{{vendor_id}}"}</InlineCode>{" "}                in the prompt.            </p>            <p className="text-13 text-muted-foreground">                Replace{" "}                <InlineCode className="bg-warning/15">                    YOUR_PUBLIC_KEY                </InlineCode>{" "}                with an active live key.            </p>        </div>    );}

In a typed confirmation

The string someone must type to confirm sits in InlineCode inside the field's label, and the field itself is mono.

import { Button } from "@oration/canon/components/button";import { InlineCode } from "@oration/canon/components/code-block";import { Input } from "@oration/canon/components/input";import { Label } from "@oration/canon/components/label";import { toast } from "@oration/canon/components/toast";import * as React from "react";export function TypedConfirmation() {    const inputId = React.useId();    const [value, setValue] = React.useState("");    const slug = "northwind-freight";    return (        <form            className="flex w-full max-w-sm flex-col gap-3"            onSubmit={(event) => {                event.preventDefault();                toast.add(                    value === slug                        ? { type: "success", title: "Supplier archived" }                        : {                              type: "error",                              title: "Type the supplier ID to confirm",                              description: `It's ${slug}.`,                          },                );            }}        >            <Label htmlFor={inputId} className="block leading-6 font-normal">                Type <InlineCode>{slug}</InlineCode> to archive this supplier.            </Label>            <Input                id={inputId}                value={value}                onChange={(event) => setValue(event.target.value)}                autoComplete="off"                spellCheck={false}                className="font-mono text-xs"            />            <div className="flex justify-end">                <Button type="submit" variant="destructive">                    Archive supplier                </Button>            </div>        </form>    );}

States#

States
StateTreatment
RestTitle, copy button and code. No syntax colors.
CopiedThe copy icon becomes a Ledger Green check and the button's name becomes Copied for 1.6s.
OverflowingLong lines scroll horizontally inside the block; the block never wraps or grows past its container.
Highlighted placeholderAn InlineCode placeholder people must replace can take bg-warning/15, as the widget install instructions do.

Behavior#

  • The copy button writes the exact code string to the clipboard, including line breaks, and swallows clipboard errors silently.
  • Whitespace is preserved: pass the code already indented. For JSON, pass the value to JSON viewer instead of JSON.stringify output.
  • There is no maximum height. Long payloads grow the block; wrap it in a scroll area or a sheet when that matters.
  • InlineCode sizes itself at 0.85em of the surrounding text, so it scales with the sentence it sits in.

Do and don't#

Test the lookup
curl -X POST https://erp.cedarline.io/lookup -d '{"phone":"+13125550100"}'
Do. Title every block with what it is or does: Test the lookup, Request body.
Code
curl -X POST https://erp.cedarline.io/lookup -d '{"phone":"+13125550100"}'
Don't. Leave the default title. Code says nothing, and the copy button is named Copy code on every block on the page.

Run run_0925_card paid $58,902.14 to 41 suppliers.

Do. Put IDs in InlineCode and keep amounts and counts in Geist Sans with tabular figures.

Run run_0925_card paid $58,902.14 to 41 suppliers.

Don't. Wrap amounts and counts in InlineCode. It breaks The Machine Mono Rule and makes figures look like identifiers.

Content#

  • Titles are short and sentence case: Create a supplier, Request body, Widget URL.
  • Use real, runnable examples with Cedarline's data, and environment variables for secrets: $ORATION_API_KEY, never a live key.
  • Placeholders people must replace are upper snake case (YOUR_PUBLIC_KEY) and called out in the sentence around the block.
  • Keep the sentence outside the block: explain what the code does above it, not in comments inside it.

Accessibility#

  • The copy button is named after the title (Copy Create a supplier), so give every block a distinct title when several share a page.
  • After copying, the button's name changes to Copied; there is no live announcement, so pair important copies with a toast.
  • The <pre> scrolls horizontally with the pointer or trackpad, but it isn't focusable, so keyboard users can't scroll a clipped line. Keep lines short where you can.
  • InlineCode is a <code> element and is read as plain text. Don't rely on the mono face alone to mark something people must type; say it in words too.
Keyboard interactions
KeysAction
TabMoves to the copy button.
EnterCopies the code.

Design tokens#

Design tokens
TokenUsed for
--surfaceRow Mist behind the code
shadow-borderThe block's hairline lift
--borderHairline under the title bar
--mutedInlineCode fill
--muted-foregroundTitle text
--font-monoGeist Mono for code
--radius-lg10px block corners
--radius-sm6px InlineCode corners

API reference#

CodeBlock

A titled, copyable block of code. Takes only the props below.

Props of CodeBlock
PropTypeDefaultDescription
codeRequiredstringNo defaultThe exact text to show and copy.
titlestring"Code"Shown in the bar and used to name the copy button.
classNamestringNo defaultMerged onto the outer container, for width and margins.

InlineCode

A machine string inside running text.

Other props spread onto <code>.

Props of InlineCode
PropTypeDefaultDescription
classNamestringNo defaultMerged after the base classes, for example bg-warning/15 on a placeholder.

Known gaps#

Where the implementation and the system disagree today. Follow the system, not the gap.

CodeBlock draws the hairline lift. Placed inside a card it becomes a raised surface inside a raised surface, which The Tint Well Rule rules out; there is no flat variant.

There's no language prop or syntax highlighting, while Code snippet tabs records a language per snippet. Canon's own docs highlight code; product code blocks don't.

There's no maxHeight or wrapping option, so long audit log payloads grow the sheet they sit in.

title doubles as the copy button's name, so a block without a title copies as Copy code. There's no separate copyLabel.

CodeBlock accepts no other props (no id, no aria-*), so a block can't be referenced by aria-describedby.