# Code Block

A code block with a language label and one-click copy button.

- Category: Text Outputs
- Source: AICSS (https://www.aicss.dev/components/code-block)
- Author: @kvnkld (https://x.com/kvnkld)
- Styling: Self-contained CSS with CSS custom properties (design tokens). Theme-aware via [data-theme] (light/dark).

## Instructions

Add this component to the project. Keep the styling self-contained. Map the design tokens (CSS custom properties) to the project's theme if they are not already defined.

## Code

### React - `CodeBlock.tsx`

```tsx
"use client";

import styles from "./CodeBlock.module.css";
import { useState } from "react";

export function CodeBlock({ lang, code }: { lang: string; code: string }) {
  const [copied, setCopied] = useState(false);
  const lines = code.split("\n");
  const copy = () => {
    navigator.clipboard.writeText(code);
    setCopied(true);
    setTimeout(() => setCopied(false), 1200);
  };
  return (
    <div className={styles.cb}>
      <div className={styles.cbHead}>
        <span className={styles.cbFile}>
          <svg className={styles.cbIcon} viewBox="0 0 24 24" width="15" height="15" aria-hidden="true"><path d="m8 6-6 6 6 6M16 6l6 6-6 6" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" /></svg>
          <span className={styles.cbLang}>{lang}</span>
        </span>
        <button className={styles.cbCopy} onClick={copy} aria-label={copied ? "Copied" : "Copy code"}>
          {copied ? (
            <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="m4.5 12.75 6 6 9-13.5" /></svg>
          ) : (
            <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2.5" /><path d="M5 15a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2" /></svg>
          )}
          <span>{copied ? "Copied" : "Copy"}</span>
        </button>
      </div>
      <div className={styles.cbBody}>
        {lines.map((line, i) => (
          <div className={styles.cbRow} key={i}>
            <span className={styles.cbLn}>{i + 1}</span>
            <code className={styles.cbCode}>{line || "\u00A0"}</code>
          </div>
        ))}
      </div>
    </div>
  );
}

```

### React - `CodeBlock.module.css`

```css
/* Theme follows the nearest [data-theme] ancestor (preview switch),
   then .dark, then the OS when no data-theme is set. */
:global(:root),
:global([data-theme="light"]) {
  --cb-bg: #fff;
  --cb-ring: #e6e8ec;
  --cb-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.05),
    0 2px 4px rgba(0, 0, 0, 0.02);
  --cb-fg: #1a1a1a;
  --cb-copy-hover-fg: #1a1a1a;
  --cb-copy-hover-bg: #f4f5f7;
}
:global([data-theme="dark"]),
:global(.dark) {
  --cb-bg: #1a1a1a;
  --cb-ring: #303030;
  --cb-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.12),
    0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
  --cb-fg: #f5f5f5;
  --cb-copy-hover-fg: #f5f5f5;
  --cb-copy-hover-bg: #242424;
}
@media (prefers-color-scheme: dark) {
  :global(:root:not([data-theme])) {
    --cb-bg: #1a1a1a;
    --cb-ring: #303030;
    --cb-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.12),
      0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
    --cb-fg: #f5f5f5;
    --cb-copy-hover-fg: #f5f5f5;
    --cb-copy-hover-bg: #242424;
  }
}

.cb { border-radius: 12px; background: var(--cb-bg, #fff); box-shadow: var(--cb-shadow); padding: 12px 16px 16px; overflow: hidden; }
.cbHead { display: flex; align-items: center; gap: 8px; margin: -12px -16px 0; padding: 10px 12px 10px 16px; background: transparent; border-bottom: 0.5px solid var(--cb-ring, #e6e8ec); }
.cbFile { display: inline-flex; align-items: center; gap: 7px; }
.cbIcon { display: block; width: 15px; height: 15px; color: #a1a1a1; flex: none; }
.cbLang { font-family: ui-monospace, monospace; font-size: 12.5px; line-height: 1; color: var(--cb-fg, #1a1a1a); }
.cbCopy { margin-left: auto; margin-top: -7px; margin-bottom: -7px; display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: #a1a1a1; border: 0; background: none; padding: 3px 3px 3px 7px; border-radius: 7px; cursor: pointer; }
.cbCopy:hover { color: var(--cb-copy-hover-fg, #1a1a1a); background: var(--cb-copy-hover-bg, #f4f5f7); }
.cbBody { position: relative; margin: 0 -16px -16px; padding: 10px 0; font-family: ui-monospace, monospace; font-size: 12.5px; line-height: 20px; }
.cbBody::before { content: ""; position: absolute; top: 0; bottom: 0; left: 32px; width: 0.5px; background: var(--cb-ring, #e6e8ec); }
.cbRow { display: grid; grid-template-columns: 32px 1fr; }
.cbLn { user-select: none; text-align: right; padding: 0 7px; color: #a1a1a1; font-size: 11px; }
.cbCode { white-space: pre; padding: 0 12px 0 8px; color: var(--cb-fg, #1a1a1a); overflow-x: auto; scrollbar-width: none; }
.cbCode::-webkit-scrollbar { display: none; }

```

### Vue - `CodeBlock.vue`

```vue
<template>
  <div class="cb">
    <div class="cb-head">
      <span class="cb-file">
        <svg class="cb-icon" viewBox="0 0 24 24" width="15" height="15" aria-hidden="true"><path d="m8 6-6 6 6 6M16 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" /></svg>
        <span class="cb-lang">{{ lang }}</span>
      </span>
      <button class="cb-copy" @click="copy" :aria-label="copied ? 'Copied' : 'Copy code'">
        <svg v-if="copied" viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m4.5 12.75 6 6 9-13.5" /></svg>
        <svg v-else viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2.5" /><path d="M5 15a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2" /></svg>
        <span>{{ copied ? 'Copied' : 'Copy' }}</span>
      </button>
    </div>
    <div class="cb-body">
      <div class="cb-row" v-for="(line, i) in lines" :key="i">
        <span class="cb-ln">{{ i + 1 }}</span>
        <code class="cb-code">{{ line || '\u00A0' }}</code>
      </div>
    </div>
  </div>
</template>

<script setup>
import { computed, ref } from "vue";
const props = defineProps({ lang: String, code: String });
const copied = ref(false);
const lines = computed(() => (props.code ?? "").split("\n"));
function copy() {
  navigator.clipboard.writeText(props.code);
  copied.value = true;
  setTimeout(() => (copied.value = false), 1200);
}
</script>

<style scoped>
/* Theme follows the nearest [data-theme] ancestor, then .dark, then the OS. */
:global(:root),
:global([data-theme="light"]) {
  --cb-bg: #fff;
  --cb-ring: #e6e8ec;
  --cb-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.02);
  --cb-fg: #1a1a1a;
  --cb-copy-hover-fg: #1a1a1a;
  --cb-copy-hover-bg: #f4f5f7;
}
:global([data-theme="dark"]),
:global(.dark) {
  --cb-bg: #1a1a1a;
  --cb-ring: #303030;
  --cb-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.12), 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
  --cb-fg: #f5f5f5;
  --cb-copy-hover-fg: #f5f5f5;
  --cb-copy-hover-bg: #242424;
}
@media (prefers-color-scheme: dark) {
  :global(:root:not([data-theme])) {
  --cb-bg: #1a1a1a;
  --cb-ring: #303030;
  --cb-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.12), 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
  --cb-fg: #f5f5f5;
  --cb-copy-hover-fg: #f5f5f5;
  --cb-copy-hover-bg: #242424;
  }
}
.cb { border-radius: 12px; background: var(--cb-bg, #fff); box-shadow: var(--cb-shadow, 0 0 0 0.5px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.02)); padding: 12px 16px 16px; overflow: hidden; }
.cb-head { display: flex; align-items: center; gap: 8px; margin: -12px -16px 0; padding: 10px 12px 10px 16px; background: transparent; border-bottom: 0.5px solid var(--cb-ring, #e6e8ec); }
.cb-file { display: inline-flex; align-items: center; gap: 7px; }
.cb-icon { display: block; width: 15px; height: 15px; color: #a1a1a1; flex: none; }
.cb-lang { font-family: ui-monospace, monospace; font-size: 12.5px; line-height: 1; color: var(--cb-fg, #1a1a1a); }
.cb-copy { margin-left: auto; margin-top: -7px; margin-bottom: -7px; display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: #a1a1a1; border: 0; background: none; padding: 3px 3px 3px 7px; border-radius: 7px; cursor: pointer; }
.cb-copy:hover { color: var(--cb-copy-hover-fg, #1a1a1a); background: var(--cb-copy-hover-bg, #f4f5f7); }
.cb-body { position: relative; margin: 0 -16px -16px; padding: 10px 0; font-family: ui-monospace, monospace; font-size: 12.5px; line-height: 20px; }
.cb-body::before { content: ""; position: absolute; top: 0; bottom: 0; left: 32px; width: 0.5px; background: var(--cb-ring, #e6e8ec); }
.cb-row { display: grid; grid-template-columns: 32px 1fr; }
.cb-ln { user-select: none; text-align: right; padding: 0 7px; color: #a1a1a1; font-size: 11px; }
.cb-code { white-space: pre; padding: 0 12px 0 8px; color: var(--cb-fg, #1a1a1a); overflow-x: auto; scrollbar-width: none; }
.cb-code::-webkit-scrollbar { display: none; }
</style>
```

### Svelte - `CodeBlock.svelte`

```svelte
<script>
  export let lang = "";
  export let code = "";
  let copied = false;
  $: lines = code.split("\n");
  function copy() {
    navigator.clipboard.writeText(code);
    copied = true;
    setTimeout(() => (copied = false), 1200);
  }
</script>

<div class="cb">
  <div class="cb-head">
    <span class="cb-file">
      <svg class="cb-icon" viewBox="0 0 24 24" width="15" height="15" aria-hidden="true"><path d="m8 6-6 6 6 6M16 6l6 6-6 6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" /></svg>
      <span class="cb-lang">{lang}</span>
    </span>
    <button class="cb-copy" on:click={copy} aria-label={copied ? 'Copied' : 'Copy code'}>
      {#if copied}
        <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m4.5 12.75 6 6 9-13.5" /></svg>
      {:else}
        <svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2.5" /><path d="M5 15a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2" /></svg>
      {/if}
      <span>{copied ? 'Copied' : 'Copy'}</span>
    </button>
  </div>
  <div class="cb-body">
    {#each lines as line, i (i)}
      <div class="cb-row">
        <span class="cb-ln">{i + 1}</span>
        <code class="cb-code">{line || '\u00A0'}</code>
      </div>
    {/each}
  </div>
</div>

<style>
/* Theme follows the nearest [data-theme] ancestor, then .dark, then the OS. */
:global(:root),
:global([data-theme="light"]) {
  --cb-bg: #fff;
  --cb-ring: #e6e8ec;
  --cb-shadow: 0 0 0 0.5px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.02);
  --cb-fg: #1a1a1a;
  --cb-copy-hover-fg: #1a1a1a;
  --cb-copy-hover-bg: #f4f5f7;
}
:global([data-theme="dark"]),
:global(.dark) {
  --cb-bg: #1a1a1a;
  --cb-ring: #303030;
  --cb-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.12), 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
  --cb-fg: #f5f5f5;
  --cb-copy-hover-fg: #f5f5f5;
  --cb-copy-hover-bg: #242424;
}
@media (prefers-color-scheme: dark) {
  :global(:root:not([data-theme])) {
  --cb-bg: #1a1a1a;
  --cb-ring: #303030;
  --cb-shadow: 0 0 0 0.5px rgba(255, 255, 255, 0.12), 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
  --cb-fg: #f5f5f5;
  --cb-copy-hover-fg: #f5f5f5;
  --cb-copy-hover-bg: #242424;
  }
}
.cb { border-radius: 12px; background: var(--cb-bg, #fff); box-shadow: var(--cb-shadow, 0 0 0 0.5px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.02)); padding: 12px 16px 16px; overflow: hidden; }
.cb-head { display: flex; align-items: center; gap: 8px; margin: -12px -16px 0; padding: 10px 12px 10px 16px; background: transparent; border-bottom: 0.5px solid var(--cb-ring, #e6e8ec); }
.cb-file { display: inline-flex; align-items: center; gap: 7px; }
.cb-icon { display: block; width: 15px; height: 15px; color: #a1a1a1; flex: none; }
.cb-lang { font-family: ui-monospace, monospace; font-size: 12.5px; line-height: 1; color: var(--cb-fg, #1a1a1a); }
.cb-copy { margin-left: auto; margin-top: -7px; margin-bottom: -7px; display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: #a1a1a1; border: 0; background: none; padding: 3px 3px 3px 7px; border-radius: 7px; cursor: pointer; }
.cb-copy:hover { color: var(--cb-copy-hover-fg, #1a1a1a); background: var(--cb-copy-hover-bg, #f4f5f7); }
.cb-body { position: relative; margin: 0 -16px -16px; padding: 10px 0; font-family: ui-monospace, monospace; font-size: 12.5px; line-height: 20px; }
.cb-body::before { content: ""; position: absolute; top: 0; bottom: 0; left: 32px; width: 0.5px; background: var(--cb-ring, #e6e8ec); }
.cb-row { display: grid; grid-template-columns: 32px 1fr; }
.cb-ln { user-select: none; text-align: right; padding: 0 7px; color: #a1a1a1; font-size: 11px; }
.cb-code { white-space: pre; padding: 0 12px 0 8px; color: var(--cb-fg, #1a1a1a); overflow-x: auto; scrollbar-width: none; }
.cb-code::-webkit-scrollbar { display: none; }
</style>
```
