Code Block

PreviousNext

A syntax-highlighting component capable of parsing Markdown fences, handling package manager tabs, and supporting adaptive themes

# Hello World in Python
def main():
print("Hello, World!")
if __name__ == "__main__":
main()

Installation

pnpm dlx shadcn@latest add https://deltacomponents.dev/r/code-block.json

Configuration

Global CSS & Utilities

This component relies on specific CSS variables for its "surface" background logic (when not using the syntax theme's background) and utility classes for scrollbar management.

Add the following to your global CSS:

globals.css
@layer base {
  :root {
    --surface: #fafafa; /* Light mode fallback */
  }
 
  .dark {
    --surface: #171717; /* Dark mode fallback */
  }
}
 
@layer utilities {
  /* Hides scrollbars for a cleaner aesthetic when interactions aren't required */
  .no-scrollbar::-webkit-scrollbar {
    display: none;
  }
 
  .no-scrollbar {
    -ms-overflow-style: none; /* IE and Edge */
    scrollbar-width: none; /* Firefox */
  }
}

Usage

Basic Code Rendering

Pass a raw string to the code prop. You can optionally add a filename to render a file header with an automatically resolved icon.

import { CodeBlock } from "@/components/ui/code-block"
 
export default function Example() {
  return (
    <CodeBlock
      code={`console.log("Hello World")`}
      language="typescript"
      filename="app.ts"
      showLineNumbers
    />
  )
}

Markdown String Parsing

The component includes a regex parser (parseMarkdownCodeBlock) that extracts the language and code from standard Markdown fence syntax. This is particularly useful when rendering content from an LLM or a CMS that returns raw markdown strings.

// The language "python" is automatically extracted from the string
<CodeBlock
  code={`\`\`\`python
def hello():
    print("world")
\`\`\``}
/>

Examples

Markdown Integration

If your code string contains a markdown code fence (e.g., typescript\n...\n), the component can parse the language and content automatically.

greet.py
1def greet(name: str) -> str:
2 """Return a greeting message."""
3 return f"Hello, {name}!"
4
5if __name__ == "__main__":
6 message = greet("World")
7 print(message)

MDX Transformer (Contentlayer/Fumadocs)

For documentation sites (like those using Contentlayer or Fumadocs), you can transform all native markdown <pre> blocks into the enhanced CodeBlock component.

mdx-components.tsx
import type { MDXComponents } from "mdx/types"
 
import { CodeBlock } from "@/components/ui/code-block"
 
export function useMDXComponents(components: MDXComponents): MDXComponents {
  return {
    ...components,
    pre: ({ children, ...props }) => {
      // Basic logic to extract code and language from markdown structure
      const code =
        typeof children === "object" && "props" in children
          ? children.props.children
          : ""
 
      const className =
        typeof children === "object" && "props" in children
          ? children.props.className || ""
          : ""
      const language = className.replace(/language-/, "")
 
      return (
        <CodeBlock
          code={code}
          language={language || "typescript"}
          showLineNumbers={true}
        />
      )
    },
  }
}

Theme Background Toggle

Use useThemeBackground={true} to apply the specific background color defined within your Prism theme, overriding the default adaptive --surface variable.

fibonacci.py
1def fibonacci(n: int) -> list[int]:
2 """Generate Fibonacci sequence up to n numbers. This is a long comment to test horizontal scrolling behavior in the code block component."""
3 if n <= 0:
4 return []
5 elif n == 1:
6 return [0]
7
8 sequence = [0, 1]
9 while len(sequence) < n:
10 sequence.append(sequence[-1] + sequence[-2]) # Add the sum of the last two numbers to the sequence list for Fibonacci calculation
11
12 return sequence
13
14def fibonacci_recursive(n: int, memo: dict[int, int] | None = None) -> int:
15 """Calculate the nth Fibonacci number using memoization for better performance in recursive calls."""
16 if memo is None:
17 memo = {}
18 if n in memo:
19 return memo[n]
20 if n <= 1:
21 return n
22 memo[n] = fibonacci_recursive(n - 1, memo) + fibonacci_recursive(n - 2, memo)
23 return memo[n]
24
25if __name__ == "__main__":
26 result = fibonacci(10)
27 print(f"Fibonacci sequence: {result}") # Output: Fibonacci sequence: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] for the first 10 numbers

JSON Syntax Highlighting

Display formatted JSON data with syntax highlighting:

Scrollbar: Visible
user-session.json
1{
2 "user": {
3 "id": "usr_2nQz7kX9pLm4",
4 "email": "sarah.chen@acme.com",
5 "name": "Sarah Chen",
6 "role": "senior_engineer",
7 "permissions": ["read", "write", "deploy"],
8 "metadata": {
9 "department": "platform",
10 "team": "infrastructure",
11 "timezone": "America/Los_Angeles"
12 }
13 },
14 "session": {
15 "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
16 "expiresAt": "2024-12-31T23:59:59Z",
17 "refreshToken": "rt_9xKmP3vN8qL2",
18 "scopes": ["api:read", "api:write", "admin:users"]
19 },
20 "preferences": {
21 "theme": "dark",
22 "notifications": {
23 "email": true,
24 "slack": true,
25 "digest": "daily"
26 },
27 "editor": {
28 "tabSize": 2,
29 "formatOnSave": true
30 }
31 }
32}

Expandable Code Blocks

Handle long code snippets with expand/collapse functionality:

data-table.tsx
|
1import * as React from "react"
2import { useEffect, useState, useCallback, useMemo } from "react"
3import { cn } from "@/lib/utils"
4
5interface DataItem {
6 id: string
7 name: string
8 description: string
9 status: "active" | "inactive" | "pending"
10 createdAt: Date
11 updatedAt: Date
12 metadata: Record<string, unknown>
13}
14
15interface DataTableProps {
16 data: DataItem[]
17 onSelect?: (item: DataItem) => void
18 onDelete?: (id: string) => void
19 onEdit?: (item: DataItem) => void
20 className?: string
21 loading?: boolean
22 error?: Error | null
23}
24
25export function DataTable({
26 data,
27 onSelect,
28 onDelete,
29 onEdit,
30 className,
31 loading = false,
32 error = null,
33}: DataTableProps) {
34 const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
35 const [sortColumn, setSortColumn] = useState<keyof DataItem>("name")
36 const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc")
37 const [filterText, setFilterText] = useState("")
38
39 const filteredData = useMemo(() => {
40 return data
41 .filter((item) =>
42 item.name.toLowerCase().includes(filterText.toLowerCase()) ||
43 item.description.toLowerCase().includes(filterText.toLowerCase())
44 )
45 .sort((a, b) => {
46 const aVal = a[sortColumn]
47 const bVal = b[sortColumn]
48 const modifier = sortDirection === "asc" ? 1 : -1
49 if (aVal < bVal) return -1 * modifier
50 if (aVal > bVal) return 1 * modifier
51 return 0
52 })
53 }, [data, filterText, sortColumn, sortDirection])
54
55 const handleSelectAll = useCallback(() => {
56 if (selectedIds.size === filteredData.length) {
57 setSelectedIds(new Set())
58 } else {
59 setSelectedIds(new Set(filteredData.map((item) => item.id)))
60 }
61 }, [filteredData, selectedIds.size])
62
63 const handleSelectItem = useCallback((id: string) => {
64 setSelectedIds((prev) => {
65 const next = new Set(prev)
66 if (next.has(id)) {
67 next.delete(id)
68 } else {
69 next.add(id)
70 }
71 return next
72 })
73 }, [])
74
75 if (loading) {
76 return <div className="flex items-center justify-center p-8">Loading...</div>
77 }
78
79 if (error) {
80 return <div className="text-red-500 p-4">Error: {error.message}</div>
81 }
82
83 return (
84 <div className={cn("rounded-lg border", className)}>
85 <div className="p-4 border-b">
86 <input
87 type="text"
88 placeholder="Filter items..."
89 value={filterText}
90 onChange={(e) => setFilterText(e.target.value)}
91 className="w-full px-3 py-2 border rounded-md"
92 />
93 </div>
94 <table className="w-full">
95 <thead>
96 <tr className="border-b bg-muted/50">
97 <th className="p-3 text-left">
98 <input
99 type="checkbox"
100 checked={selectedIds.size === filteredData.length}
101 onChange={handleSelectAll}
102 />
103 </th>
104 <th className="p-3 text-left font-medium">Name</th>
105 <th className="p-3 text-left font-medium">Status</th>
106 <th className="p-3 text-left font-medium">Actions</th>
107 </tr>
108 </thead>
109 <tbody>
110 {filteredData.map((item) => (
111 <tr key={item.id} className="border-b hover:bg-muted/30">
112 <td className="p-3">
113 <input
114 type="checkbox"
115 checked={selectedIds.has(item.id)}
116 onChange={() => handleSelectItem(item.id)}
117 />
118 </td>
119 <td className="p-3">{item.name}</td>
120 <td className="p-3">
121 <span className={`px-2 py-1 rounded-full text-xs ${
122 item.status === "active" ? "bg-green-100 text-green-800" :
123 item.status === "inactive" ? "bg-gray-100 text-gray-800" :
124 "bg-yellow-100 text-yellow-800"
125 }`}>
126 {item.status}
127 </span>
128 </td>
129 <td className="p-3 flex gap-2">
130 <button onClick={() => onEdit?.(item)}>Edit</button>
131 <button onClick={() => onDelete?.(item.id)}>Delete</button>
132 </td>
133 </tr>
134 ))}
135 </tbody>
136 </table>
137 </div>
138 )
139}

Interactive Theme Demo

1package main
2
3import "fmt"
4
5func main() {
6 fmt.Println("Hello, World!")
7}

Cycle through Prism themes with background toggle to compare bg-surface vs. useThemeBackground behavior.

NPX Commands

Handle package manager commands in markdown:

npx shadcn@latest add button

Package Manager Commands

npm install @radix-ui/react-dropdown-menu

API Reference

Props

PropTypeDefaultDescription
codestringThe code content. Can be a raw string or a markdown fenced string.
languagestring"typescript"The language for syntax highlighting. Ignored if markdown fence detects a language.
filenamestringIf provided, renders a header with a file icon.
showLineNumbersbooleantrueToggles the line number gutter.
expandablebooleanfalseEnables the collapse/expand functionality.
defaultExpandedbooleanfalseThe initial state of the expandable block.
collapsedHeightstring"12rem"CSS height value for the collapsed state.
themePrismThemeundefinedA static Prism theme object.
adaptiveTheme{ light, dark }Object containing themes for light and dark modes.
useThemeBackgroundbooleanfalseIf true, applies the theme's background color. If false, uses var(--surface).
npmstringCommand for npm tab.
yarnstringCommand for yarn tab.
pnpmstringCommand for pnpm tab.
bunstringCommand for bun tab.
defaultPackageManager"npm" | "yarn" | "pnpm" | "bun""npm"The default active tab.

Background Behavior

  • Default (useThemeBackground={false}): Uses bg-surface CSS variable
  • Theme Background (useThemeBackground={true}): Uses theme.plain.backgroundColor from Prism theme
  • Requires --surface CSS variable definition for default mode

Types

type PackageManager = "npm" | "yarn" | "pnpm" | "bun"
 
interface AdaptiveTheme {
  light: PrismTheme
  dark: PrismTheme
}

Theme Structure

Custom themes follow the Prism theme structure:

const customTheme: PrismTheme = {
  plain: {
    color: "#e6e6fa",
    backgroundColor: "#1a1a2e",
  },
  styles: [
    {
      types: ["comment"],
      style: {
        color: "#6a7b9a",
        fontStyle: "italic",
      },
    },
    // ... more styles
  ],
}