Code Block
A syntax-highlighted code block with copy functionality and language support.
example.tsx
import { Button } from "@/components/ui/button"
export function Example() { return <Button>Click me</Button>}
tsx
import { CodeBlock } from "@/components/ui/code-block"
export function CodeBlockDemo() { return ( <CodeBlock language="tsx" filename="example.tsx" code={`import { Button } from "@/components/ui/button"
export function Example() { return <Button>Click me</Button>}`} /> )}
Installation
CLI
bash
npx fivui add code-block
Manual
Copy and paste the following code into your project.
bash
npm install prism-react-renderer lucide-react
components/ui/code-block.tsx
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184"use client"
import * as React from "react"import { Check, Copy, FileText, Terminal } from "lucide-react"import { Highlight, themes } from "prism-react-renderer"import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"import { Button } from "@/components/ui/button"
const codeBlockVariants = cva( "relative overflow-hidden rounded-lg border bg-muted/50", { variants: { variant: { default: "border-border", ghost: "border-transparent bg-transparent", }, }, defaultVariants: { variant: "default", }, })
interface CodeBlockProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof codeBlockVariants> { code: string language?: string filename?: string showLineNumbers?: boolean showCopy?: boolean maxHeight?: string theme?: "light" | "dark"}
// Language icons mappingconst languageIcons: Record<string, React.ReactNode> = { javascript: <FileText className="h-4 w-4 text-yellow-500" />, typescript: <FileText className="h-4 w-4 text-blue-500" />, jsx: <FileText className="h-4 w-4 text-cyan-500" />, tsx: <FileText className="h-4 w-4 text-blue-600" />, python: <FileText className="h-4 w-4 text-green-500" />, bash: <Terminal className="h-4 w-4 text-gray-500" />, shell: <Terminal className="h-4 w-4 text-gray-500" />, json: <FileText className="h-4 w-4 text-orange-500" />, css: <FileText className="h-4 w-4 text-purple-500" />, html: <FileText className="h-4 w-4 text-red-500" />, sql: <FileText className="h-4 w-4 text-blue-400" />, yaml: <FileText className="h-4 w-4 text-red-400" />, xml: <FileText className="h-4 w-4 text-orange-400" />, default: <FileText className="h-4 w-4 text-muted-foreground" />,}
const CodeBlock = React.forwardRef<HTMLDivElement, CodeBlockProps>( ({ className, variant, code, language = "text", filename, showLineNumbers = false, showCopy = true, maxHeight = "400px", theme = "dark", ...props }, ref) => { const [copied, setCopied] = React.useState(false)
const copyToClipboard = async () => { try { await navigator.clipboard.writeText(code) setCopied(true) setTimeout(() => setCopied(false), 2000) } catch (err) { console.error("Failed to copy code:", err) } }
const languageIcon = languageIcons[language.toLowerCase()] || languageIcons.default
return ( <div ref={ref} className={cn(codeBlockVariants({ variant, className }))} {...props} > {/* Header */} {(filename || language || showCopy) && ( <div className="flex items-center justify-between border-b bg-muted/30 px-4 py-2"> <div className="flex items-center gap-2"> {languageIcon} {filename && ( <span className="text-sm font-medium text-foreground"> {filename} </span> )} {!filename && language && language !== "text" && ( <span className="text-sm text-muted-foreground"> {language} </span> )} </div> {showCopy && ( <Button variant="ghost" size="sm" className="h-8 w-8 p-0" onClick={copyToClipboard} > {copied ? ( <Check className="h-4 w-4" /> ) : ( <Copy className="h-4 w-4" /> )} <span className="sr-only">Copy code</span> </Button> )} </div> )}
{/* Code Content */} <div className="relative"> <Highlight theme={theme === "dark" ? themes.vsDark : themes.vsLight} code={code.trim()} language={language as any} > {({ className: highlightClassName, style, tokens, getLineProps, getTokenProps }) => ( <pre className={cn( "overflow-auto p-4 text-sm", highlightClassName )} style={{ ...style, maxHeight, backgroundColor: 'transparent' }} > <code className="relative block"> {showLineNumbers ? ( <div className="flex"> {/* Line Numbers */} <div className="mr-4 select-none border-r pr-4 text-muted-foreground"> {tokens.map((_, index) => ( <div key={index} className="text-right leading-6"> {index + 1} </div> ))} </div> {/* Code Lines */} <div className="flex-1"> {tokens.map((line, i) => ( <div key={i} {...getLineProps({ line })} className="leading-6"> {line.map((token, key) => ( <span key={key} {...getTokenProps({ token })} /> ))} </div> ))} </div> </div> ) : ( tokens.map((line, i) => ( <div key={i} {...getLineProps({ line })} className="leading-6"> {line.map((token, key) => ( <span key={key} {...getTokenProps({ token })} /> ))} </div> )) )} </code> </pre> )} </Highlight> </div> </div> ) })CodeBlock.displayName = "CodeBlock"
export { CodeBlock, codeBlockVariants }
Usage
tsx
import { CodeBlock } from "@/components/ui/code-block"
tsx
<CodeBlock language="tsx" code={`console.log("Hello, World!")`}/>
Examples
Basic
javascript
function greet(name) { return `Hello, ${name}!`;}
console.log(greet("World"));
tsx
<CodeBlock language="javascript" code={`function greet(name) { return \`Hello, \${name}!\`;}
console.log(greet("World"));`}/>
With Filename
components/ui/button.tsx
import { Button } from "@/components/ui/button"
export function MyButton() { return <Button variant="outline">Click me</Button>}
tsx
<CodeBlock language="tsx" filename="components/ui/button.tsx" code={`import { Button } from "@/components/ui/button"
export function MyButton() { return <Button variant="outline">Click me</Button>}`}/>
With Line Numbers
app.py
12345678def fibonacci(n): if n <= 1: return n return fibonacci(n-1) + fibonacci(n-2)
# Generate first 10 fibonacci numbersfor i in range(10): print(f"F({i}) = {fibonacci(i)}")
tsx
<CodeBlock language="python" filename="app.py" showLineNumbers code={`def fibonacci(n): if n <= 1: return n return fibonacci(n-1) + fibonacci(n-2)
# Generate first 10 fibonacci numbersfor i in range(10): print(f"F({i}) = {fibonacci(i)}")`}/>
Ghost Variant
bash
npm install @radix-ui/react-slotnpm install class-variance-authoritynpm install clsx tailwind-merge
tsx
<CodeBlock variant="ghost" language="bash" code={`npm install @radix-ui/react-slotnpm install class-variance-authoritynpm install clsx tailwind-merge`}/>
Without Copy Button
package.json
{ "name": "my-app", "version": "1.0.0", "dependencies": { "react": "^18.0.0", "next": "^14.0.0" }}
tsx
<CodeBlock language="json" filename="package.json" showCopy={false} code={`{ "name": "my-app", "version": "1.0.0", "dependencies": { "react": "^18.0.0", "next": "^14.0.0" }}`}/>
Custom Max Height
styles.css
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556/* Global Styles */* { margin: 0; padding: 0; box-sizing: border-box;}
body { font-family: 'Inter', sans-serif; line-height: 1.6; color: #333;}
.container { max-width: 1200px; margin: 0 auto; padding: 0 20px;}
.header { background-color: #fff; box-shadow: 0 2px 4px rgba(0,0,0,0.1); position: sticky; top: 0; z-index: 100;}
.nav { display: flex; justify-content: space-between; align-items: center; padding: 1rem 0;}
.logo { font-size: 1.5rem; font-weight: bold; color: #2563eb;}
.nav-links { display: flex; list-style: none; gap: 2rem;}
.nav-link { text-decoration: none; color: #6b7280; font-weight: 500; transition: color 0.2s;}
.nav-link:hover { color: #2563eb;}
tsx
<CodeBlock language="css" filename="styles.css" maxHeight="200px" showLineNumbers code={`/* Long CSS content... */`}/>
Language Support
The CodeBlock component supports syntax highlighting for many languages:
query.sql
SELECT users.name, COUNT(orders.id) as order_countFROM usersLEFT JOIN orders ON users.id = orders.user_idWHERE users.created_at > '2024-01-01'GROUP BY users.id, users.nameORDER BY order_count DESC;
docker-compose.yml
version: '3.8'services: app: build: . ports: - "3000:3000" environment: - NODE_ENV=production volumes: - ./src:/app/src depends_on: - db db: image: postgres:15 environment: POSTGRES_DB: myapp POSTGRES_PASSWORD: secret
API Reference
Prop | Type | Default | Description |
---|---|---|---|
code | string | - | The code content to display |
language | string | "text" | Programming language for syntax highlighting |
filename | string | - | Optional filename to display in header |
showLineNumbers | boolean | false | Whether to show line numbers |
showCopy | boolean | true | Whether to show copy button |
maxHeight | string | "400px" | Maximum height of the code block |
variant | "default" | "ghost" | "default" | Visual variant of the code block |
theme | "light" | "dark" | "dark" | Syntax highlighting theme |
CodeBlock also accepts all standard HTML div attributes. Built with prism-react-renderer
for syntax highlighting.