CodeBlock

The shell around every highlighted code fence.

CodeBlock is the visual container Boltdocs wraps around every fenced code block (```ts, ```tsx, ```bash, etc.). It handles the rounded border, the header strip with the language label, and the action toolbar (copy button + expand / collapse). You generally won't reach for it directly in .mdx content — Boltdocs injects it automatically through MDX — but it's exposed so you can build a custom highlighted snippet inside your own components.

When to reach for itLink

Three situations call for using CodeBlock directly:

  1. Highlighting a snippet that isn't fenced — for example, a string returned from a fetch you want to render as code.
  2. Building a Tab-like demo widget — each panel contains a code sample that should share the same border/header style as the rest of the docs.
  3. Building a custom <Playground> or <LiveCode> component — for editor playgrounds that need to mirror the docs look.

If you're just writing documentation content, don't bother — write a fenced code block instead. Boltdocs wires it up automatically.


AnatomyLink

A code block has three optional regions:

tsx
<CodeBlock>
  <CodeBlock.Header>
    <CodeBlock.Group>{/* e.g. language icon + "TypeScript" */}</CodeBlock.Group>
    <CodeBlock.Actions>{/* e.g. copy button, feedback thumbs */}</CodeBlock.Actions>
  </CodeBlock.Header>

  <CodeBlock.Content>
    <CodeBlock.Pre>{/* highlighted or raw code */}</CodeBlock.Pre>
    <CodeBlock.Expand />
  </CodeBlock.Content>
</CodeBlock>
  • CodeBlock (root) — sets the rounded border, background color, and outer padding. Pass plain to render without a border, useful inside cards that already have one.
  • CodeBlock.Header — the strip above the code. Holds the language label on the left and copy/expand buttons on the right.
  • CodeBlock.Group — horizontal flex container. Use it to group the language logo and label together inside Header.
  • CodeBlock.Actions — toolbar container for the header's right-aligned actions (copy button, feedback). Pass className to tint the surface behind floating headers.
  • CodeBlock.Content — wraps the <pre> (or whatever renderer you choose). Pass shouldTruncate to clamp the snippet to 300px and let users expand it.
  • CodeBlock.Pre — renders the Shiki HTML when highlightedHtml is provided, otherwise falls back to children. Accepts highlightedHtml, isHighlighted, and a ref so hooks can measure the rendered content.
  • CodeBlock.Expand — the expand/collapse button for truncated blocks. Wire it to useExpandable with isExpandable, shouldTruncate, isExpanded, and onToggle. Accepts buttonClassName for the button surface.

Putting it togetherLink

tsx
import { Button, CodeBlock } from 'boltdocs/primitives'
import { useCopyButton, useExpandable } from 'boltdocs/client'
import { Copy, Check } from 'lucide-react'

export function RawSnippet({ lang, code, html }: { lang: string; code: string; html: string }) {
  const { copied, handleCopy } = useCopyButton()
  const { isExpanded, isExpandable, shouldTruncate, toggle, preRef } =
    useExpandable({ children: code, highlightedHtml: html })

  return (
    <CodeBlock plain>
      <CodeBlock.Header>
        <CodeBlock.Group>
          <span className="text-xs font-mono uppercase">{lang}</span>
        </CodeBlock.Group>
        <CodeBlock.Actions>
          <Button onPress={() => handleCopy(preRef.current?.textContent ?? '')}>
            {copied ? <Check size={16} /> : <Copy size={16} />}
          </Button>
        </CodeBlock.Actions>
      </CodeBlock.Header>
      <CodeBlock.Content shouldTruncate={shouldTruncate}>
        <CodeBlock.Pre ref={preRef} highlightedHtml={html} isHighlighted>
          {code}
        </CodeBlock.Pre>
        <CodeBlock.Expand
          isExpandable={isExpandable}
          shouldTruncate={shouldTruncate}
          isExpanded={isExpanded}
          onToggle={toggle}
        />
      </CodeBlock.Content>
    </CodeBlock>
  )
}

That's it — the inner HTML is the pre-rendered Shiki output, so it works during SSG with no client-side highlighter shipped to the browser.


HooksLink

Feature-scoped hooks give you the interactive behavior without coupling to the shell. They are exported from boltdocs/client and can be used with the primitive parts or standalone.

useCopyButtonLink

Copy-to-clipboard state with a transient 2s "copied" confirmation:

tsx
const { copied, handleCopy } = useCopyButton()
<CodeBlock.Actions>
  <Button onPress={() => handleCopy(preRef.current?.textContent ?? '')}>
    {copied ? <Check size={16} /> : <Copy size={16} />}
  </Button>
</CodeBlock.Actions>

useExpandableLink

Expand/collapse for long blocks. Measures the rendered code through preRef and reports whether the block overflows maxLines (default 6):

tsx
const { isExpanded, isExpandable, shouldTruncate, toggle, preRef } =
  useExpandable({ children, highlightedHtml })

<CodeBlock.Content shouldTruncate={shouldTruncate}>
  <CodeBlock.Pre ref={preRef} highlightedHtml={html} isHighlighted>
    {children}
  </CodeBlock.Pre>
  <CodeBlock.Expand
    isExpandable={isExpandable}
    shouldTruncate={shouldTruncate}
    isExpanded={isExpanded}
    onToggle={toggle}
  />
</CodeBlock.Content>

useCodeBlockFeedbackLink

Thumbs-up / thumbs-down feedback. Only active when the feedback.custom integration is enabled in the site config, and never for plain blocks. Override submission via submitFeedback:

tsx
const { rated, handleRate, enabled } = useCodeBlockFeedback({ plain, lang })

useCodeBlock (deprecated)Link

The old single-hook composition still works but is deprecated since v4. It is now an internal composition of useCopyButton, useExpandable, and useCodeBlockFeedback and will be removed in a future major version. Migrate by composing the three feature hooks yourself.


PropsLink

PropTypeDefaultWhat it does
plain (root)booleanfalseSkip the outer border and padding. Useful inside a parent that already has them.
shouldTruncate (content)booleanfalseCap the code at ~300px and add an expand button.
highlightedHtml (pre)string—Pre-rendered Shiki HTML rendered instead of children.
isHighlighted (pre)booleanfalseMarks the content as already-highlighted (disables the highlight source-swap).
ref (pre)Ref<HTMLElement>—Forwarded to the code element so hooks can measure text content.
isExpandable / shouldTruncate / isExpanded / onToggle (expand)boolean × 3, () => voidfalse / false / false / —Expand-button state wired from useExpandable.
buttonClassName (expand)string—Extra Tailwind classes for the expand button.
expandLabel (expand)ReactNode'Expand code'Label shown when the collapsed button asks to expand.
collapseLabel (expand)ReactNode'Show less'Label shown when the expanded toggle asks to collapse.
expandIcon (expand)ReactNode—Custom leading icon for the expand/collapse toggle button.
classNamestring—Extra Tailwind classes.
childrenReactNode—Sub-components.

Every sub-component inherits the common children / className / style shape.


PitfallsLink

  • The default Boltdocs <pre> block uses scroll-into-view-if-needed internally for code that overflows. Don't override the <pre>'s overflow style unless you know what you're doing.
  • Highlighting happens at build/dev time, not in the browser. The highlight() helper returns pre-rendered HTML. If you ship a heavy RxJS live editor you may need a different runtime highlighter — CodeBlock itself is just a shell.
  • The copy button reads from the original code text, not the highlighted HTML — so international characters and emojis round-trip cleanly.

See alsoLink

  • DocsLayout — uses CodeBlock implicitly via MDX rendering.
  • Tabs — pair with CodeBlock to build multi-language copy tabs.

Customization & statesLink

The CodeBlock primitive is style-neutral: it is a shell — structure and behavior come from the primitive, and every visual decision (colors, borders, spacing) belongs to the site theme through className passthrough on every part. It emits no data-* state attributes; the copy "copied" state is exposed by the useCopyButton hook (it returns copied), so the copied styling is handled inside the theme's button.

State / SlotMechanism
CodeBlock (root)Shell <div>; plain skips border and padding; background and border come from theme tokens (--color-code-bg, border-subtle)
CodeBlock.Header / Group / ActionsLayout slots; alignment is baked in, colors via className
CodeBlock.ContentWraps the <pre>; shouldTruncate clamps the snippet to ~300px
CodeBlock.Pre<pre> or Shiki wrapper; typography and spacing via className
CodeBlock.ExpandExpand/collapse button from useExpandable; isExpanded drives the label; styling via buttonClassName
Copied stateNo data-* attribute — useCopyButton() returns copied; swap the icon or classes in the theme's button
tsx
<CodeBlock className="my-6 rounded-xl border border-subtle bg-(--color-code-bg)">
  <CodeBlock.Header>
    <CodeBlock.Group>
      <span className="text-xs font-mono uppercase text-muted">{lang}</span>
    </CodeBlock.Group>
    <CodeBlock.Actions>
      <Button
        onPress={() => handleCopy(preRef.current?.textContent ?? '')}
        className={copied ? 'text-primary-500' : 'text-muted hover:text-body'}
      >
        {copied ? <Check size={16} /> : <Copy size={16} />}
      </Button>
    </CodeBlock.Actions>
  </CodeBlock.Header>
  <CodeBlock.Content>
    <CodeBlock.Pre ref={preRef}>{code}</CodeBlock.Pre>
  </CodeBlock.Content>
</CodeBlock>
Last updated on October 18, 2026

Was this page helpful?