Skip to content
    Style: Wireframe

    Components · Display

    Code block

    Code with a filename bar and Copy, in a focusable scroll box that shades its edges.

    0.29% of the 2,073 sites in The UI Index have a code block (6 sites).

    Used in UI Pro: Hero, Feature tabs

    astro.config.mjs
    // astro.config.mjs
    import { defineConfig } from 'astro/config';
    
    export default defineConfig({
      site: 'https://northwind.example',
      trailingSlash: 'always',
    });
    Terminal
    npx shadcn@latest add https://www.themes.dev/ui/r/code-block.json

    Both previews render the same markup; the HTML tab below is that markup without a framework.

    Install

    React
    npx shadcn@latest add https://www.themes.dev/ui/r/code-block.json
    Astro
    npx shadcn@latest add https://www.themes.dev/ui/r/astro/code-block.json

    Or copy the files below into your project. They need clsx, tailwind-merge, and the themes.dev UI theme. The HTML needs only the compiled CSS and the script.

    code-block.client.ts
    // Code block copy for Astro, HTML and React: [data-code-copy] copies its block's code and says "Copied" for a
    // moment (also announced: the button's label changes, and a polite live region in the bar repeats it).
    declare global {
      interface Window {
        __caseroomCode?: boolean;
      }
    }
    
    if (typeof window !== 'undefined' && !window.__caseroomCode) {
      window.__caseroomCode = true;
      document.addEventListener('click', async (e) => {
        const b = (e.target as Element).closest<HTMLButtonElement>('[data-code-copy]');
        if (!b) return;
        const code = b.closest('[data-code-block]')?.querySelector('code')?.textContent ?? '';
        try {
          await navigator.clipboard.writeText(code);
        } catch {
          return;
        }
        const live = b.closest('[data-code-block]')?.querySelector('[data-code-live]');
        b.textContent = 'Copied';
        if (live) live.textContent = 'Copied to the clipboard';
        setTimeout(() => {
          b.textContent = 'Copy';
          if (live) live.textContent = '';
        }, 1600);
      });
    }
    
    export {};
    code-block.tsx
    import { cn } from '@/lib/utils';
    import './code-block.client';
    import { codeBlock as c } from './code-block.variants';
    
    export interface CodeBlockProps {
      code: string;
      /** Shown in the bar and names the scroll box, e.g. "astro.config.mjs" or "Terminal". */
      filename: string;
      id: string;
      className?: string;
    }
    
    /** Code with a filename bar and a Copy button, in a focusable scroll box. */
    export function CodeBlock({ code, filename, id, className }: CodeBlockProps) {
      return (
        <figure data-code-block="" className={cn(c.root, className)}>
          <figcaption id={`${id}-name`} className={c.bar}>
            {filename}
            <span data-code-live="" className="sr-only" aria-live="polite"></span>
            <button type="button" data-code-copy="" className={c.copy}>Copy</button>
          </figcaption>
          <pre tabIndex={0} aria-labelledby={`${id}-name`} className={c.pre}><code>{code}</code></pre>
        </figure>
      );
    }
    code-block.variants.ts
    /** A code block: a filename (or language) bar with a Copy button, then the code: lines wrap, a long block scrolls
     *  down. Black and white, monospace, no colours to keep in step with a theme. The scroll box is
     *  focusable and named by the filename, so keyboard users can scroll it. */
    export const codeBlock = {
      root: 'w-full overflow-hidden rounded-lg border',
      bar: 'flex items-center justify-between gap-3 border-b bg-muted/50 px-4 py-2 font-mono text-xs text-muted-foreground',
      copy: 'inline-flex h-7 cursor-pointer items-center rounded-md border bg-background px-2.5 font-sans text-xs font-medium text-foreground transition-[background-color,transform] hover:bg-accent active:scale-[0.97] active:bg-pressed',
      // Lines wrap (a snippet reads better than a sideways scroll); long blocks scroll down.
      pre: 'm-0 max-h-[28rem] overflow-auto rounded-b-lg p-4 focus-visible:outline-offset-[-2px] font-mono text-[13px] leading-relaxed whitespace-pre-wrap [overflow-wrap:anywhere] [background:linear-gradient(to_right,var(--background)_50%,transparent)_left/2rem_100%_no-repeat_local,linear-gradient(to_left,var(--background)_50%,transparent)_right/2rem_100%_no-repeat_local,radial-gradient(farthest-side_at_0_50%,var(--tabs-shade,rgb(0_0_0/0.2)),transparent)_left/0.875rem_100%_no-repeat_scroll,radial-gradient(farthest-side_at_100%_50%,var(--tabs-shade,rgb(0_0_0/0.2)),transparent)_right/0.875rem_100%_no-repeat_scroll,var(--background)]',
    };