CodeBlockexperimental
Read-only code display card with a copy button, an accessible copy status, and horizontal scroll kept inside the block. It renders raw text, so syntax highlighting comes from a consumer or the StreamingMarkdown renderer.
Playground
type Source = { id: string; title: string; url?: string };
function cite(sources: Source[]): string {
return sources.map((s, i) => `[${i + 1}] ${s.title}`).join('\n');
} <CodeBlock lang="ts" {code} />01 Examples
Multi-line code
import { CodeBlock } from '@urbicon-ui/blocks';
export function greet(name: string): string {
return `Hello, ${name} — this line is long enough to scroll inside the block.`;
} <script lang="ts">
import { CodeBlock } from '@urbicon-ui/blocks';
// Rendered as raw text — CodeBlock never highlights on its own. Long lines
// scroll horizontally inside the block, so the page never scrolls sideways.
const code = `import { CodeBlock } from '@urbicon-ui/blocks';
export function greet(name: string): string {
return \`Hello, \${name} — this line is long enough to scroll inside the block.\`;
}`;
</script>
<CodeBlock lang="ts" {code} />
Track copies with onCopy
curl -fsSL https://ui.urbicon.de/install.sh | sh Copied 0 times.
<script lang="ts">
import { CodeBlock } from '@urbicon-ui/blocks';
// onCopy fires only after the clipboard write succeeds — the perfect hook for
// analytics ("install command copied") without polling the clipboard.
let copies = $state(0);
const code = 'curl -fsSL https://ui.urbicon.de/install.sh | sh';
</script>
<div class="w-full space-y-2">
<CodeBlock lang="bash" {code} onCopy={() => (copies += 1)} />
<p class="text-text-tertiary text-xs">
Copied {copies} time{copies === 1 ? '' : 's'}.
</p>
</div>
Extra header actions
SELECT id, title FROM sources WHERE cited = true; <script lang="ts">
import { CodeBlock, Button } from '@urbicon-ui/blocks';
// The `actions` snippet renders extra controls in the header, before the copy
// button — e.g. "Run in playground", "Open in editor".
const code = 'SELECT id, title FROM sources WHERE cited = true;';
</script>
<CodeBlock lang="sql" {code}>
{#snippet actions()}
<Button variant="ghost" size="sm">Run</Button>
{/snippet}
</CodeBlock>
02 Accessibility
Copy button label
The copy button carries an aria-label that swaps from copyLabel ("Copy") to copiedLabel ("Copied") for two seconds after a successful
copy, so its accessible name always matches what the user sees. A denied or failed clipboard write
leaves the label unchanged, so a copy is confirmed only when it actually happened.
Status announcement
A visually hidden role="status" region announces "Copied"
to screen readers. It ships in the DOM up front and only its text content changes, which assistive
tech announces reliably.
Scrollable region
The code body is a focusable role="region" (tabindex="0") labelled by the language, so keyboard users can reach and scroll horizontally overflowing
code (WCAG 2.1.1). Focus rings use focus-visible: for keyboard-only visibility.
03 API Reference
18 propsProp | Type | Default | Description | |
|---|---|---|---|---|
code required | string | — | The code to display and copy. Rendered as raw text (no highlighting). | |
actions | Snippet | — | Extra header actions rendered in the header, just before the copy button. | |
class | string | — | Extra classes merged onto the root element. | |
copiedLabel | string | 'Copied' | Label shown for two seconds after a successful copy. | |
copyFailedLabel | string | 'Copy failed' | Label shown for two seconds after a FAILED copy — e.g. a denied clipboard permission or a non-secure context. | |
copyLabel | string | 'Copy' | Accessible label / tooltip for the copy button at rest. These labels are plain English defaults rather than i18n-resolved, so this leaf stays out of the i18n registry. Pass your own strings to localise. | |
label | string | — | Header caption, shown instead of lang. For an embedded block, naming what
the payload *is* ("Input", "Response body") is often clearer than the
language it is serialised in. lang still names the scrollable region for
screen readers when both are given. | |
lang | string | — | Language label shown in the header. Display-only — does not drive highlighting. | |
onCopy | (code: string) => void | — | Called with the copied code after a successful clipboard write. | |
onCopyError | (error: unknown) => void | — | Called with the thrown reason when the clipboard write fails. Without a handler the failure is logged; either way the button reports it. | |
preset | string | — | Apply a named preset registered via <BlocksProvider presets={{ CodeBlock: {...} }}>.
Prefer this over class overrides when the requested look falls outside the
semantic intent palette. | |
showCopy | boolean | — | Show the copy button in the header. | |
slotClasses | Partial<Record<CodeBlockSlots, string>> | — | Per-slot class overrides. Slots: root | header | langLabel | copyButton | pre | code.
Note variant="plain" deliberately leaves root/header/pre without surface,
outline or padding — the embedding parent supplies those. | |
unstyled | boolean | — | Remove all default tv classes. | |
variant variant | cardplain | card | Controls the visual style and presentation of the CodeBlock. Determines the component's visual treatment. Available options: card, plain. | |
wrap | boolean | — | Soft-wrap long lines. false (default) scrolls horizontally inside the
block; true wraps with whitespace-pre-wrap + word breaking. | |
...CodeBlockVariants variant | VariantProps | — | Styling variants from CodeBlockVariants | |
...HTMLAttributes<HTMLElement> inherited | HTMLAttributes | — | HTML attributes (excluding: 'children' | 'class') |
04 Types
Local type definitions used by this component.
Name | Kind | Category | Used by | Description | |
|---|---|---|---|---|---|
CodeBlockProps | interface | props | 0 | — | |
CodeBlockSlots | type | variant | 0 | Slot names derived from the tv() config — single source of truth for slotClasses. | |
CodeBlockVariants | type | variant | 1 | — | |
SlotNames | type | helper | 0 | Extracts the slot-name union from a slotted tv() config function — the
companion to VariantProps. The slot-mode overload returns
(props?) => { [K in keyof S]: SlotFn }, so keyof ReturnType<T> is exactly
the set of slot names a component declares in tv({ slots: … }).
Use it to type a component's slotClasses prop from the single source of
truth (its *.variants.ts) instead of hand-maintaining a parallel union
that silently drifts when a slot is added or renamed: | |
VariantProps | type | helper | 1 | — |
05 Installation
Import
import { CodeBlock } from '@urbicon-ui/blocks';
import type { CodeBlockProps } from '@urbicon-ui/blocks';