react ~4 KB 0 deps v0.1.1 β†— GitHub β†—

@mshafiqyajid/react-code-block

Headless code block hook and styled syntax-highlighted code component for React. Optional Shiki integration, diff view, line numbers, copy button, terminal bar, focus lines, collapsible, badge.

Playground #

tsx
import { useState } from "react";

interface CounterProps {
  initialCount?: number;
}

export function Counter({ initialCount = 0 }: CounterProps) {
  const [count, setCount] = useState(initialCount);

  return (
    <div className="counter">
      <button onClick={() => setCount((c) => c - 1)}>βˆ’</button>
      <span>{count}</span>
      <button onClick={() => setCount((c) => c + 1)}>+</button>
    </div>
  );
}
Props
TSX
import { CodeBlockStyled } from "@mshafiqyajid/react-code-block/styled";
import "@mshafiqyajid/react-code-block/styles.css";

<CodeBlockStyled
  language="tsx"
/>

Install #

npm install @mshafiqyajid/react-code-block

For syntax highlighting, install Shiki as an optional peer dep:

npm install shiki

Quick start #

import { CodeBlockStyled } from "@mshafiqyajid/react-code-block/styled";
import "@mshafiqyajid/react-code-block/styles.css";

export function Example() {
  return (
    <CodeBlockStyled
      code={`import { useState } from "react";

export function Counter({ initialCount = 0 }) {
  const [count, setCount] = useState(initialCount);
  return (
    <div>
      <button onClick={() => setCount(c => c - 1)}>βˆ’</button>
      <span>{count}</span>
      <button onClick={() => setCount(c => c + 1)}>+</button>
    </div>
  );
}`}
      language="tsx"
      showLanguageLabel
      showCopy
    />
  );
}

Terminal mode #

Set terminal to render a macOS-style window chrome header. The existing title prop becomes the centered window title.

<CodeBlockStyled
  code={shellCode}
  language="bash"
  terminal
  title="~/projects/my-app"
  showCopy
/>

Collapsible #

Use maxLines to clamp visible lines. A gradient fade and an Expand / Collapse button appear automatically. Set expandable= to disable the button and keep the block permanently truncated.

<CodeBlockStyled
  code={longCode}
  language="tsx"
  maxLines={10}
  showLineNumbers
/>

Focus lines #

Pass 1-based line numbers to focusLines to highlight those lines and dim all others to 30% opacity. Transitions are 200 ms.

<CodeBlockStyled
  code={multiLineCode}
  language="tsx"
  focusLines={[3, 4, 5, 6]}
  showLineNumbers
/>

Badge #

The badge prop renders a small pill label in the header bar next to the language label. Useful for "Live", "New", "Beta", etc.

<CodeBlockStyled code={code} language="tsx" badge="Live" showLanguageLabel />

Diff mode #

Set diff to parse lines prefixed with + (add) or - (remove). Neutral lines begin with a space.

<CodeBlockStyled
  code={`-const old = "removed";
+const next = "added";
 const unchanged = true;`}
  language="typescript"
  diff
  title="example.ts"
/>

Line highlights #

Pass 1-based line numbers to highlightLines to draw attention to specific lines.

<CodeBlockStyled
  code={multiLineCode}
  language="tsx"
  highlightLines={[3, 7]}
  showLineNumbers
/>

With title #

<CodeBlockStyled
  code={code}
  language="tsx"
  title="src/components/Button.tsx"
  showLanguageLabel
/>

Headless usage #

import { useCodeBlock } from "@mshafiqyajid/react-code-block";

function MyCodeBlock({ code, language = "text" }) {
  const { rootProps, copyProps, isCopied } = useCodeBlock({
    code,
    language,
    copyLabel: "Copy",
    copiedLabel: "Copied!",
    onCopy: () => console.log("copied!"),
  });

  return (
    <div {...rootProps}>
      <pre><code>{code}</code></pre>
      {isCopied ? (
        <button {...copyProps}>Copied!</button>
      ) : (
        <button {...copyProps}>Copy</button>
      )}
    </div>
  );
}

Theme #

theme="auto" (default) reads the data-theme attribute on document.documentElement and maps "dark" β†’ Shiki's github-dark-default theme, and "light" (or no attribute) β†’ github-light. A MutationObserver watches for attribute changes so the highlighted output updates when you toggle the site theme.

Pass any Shiki theme name as the theme prop to pin a specific theme:

<CodeBlockStyled code={code} language="ts" theme="dracula" />

Props #

PropTypeDefaultDescription
codestringβ€”The code string to display
languagestring"text"Language identifier passed to Shiki
theme"auto" | string"auto"Shiki theme; "auto" follows [data-theme]
showLineNumbersbooleanfalsePrepend line numbers to each line
highlightLinesnumber[][]1-based line numbers to highlight
diffbooleanfalseParse +/- prefix as add/remove diff lines
showCopybooleantrueShow the copy button
copyLabelstring"Copy"Copy button label
copiedLabelstring"Copied!"Label after successful copy (2 s)
onCopy() => voidβ€”Called on successful copy
titlestringβ€”Filename or label shown in the header bar
showLanguageLabelbooleantrueShow the language identifier in the header
maxHeightstring | numberβ€”Max height of the scrollable code area
wrapbooleanfalseWrap long lines instead of scrolling
size"sm" | "md" | "lg""md"Font size and padding scale
radius"none" | "sm" | "md" | "lg""md"Border radius
maxLinesnumberβ€”Clamp visible lines; shows fade and expand button
expandablebooleantrueShow expand/collapse button when maxLines is set
terminalbooleanfalseRender a macOS-style terminal header bar
focusLinesnumber[]β€”1-based lines to focus; others are dimmed to 30%
badgestringβ€”Pill badge shown in header (e.g. "Live", "Beta")
classNamestringβ€”Extra class on the root element
styleCSSPropertiesβ€”Inline style on the root element
refRef<HTMLDivElement>β€”Forwarded to the root div

Data attributes #

AttributeValues
data-languageThe current language string
data-theme"light" | "dark"
data-diff"true" when diff is enabled
data-wrap"true" when wrap is enabled
data-has-title"true" when a title is set
data-size"sm" | "md" | "lg"
data-radius"none" | "sm" | "md" | "lg"
data-collapsed"true" when maxLines is set and block is collapsed
data-terminal"true" when terminal mode is active
data-has-focus"true" when focusLines is non-empty
data-has-badge"true" when a badge is set
data-focused"true" on lines in focusLines
data-unfocused"true" on lines not in focusLines

CSS variables #

:root {
  --rcblk-bg: #f6f8fa;
  --rcblk-header-bg: #eaeef2;
  --rcblk-border: #d0d7de;
  --rcblk-fg: #24292e;
  --rcblk-line-number-fg: #8b949e;
  --rcblk-line-highlight-bg: rgba(255, 213, 0, 0.15);
  --rcblk-line-highlight-border: #d4a017;
  --rcblk-diff-add-bg: rgba(22, 163, 74, 0.12);
  --rcblk-diff-add-border: #16a34a;
  --rcblk-diff-remove-bg: rgba(220, 38, 38, 0.12);
  --rcblk-diff-remove-border: #dc2626;
  --rcblk-copy-fg: #57606a;
  --rcblk-copy-bg-hover: rgba(0, 0, 0, 0.06);
  --rcblk-radius: 8px;
  --rcblk-font-size-sm: 0.75rem;
  --rcblk-font-size-md: 0.8125rem;
  --rcblk-font-size-lg: 0.9rem;
}
Edit this page on GitHub