41 / Code
Code Block
A code panel with copy, optional line numbers, highlighted lines, and a collapse for anything long.
import { rateLimit } from "./limiter" export async function POST(request: Request) { const allowed = await rateLimit(request) if (!allowed) { return Response.json({ error: "Slow down" }, { status: 429 }) } const body = await request.json() return Response.json({ ok: true, body })}"use client"
import { DemoVariants } from "@/components/demos/demo-variants"
import { CodeBlock } from "mischief-ui/code-block"
const source = `import { rateLimit } from "./limiter"
export async function POST(request: Request) {
const allowed = await rateLimit(request)
if (!allowed) {
return Response.json({ error: "Slow down" }, { status: 429 })
}
const body = await request.json()
return Response.json({ ok: true, body })
}`
export function CodeBlockDemo() {
return (
<DemoVariants
label="Code block"
variants={[
{
id: "plain",
label: "Plain",
render: () => <CodeBlock code={source} filename="route.ts" />,
},
{
id: "numbered",
label: "Numbered",
render: () => (
<CodeBlock
code={source}
filename="route.ts"
showLineNumbers
highlightLines={[6, 7]}
/>
),
},
{
id: "collapsed",
label: "Collapsed",
render: () => (
<CodeBlock
code={source}
filename="route.ts"
showLineNumbers
maxLines={5}
wrappable
/>
),
},
]}
/>
)
}Installation
Copy the source into your project, or keep it behind a package.
npx shadcn@latest add Tinkerers-Labs/mischief-ui/code-blockimport { CodeBlock } from "mischief-ui/code-block"Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.
"use client" import * as React from "react"import { Check, ChevronDown, Clipboard, WrapText } from "lucide-react" import { cn } from "@/lib/utils" export type CodeBlockProps = Omit< React.HTMLAttributes<HTMLDivElement>, "children"> & { code: string /** Shown in the header, and used as the download-free label for the block. */ filename?: stringUsage
export function Snippet() {
return (
<CodeBlock
code={source}
filename="server.ts"
showLineNumbers
highlightLines={[4, 5]}
maxLines={12}
/>
)
}On syntax highlighting
There is none, and that is deliberate. Every highlighter worth using is larger than this entire library, and one baked in would be paid for by everyone who only wanted a copy button. The component is a place to put code, not an opinion about how code should be coloured.
When you do want it, highlight on the server and pass the result as children of your own pre, or reach for shiki or Prism directly. The language prop is a label rather than an instruction -- nothing reads it but the header.
Long code
maxLines collapses anything past a point behind a toggle that says how much is hidden, and the copy button always copies the whole source rather than the visible part. Combine it with wrappable when lines are long as well as many, so the reader can choose between scrolling sideways and reading wrapped.
<CodeBlock
code={source}
filename="server.ts"
showLineNumbers
highlightLines={[12, 13]}
maxLines={20}
wrappable
/>API
codestringThe source to render. A single trailing newline is dropped.filenamestringShown in the header, and preferred over language.languagestringA short label such as tsx, used when there is no filename.showLineNumbersbooleanAdds a gutter sized to the highest line number.highlightLinesnumber[]One-based lines to mark as the interesting ones.maxLinesnumberCollapses anything past this many lines behind a toggle.wrap, wrappableboolean, booleanWrap long lines, and offer a control that overrides it.copyablebooleanShows the copy control. Defaults to true.actionsReactNodeExtra controls placed in the header.Accessibility
The code region is focusable so it can be scrolled from the keyboard. Copying announces itself through a polite live region, and the copy control renames itself once it succeeds. A clipboard that refuses -- denied permission, an insecure context, a sandboxed frame -- is caught and reported rather than leaving the control looking like it worked. Line numbers and the diff-style gutter are aria-hidden, so a screen reader reads the source rather than the decoration. There is no syntax highlighting and no highlighting dependency.