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,062 sites in The UI Index have a code block (6 sites).
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
site: 'https://northwind.example',
trailingSlash: 'always',
});npx shadcn@latest add https://www.themes.dev/caseroom/r/code-block.json// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
site: 'https://northwind.example',
trailingSlash: 'always',
});npx shadcn@latest add https://www.themes.dev/caseroom/r/code-block.jsonBoth 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/ caseroom/ r/ code-block.json - Astro
- npx shadcn@
latest add https:// www.themes.dev/ caseroom/ r/ astro/ code-block.json
Or copy the files below into your project. They need clsx, tailwind-merge, and the Caseroom theme. The HTML needs only the compiled CSS and the script.
// 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 {};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>
);
}/** 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)]',
};---
import { cn } from '@/lib/utils';
import { codeBlock as c } from './code-block.variants';
interface Props {
code: string;
/** Shown in the bar and names the scroll box, e.g. "astro.config.mjs" or "Terminal". */
filename: string;
id: string;
class?: string;
}
const { code, filename, id, class: className } = Astro.props;
---
<figure data-code-block class={cn(c.root, className)}>
<figcaption id={`${id}-name`} class={c.bar}>
{filename}
<span data-code-live class="sr-only" aria-live="polite"></span>
<button type="button" data-code-copy class={c.copy}>Copy</button>
</figcaption>
<pre tabindex="0" aria-labelledby={`${id}-name`} class={c.pre}><code>{code}</code></pre>
</figure>
<script>
import './code-block.client';
</script>// 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 {};/** 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)]',
};<div class="grid w-full max-w-xl gap-4">
<figure data-code-block class="w-full overflow-hidden rounded-lg border">
<figcaption id="ex-code-name" class="flex items-center justify-between gap-3 border-b bg-muted/50 px-4 py-2 font-mono text-xs text-muted-foreground">
astro.config.mjs
<span data-code-live class="sr-only" aria-live="polite"></span>
<button type="button" data-code-copy class="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">
Copy
</button>
</figcaption>
<pre tabindex="0" aria-labelledby="ex-code-name" class="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)]">
<code>
// astro.config.mjs import { defineConfig } from 'astro/config'; export default defineConfig({ site: 'https://northwind.example', trailingSlash: 'always', });
</code>
</pre>
</figure>
<figure data-code-block class="w-full overflow-hidden rounded-lg border">
<figcaption id="ex-code-term-name" class="flex items-center justify-between gap-3 border-b bg-muted/50 px-4 py-2 font-mono text-xs text-muted-foreground">
Terminal
<span data-code-live class="sr-only" aria-live="polite"></span>
<button type="button" data-code-copy class="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">
Copy
</button>
</figcaption>
<pre tabindex="0" aria-labelledby="ex-code-term-name" class="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)]">
<code>npx shadcn@latest add https://www.themes.dev/caseroom/r/code-block.json</code>
</pre>
</figure>
</div><script type="module" src="/caseroom/r/html/code-block.js"></script>