Skip to main content
Gremorie

Code Block

Syntax-highlighted code surface powered by shiki. Lazy-loads token highlighting on mount, ships matched dark and light themes, and pairs with a context-aware copy button.

Overview

CodeBlock renders highlighted source code for any of shiki's bundled languages. It loads codeToHtml on mount (so the bundle stays small), produces both a light and a dark variant in parallel, and swaps between them via Tailwind's dark: selector instead of remounting.

A small React context exposes the raw code string to descendants. That lets CodeBlockCopyButton (and any custom child you drop into the absolutely-positioned action slot) copy the underlying source without prop drilling.

Use it for streamed code artifacts, MDX snippets inside tool output, agent-generated diffs and anywhere else you need an inline highlighted block.

Preview

Default

With copy button

Installation

bash npx gremorie@latest add rx-code-block
bash pnpm dlx gremorie@latest add rx-code-block
bash yarn dlx gremorie@latest add rx-code-block

bash bunx --bun gremorie@latest add rx-code-block

Requires shiki and @gremorie/rx-forms (for Button) as transitive dependencies. Both are pulled in automatically by the registry.

Usage

import { CodeBlock, CodeBlockCopyButton } from "@gremorie/rx-ai";

export function Example({ source }) {
  return (
    <CodeBlock code={source} language="tsx" showLineNumbers>
      <CodeBlockCopyButton />
    </CodeBlock>
  );
}
import { Component, input } from "@angular/core";
import { CodeBlock, CodeBlockCopyButton } from "@gremorie/ng-ai";

@Component({
selector: "app-example",
standalone: true,
imports: [CodeBlock, CodeBlockCopyButton],
template: `     <code-block [code]="source()" language="tsx" [showLineNumbers]="true">
      <code-block-copy-button />
    </code-block>
  `,
})
export class ExampleComponent {
readonly source = input.required<string>();
}

API

<CodeBlock>

PropTypeDefaultDescription
codestring-The source string to highlight. Required.
languageBundledLanguage-Any shiki bundled language ("tsx", "json", "bash", "python", etc.).
showLineNumbersbooleanfalseWhen true, prepends a muted line-number gutter via a shiki transformer.
childrenReactNode-Rendered in an absolutely-positioned top-right slot. Use for the copy button or custom actions.
classNamestring-Extra classes on the root div.

Extends all HTMLAttributes<HTMLDivElement>.

Themes used internally:

  • Light: github-light-default (Odo final audit upgrade. The plain github-light theme missed AA on operator and punctuation tokens.)
  • Dark: github-dark-default for symmetric AA contrast.

<CodeBlockCopyButton>

PropTypeDefaultDescription
onCopy() => void-Fires after a successful write to the clipboard.
onError(error: Error) => void-Fires when the Clipboard API is missing or rejected.
timeoutnumber2000How long (ms) the button stays in the "copied" state before reverting.
childrenReactNodecopy / check iconOverride the icon. The button still swaps to the check icon on success unless you fully replace it.

Pulls the code string from CodeBlockContext, so it must be rendered inside a <CodeBlock>.

highlightCode(code, language, showLineNumbers?)

Async helper exposed for SSR / prerender flows that want to compute both light and dark HTML up front. Returns Promise<[lightHtml, darkHtml]>.

Composition

  1. <CodeBlock> mounts a light and a dark <pre> block. Tailwind's dark: selector flips visibility, so theme switches are instant (no re-highlight).
  2. The CodeBlockContext provides code to descendants.
  3. Children (typically CodeBlockCopyButton) absolutely position into the top-right corner. The slot supports any node, so you can drop multiple buttons (e.g. copy + open in editor) by wrapping them in your own flex row.
  4. Line numbers are produced by the lineNumberTransformer so they participate in shiki's HTML output, not as a separate column.

Variations

With line numbers

Useful for long snippets where users need a reference point. The gutter is muted so it does not compete with the token colors.

<CodeBlock code={longSnippet} language="tsx" showLineNumbers />

Copy plus custom action

The action slot accepts any node. Compose multiple buttons by nesting your own flex container.

<CodeBlock code={source} language="tsx">
  <CodeBlockCopyButton />
  <Button size="icon" variant="ghost" onClick={openInEditor}>
    <ExternalLinkIcon />
  </Button>
</CodeBlock>

JSON for tool output

Pair with Tool. ToolInput and ToolOutput both render through CodeBlock, so JSON tool calls inherit the same highlighting and theming.

<CodeBlock code={JSON.stringify(toolResult, null, 2)} language="json" />

Accessibility

  • Keyboard: The copy button is a real Button, so it participates in normal Tab order and responds to Enter / Space.
  • Contrast: Both github-light-default and github-dark-default themes clear WCAG AA 4.5:1 on every token. The Odo audit explicitly rejected the older github-light theme because operator tokens dropped below the threshold.
  • Reduced motion: There is no animation on the surface itself. The copy button swaps between two icons without transition.
  • Screen readers: When the copy button is icon-only, supply an accessible name through your own composition (e.g. wrap in a Tooltip with aria-label). Internally the button is a ghost icon Button, so the standard a11y rules for icon buttons apply.
  • Artifact - the card chrome that hosts a CodeBlock for generated code artifacts
  • Tool - uses CodeBlock internally to render tool input and output
  • Web Preview - the iframe surface paired with CodeBlock for live HTML preview

On this page