GitHubnpm

Accent theme

Base radius

GitHubnpm

Accent theme

Base radius

Colour scheme

Docs menu

Guides


Primitives
Forms
Layout
Feedback
Overlays
Navigation
Data Display
AI

Code Block

A real <pre><code> inside a<figure>, with optional line numbers, emphasised lines, copy, and stable streaming. It highlights nothing itself — you hand it pre-tokenised lines — which is what keeps both packages dependency-free and lets one component serve Shiki, Prism, a server-side highlighter, or plain text.

packages/react/src/utils/stickToBottom.ts
export function useStickToBottom(options = {}) {
  const viewportRef = useRef(null);
  const contentRef = useRef(null);
  const [pinned, setPinned] = useState(true);

Playground

Toggle every adjustable prop and watch the component — and the code — update live.

stickToBottom.ts
const controller = new StickToBottomController(viewport, content, {
  onPinnedChange: (pinned) => setPinned(pinned),
});

// Following the live edge is always instant.
controller.scrollToBottom("instant");
Unstyled

Same component — Kernel look stripped. See Platforms.

line numbers
copyable
Usage
<CodeBlock
  label="stickToBottom.ts"
  language="ts"
  showLineNumbers={true}
  copyable={true}
  highlightLines={[4]}
  // or lines={await codeToTokens(source)} from your highlighter
  code={source}
/>

Props

Props for Code Block.
PropTypeDefault
linesCodeLine[]
codestring""
languagestring
labelReactNodefrom language
showLineNumbersbooleanfalse
highlightLinesnumber[] (1-based)
streamingbooleanfalse
copyablebooleantrue
maxBlockSizestring

Bring your own tokens

A CodeLine is { tokens: CodeToken[] }, and aCodeToken is{ text, className?, color? }className for highlighters that emit classes, color for the ones that emit inline colours. Shiki's codeToTokens maps over directly:

import { codeToTokens } from "shiki";

const { tokens } = await codeToTokens(source, { lang: "ts", theme: "github-dark" });
const lines = tokens.map((line) => ({ tokens: line }));

<CodeBlock lines={lines} language="ts" showLineNumbers />

That's documentation, not a dependency —@kernelui-lib/react and@kernelui-lib/elements ship with none. Skip it entirely and pass code for unstyled source. In the elements package the same data arrives as a DOM property (el.lines = … orel.code = …), because structured data can't travel through an attribute; light-DOM<pre><code> written inside the tag is read once at connect, so the code is real page content before any script runs.

Stable streaming

Lines are keyed by index, so appending output re-renders only the last line. Earlier lines aren't unmounted and re-created — which is what causes the flicker and lost text selection you get from re-rendering a growing block as one string. Following the last line reuses the sameStickToBottomControllerthat Message Scroller is built on, rather than a second scroll implementation, and following is always instant: a smooth animation per chunk would never catch up with the next one.

Accessibility

  • Line numbers are real text, but aria-hidden and unselectable. A screen reader reading "one const two import three…" is noise, and a copy that interleaves numbers with code is worse than no copy button at all.
  • Copy feedback is a live region, not just a changed icon — a button's own label changing is announced only if focus happens to be on it.
  • Denied clipboard access (insecure context, blocked permission) fails silently on purpose: the code is on screen and selectable, so there's nothing for the reader to act on.