UIPackage

Code Block

React data-display
Edit on GitHub

Read-only code preview with a header, optional filename, copy button, and `<pre>`-rendered content. Use for installation snippets, API examples, and snippets you want users to copy verbatim.

Also available for Vue ->

Installation

$ npx shadcn@latest add https://react.uipkge.dev/r/react/code-block.json

Or with the named registry: npx shadcn@latest add @uipkge-react/code-block

Examples

Props

Name Type / Values Default Required
code

Source code to display.

string required
language

Language label shown in the header.

string optional
showLineNumbers

Render line numbers in the gutter.

boolean optional
maxHeight

Maximum height of the code body before scrolling kicks in.

string optional
defaultExpanded

Render expanded on first paint. Default true.

boolean optional
showHeader

Show the language label / copy / collapse header.

boolean optional
className string optional

Dependencies

Files (2)

  • components/ui/code-block/CodeBlock.tsx 4 kB
    'use client'
    
    import * as React from 'react'
    import { Check, ChevronDown, ChevronUp, Copy } from 'lucide-react'
    import { cn } from '@/lib/utils'
    import { Button } from '@/components/ui/button'
    
    export interface CodeBlockProps {
      /** Source code to display. */
      code: string
      /** Language label shown in the header. */
      language?: string
      /** Render line numbers in the gutter. */
      showLineNumbers?: boolean
      /** Maximum height of the code body before scrolling kicks in. */
      maxHeight?: string
      /** Render expanded on first paint. Default true. */
      defaultExpanded?: boolean
      /** Show the language label / copy / collapse header. */
      showHeader?: boolean
      className?: string
    }
    
    function CodeBlock({
      code,
      language = 'tsx',
      showLineNumbers = true,
      maxHeight = '400px',
      defaultExpanded = true,
      showHeader = true,
      className,
    }: CodeBlockProps) {
      const [isExpanded, setIsExpanded] = React.useState(defaultExpanded)
      const [copied, setCopied] = React.useState(false)
    
      const lines = React.useMemo(() => code.replace(/\s+$/, '').split('\n'), [code])
    
      async function copyToClipboard() {
        try {
          await navigator.clipboard.writeText(code)
          setCopied(true)
          setTimeout(() => setCopied(false), 1600)
        } catch (e) {
          console.warn('Clipboard write failed', e)
        }
      }
    
      return (
        <div
          data-uipkge=""
          data-slot="code-block"
          className={cn('group border-border bg-muted/20 relative overflow-hidden rounded-lg border', className)}
        >
          {/* Header */}
          {showHeader && (
            <div className="border-border bg-muted/40 flex items-center justify-between gap-2 border-b px-3 py-2">
              <span className="text-muted-foreground text-xs font-medium tracking-wide uppercase">{language}</span>
              <div className="flex items-center gap-1">
                <Button variant="ghost" size="xs" className="h-7 gap-1.5 px-2" onClick={copyToClipboard}>
                  {copied ? (
                    <Check className="text-success size-3" aria-hidden="true" />
                  ) : (
                    <Copy className="size-3" aria-hidden="true" />
                  )}
                  <span className="text-xs">{copied ? 'Copied' : 'Copy'}</span>
                </Button>
                <Button
                  variant="ghost"
                  size="xs"
                  className="h-7 gap-1.5 px-2"
                  onClick={() => setIsExpanded((v) => !v)}
                >
                  {isExpanded ? (
                    <ChevronUp className="size-3" aria-hidden="true" />
                  ) : (
                    <ChevronDown className="size-3" aria-hidden="true" />
                  )}
                  <span className="text-xs">{isExpanded ? 'Hide' : 'Show'} code</span>
                </Button>
              </div>
            </div>
          )}
    
          {/* Code body. We render line-by-line so the gutter and the code stay in
              sync without measuring text height. The renderer is intentionally
              plain: code is displayed as-is and the consumer can layer their own
              syntax highlighter (shiki, prism, hljs) on top by replacing this
              file or by wrapping it in their own component. */}
          {isExpanded && (
            <div
              className="bg-background/40 overflow-auto font-mono text-sm leading-relaxed"
              style={{ maxHeight }}
            >
              <pre className="m-0 flex">
                {showLineNumbers && (
                  <div
                    aria-hidden="true"
                    className="text-muted-foreground/60 sticky left-0 select-none border-r border-border/60 bg-muted/30 px-3 py-3 text-right tabular-nums"
                  >
                    {lines.map((_, i) => (
                      <div key={i}>{i + 1}</div>
                    ))}
                  </div>
                )}
                <code className="block flex-1 px-4 py-3">
                  {lines.map((line, i) => (
                    <React.Fragment key={i}>
                      <span>{line || ' '}</span>
                      {i < lines.length - 1 && <br />}
                    </React.Fragment>
                  ))}
                </code>
              </pre>
            </div>
          )}
        </div>
      )
    }
    
    export { CodeBlock }
  • components/ui/code-block/index.ts 0.1 kB
    export { CodeBlock, type CodeBlockProps } from './CodeBlock'

Raw manifest: https://react.uipkge.dev/r/react/code-block.json