# Code Block

Code block with syntax-highlighted snippets, a multi-language tab switcher, built-in copy button, and an actions slot. Highlighting via highlight.js with a built-in GitHub palette; theme="custom" skips the token colors for any highlight.js theme stylesheet.

- Package: `@mielui/svelte`
- Component version: `2.0.2`
- Depends on Mielui components: _internal/utils, tabs, copy-button, _internal/highlight
- Shared utilities: components/_internal/inset-layout, utils.cn, utils.createContext

## Install

~~~~sh
pnpm dlx @mielui/svelte add code-block
~~~~

## API

This reference is generated at build time from the component manifest, public `index.ts`, and documentation examples below. Changes to those source files are reflected here in the published Markdown. Standard Svelte and HTML attributes accepted by the exported prop types are supported.


### CodeBlock

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| labels | CodeBlockLabels \| undefined | - | No | No | Overrides the built-in text and accessible names. Every key is optional and English is the fallback. |
| value | string \| undefined | - | No | Yes | Active tab id (bindable). Defaults to the first tab. |
| tabs | CodeBlockTab[] \| undefined | - | No | No | High-level multi-language form. |
| code | string \| undefined | - | No | No | Single-snippet shorthand (ignored when `tabs` is set). |
| lang | string \| undefined | - | No | No | Language for the single-snippet shorthand. |
| showLineNumbers | boolean \| undefined | false | No | No | Render a line-number gutter. |
| copy | CodeBlockCopyPlacement \| undefined | 'actionbar' | No | No | Copy button placement: - `actionbar` (default): in the header's action cluster. - `overlay`: pinned to the top-right of the code body. - `inline`: a single-line body with the copy centered on the far right. |
| actions | Snippet<[]> \| undefined | - | No | No | Extra buttons placed left of the built-in copy button (high-level form). |
| theme | CodeBlockTheme \| undefined | 'mielui' | No | No | Syntax paint: - `mielui` (default): the built-in GitHub palette. - `custom`: no token colors; load any `highlight.js/styles/*` theme instead. |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### Actions

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| copy | boolean \| undefined | true | No | No | Append the built-in copy button after `children`. |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### Content

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| value | string \| undefined | 'default' | No | No | Tab id this panel belongs to. |
| code | string | - | Yes | No | Source text to show. |
| lang | string \| undefined | - | No | No | Language used for highlighting. |
| showLineNumbers | boolean \| undefined | false | No | No | Shows a line number before each line. |
| copyPlacement | "overlay" \| "inline" \| undefined | - | No | No | Render the copy button in/over this panel (`overlay` or `inline`). |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### Copy

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### Header

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### List

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### Trigger

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| value | string | - | Yes | No | Tab id; matches a `Content` value. |
| disabled | boolean \| undefined | false | No | No | Prevents interaction and dims the control. |
| class | string \| undefined | - | No | No | Classes added to the element. |
| children | Snippet<[]> \| undefined | - | No | No | Content rendered inside. |



### Public types

~~~~ts
import type { DefaultProps } from '@mielui/svelte/utils';
import type { Snippet } from 'svelte';

import Root from './code-block.svelte';
import Actions from './code-block-actions.svelte';
import Content from './code-block-content.svelte';
import Copy from './code-block-copy.svelte';
import Header from './code-block-header.svelte';
import List from './code-block-list.svelte';
import Trigger from './code-block-trigger.svelte';

/** One language entry for the high-level `tabs` prop. */
export type CodeBlockTab = {
    /** Visible tab label, e.g. "JavaScript". */
    label: string;
    /** Highlight.js language id (or alias), e.g. "javascript" / "py" / "c#". */
    lang: string;
    /** Raw source for this tab. */
    code: string;
    /** Stable tab id; defaults to `lang`. Needed when two tabs share a `lang`. */
    value?: string;
};

/** Where the built-in copy button sits. */
export type CodeBlockCopyPlacement = 'actionbar' | 'overlay' | 'inline';

/**
 * Syntax paint for the highlighted tokens.
 * - `mielui`: the built-in GitHub palette (light in light mode, dark in dark mode).
 * - `custom`: no token colors; load any `highlight.js/styles/*` theme instead.
 */
export type CodeBlockTheme = 'mielui' | 'custom';

/** Context shape shared with the subparts. */
export type CodeBlockRegistry = {
    /** Raw code per tab value, registered by each `Content` (Copy reads the active one). */
    codes: Record<string, string>;
    /** Highlight.js language id per tab value. */
    langs: Record<string, string>;
    /** The currently-active tab value. */
    active: string;
    /** Tab values in source order — drives the directional slide on swap. */
    order: string[];
    /** Whether the high-level root owns the shared panel surface. */
    contained: boolean;
    /** Token paint; `custom` skips the built-in colors for a stock theme stylesheet. */
    theme: CodeBlockTheme;
};

export type CodeBlockLabels = {
    /** Accessible name of an untabbed code region. */
    code?: string;
    /** Tooltip and accessible name of the copy button at rest. */
    copy?: string;
    /** Tooltip and accessible name of the copy button right after copying. */
    copied?: string;
};

export type CodeBlockProps = {
    /**
     * Overrides the built-in text and accessible names. Every key is optional and English is the
     * fallback.
     */
    labels?: CodeBlockLabels;
    /** Active tab id (bindable). Defaults to the first tab. */
    value?: string;
    /** High-level multi-language form. */
    tabs?: CodeBlockTab[];
    /** Single-snippet shorthand (ignored when `tabs` is set). */
    code?: string;
    /** Language for the single-snippet shorthand. */
    lang?: string;
    /** Render a line-number gutter. */
    showLineNumbers?: boolean;
    /**
     * Copy button placement:
     * - `actionbar` (default): in the header's action cluster.
     * - `overlay`: pinned to the top-right of the code body.
     * - `inline`: a single-line body with the copy centered on the far right.
     */
    copy?: CodeBlockCopyPlacement;
    /** Extra buttons placed left of the built-in copy button (high-level form). */
    actions?: Snippet;
    /**
     * Syntax paint:
     * - `mielui` (default): the built-in GitHub palette.
     * - `custom`: no token colors; load any `highlight.js/styles/*` theme instead.
     */
    theme?: CodeBlockTheme;
} & DefaultProps;

export type CodeBlockHeaderProps = DefaultProps;

export type CodeBlockListProps = DefaultProps;

export type CodeBlockTriggerProps = {
    /** Tab id; matches a `Content` value. */
    value: string;
    /** Prevents interaction and dims the control. */
    disabled?: boolean;
} & DefaultProps;

export type CodeBlockActionsProps = {
    /** Append the built-in copy button after `children`. */
    copy?: boolean;
} & DefaultProps;

export type CodeBlockCopyProps = DefaultProps;

export type CodeBlockContentProps = {
    /** Tab id this panel belongs to. */
    value?: string;
    /** Source text to show. */
    code: string;
    /** Language used for highlighting. */
    lang?: string;
    /** Shows a line number before each line. */
    showLineNumbers?: boolean;
    /** Render the copy button in/over this panel (`overlay` or `inline`). */
    copyPlacement?: 'overlay' | 'inline';
} & DefaultProps;

export { default as CodeBlock } from './code-block.svelte';
export { Actions, Content, Copy, Header, List, Root, Trigger };
export default Root;
~~~~

## Examples

### Compound

~~~~svelte
<script lang="ts">
    import { Download01Icon as Download } from '@hugeicons/core-free-icons';
    import { Button } from '@mielui/svelte/components/button';
    import * as CodeBlock from '@mielui/svelte/components/code-block';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';

    const sh = `npm install @mielui/svelte
npx @mielui/svelte add code-block`;

    const yaml = `name: ci
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test`;
</script>

<CodeBlock.Root value="shell">
    <CodeBlock.Header>
        <CodeBlock.List>
            <CodeBlock.Trigger value="shell">Terminal</CodeBlock.Trigger>
            <CodeBlock.Trigger value="yaml">ci.yml</CodeBlock.Trigger>
        </CodeBlock.List>
        <CodeBlock.Actions>
            <Button variant="ghost" size="icon" aria-label="Download">
                <HugeiconsIcon icon={Download} size={15} />
            </Button>
        </CodeBlock.Actions>
    </CodeBlock.Header>
    <CodeBlock.Content value="shell" code={sh} lang="bash" />
    <CodeBlock.Content value="yaml" code={yaml} lang="yaml" />
</CodeBlock.Root>
~~~~

### Copy Inline

~~~~svelte
<script lang="ts">
    import * as CodeBlock from '@mielui/svelte/components/code-block';
</script>

<CodeBlock.Root value="sh">
    <CodeBlock.Content
        value="sh"
        code="pnpm dlx @mielui/svelte add code-block"
        lang="bash"
        copyPlacement="inline"
    />
</CodeBlock.Root>
~~~~

### Copy Overlay

~~~~svelte
<script lang="ts">
    import * as CodeBlock from '@mielui/svelte/components/code-block';

    const code = `model = SentenceTransformer("all-MiniLM-L6-v2")
embeddings = model.encode(documents)
index.upsert(vectors=zip(ids, embeddings))`;
</script>

<CodeBlock.Root value="py">
    <CodeBlock.Content value="py" {code} lang="python" copyPlacement="overlay" />
</CodeBlock.Root>
~~~~

### Custom Actions

~~~~svelte
<script lang="ts">
    import { PencilEdit02Icon as Pencil, PlayIcon as Play } from '@hugeicons/core-free-icons';
    import { Button } from '@mielui/svelte/components/button';
    import * as CodeBlock from '@mielui/svelte/components/code-block';
    import { Textarea } from '@mielui/svelte/components/textarea';
    import * as Tooltip from '@mielui/svelte/components/tooltip';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';

    let ran = $state(false);
    let editing = $state(false);
    let code = $state(`SELECT id, email, created_at
FROM users
WHERE created_at > now() - interval '7 days'
ORDER BY created_at DESC
LIMIT 50;`);

    const message = $derived(
        ran
            ? 'Demo complete: 12 matching users. No database request was sent.'
            : 'Edit the query or run the local demonstration.'
    );
</script>

<div class="w-full space-y-3">
    <CodeBlock.Root value="sql">
        <CodeBlock.Header>
            <CodeBlock.Actions>
                <Tooltip.Root>
                    <Tooltip.Trigger>
                        <Button
                            variant="ghost"
                            size="icon"
                            aria-label="Run query"
                            onclick={() => {
                                ran = true;
                            }}
                        >
                            <HugeiconsIcon icon={Play} size={15} />
                        </Button>
                    </Tooltip.Trigger>
                    <Tooltip.Content>Run query</Tooltip.Content>
                </Tooltip.Root>
                <Tooltip.Root>
                    <Tooltip.Trigger>
                        <Button
                            variant="ghost"
                            size="icon"
                            aria-label="Edit query"
                            aria-pressed={editing}
                            onclick={() => {
                                editing = !editing;
                            }}
                        >
                            <HugeiconsIcon icon={Pencil} size={15} />
                        </Button>
                    </Tooltip.Trigger>
                    <Tooltip.Content>Edit query</Tooltip.Content>
                </Tooltip.Root>
            </CodeBlock.Actions>
        </CodeBlock.Header>
        <CodeBlock.Content value="sql" {code} lang="sql" />
    </CodeBlock.Root>
    {#if editing}
        <Textarea label="Query editor" bind:value={code} rows={6} class="font-mono text-sm" />
    {/if}
    <p role="status" class="text-sm text-foreground-muted">
        {message}
    </p>
</div>
~~~~

### Custom Theme Stylesheet

~~~~svelte
<script lang="ts">
    import { CodeBlock } from '@mielui/svelte/components/code-block';

    const code = `export async function getUser(id: string) {
  const res = await fetch(\`/api/users/\${id}\`);
  if (!res.ok) throw new Error('Not found');
  return res.json();
}`;
</script>

<!-- theme="custom" skips Mielui's token colors, so these stock-style
     highlight.js selectors paint the tokens instead. -->
<CodeBlock {code} lang="typescript" copy="overlay" theme="custom" class="demo-hljs-onedark" />

<style>
    /* One Dark / One Light inspired token colors, scoped to this demo. */
    :global(.demo-hljs-onedark .hljs-comment),
    :global(.demo-hljs-onedark .hljs-quote) {
        color: #a0a1a7;
        font-style: italic;
    }
    :global(.demo-hljs-onedark .hljs-keyword),
    :global(.demo-hljs-onedark .hljs-selector-tag),
    :global(.demo-hljs-onedark .hljs-literal),
    :global(.demo-hljs-onedark .hljs-section),
    :global(.demo-hljs-onedark .hljs-link) {
        color: #a626a4;
    }
    :global(.demo-hljs-onedark .hljs-string),
    :global(.demo-hljs-onedark .hljs-regexp) {
        color: #50a14f;
    }
    :global(.demo-hljs-onedark .hljs-number) {
        color: #986801;
    }
    :global(.demo-hljs-onedark .hljs-title),
    :global(.demo-hljs-onedark .hljs-title.function_) {
        color: #4078f2;
    }
    :global(.demo-hljs-onedark .hljs-attr),
    :global(.demo-hljs-onedark .hljs-attribute),
    :global(.demo-hljs-onedark .hljs-property),
    :global(.demo-hljs-onedark .hljs-variable) {
        color: #e45649;
    }
    :global(.demo-hljs-onedark .hljs-built_in),
    :global(.demo-hljs-onedark .hljs-type) {
        color: #0184bc;
    }
    :global(.demo-hljs-onedark .hljs-meta) {
        color: #383a42;
    }

    :global(.dark .demo-hljs-onedark .hljs-comment),
    :global(.dark .demo-hljs-onedark .hljs-quote) {
        color: #7f848e;
    }
    :global(.dark .demo-hljs-onedark .hljs-keyword),
    :global(.dark .demo-hljs-onedark .hljs-selector-tag),
    :global(.dark .demo-hljs-onedark .hljs-literal),
    :global(.dark .demo-hljs-onedark .hljs-section),
    :global(.dark .demo-hljs-onedark .hljs-link) {
        color: #c678dd;
    }
    :global(.dark .demo-hljs-onedark .hljs-string),
    :global(.dark .demo-hljs-onedark .hljs-regexp) {
        color: #98c379;
    }
    :global(.dark .demo-hljs-onedark .hljs-number) {
        color: #d19a66;
    }
    :global(.dark .demo-hljs-onedark .hljs-title),
    :global(.dark .demo-hljs-onedark .hljs-title.function_) {
        color: #61afef;
    }
    :global(.dark .demo-hljs-onedark .hljs-attr),
    :global(.dark .demo-hljs-onedark .hljs-attribute),
    :global(.dark .demo-hljs-onedark .hljs-property),
    :global(.dark .demo-hljs-onedark .hljs-variable) {
        color: #e06c75;
    }
    :global(.dark .demo-hljs-onedark .hljs-built_in),
    :global(.dark .demo-hljs-onedark .hljs-type) {
        color: #e5c07b;
    }
    :global(.dark .demo-hljs-onedark .hljs-meta) {
        color: #abb2bf;
    }
</style>
~~~~

### Custom Theme Variables

~~~~svelte
<script lang="ts">
    import { CodeBlock } from '@mielui/svelte/components/code-block';

    const code = `export async function getUser(id: string) {
  const res = await fetch(\`/api/users/\${id}\`);
  if (!res.ok) throw new Error('Not found');
  return res.json();
}`;
</script>

<!-- The eight --code-block-token-* variables inherit, so a wrapper can
     recolor the built-in palette per mode without a stylesheet. -->
<div class="demo-brand-tokens w-full min-w-0">
    <CodeBlock {code} lang="typescript" copy="overlay" />
</div>

<style>
    :global(.demo-brand-tokens) {
        --code-block-token-comment: #7d8590;
        --code-block-token-keyword: #a21caf;
        --code-block-token-string: #047857;
        --code-block-token-number: #b45309;
        --code-block-token-function: #4d7c0f;
        --code-block-token-property: #0e7490;
        --code-block-token-builtin: #be123c;
        --code-block-token-entity: #15803d;
        --code-block-token-meta: #64748b;
    }
    :global(.dark .demo-brand-tokens) {
        --code-block-token-comment: #8b949e;
        --code-block-token-keyword: #e879f9;
        --code-block-token-string: #34d399;
        --code-block-token-number: #fbbf24;
        --code-block-token-function: #a3e635;
        --code-block-token-property: #22d3ee;
        --code-block-token-builtin: #fb7185;
        --code-block-token-entity: #4ade80;
        --code-block-token-meta: #94a3b8;
    }
</style>
~~~~

### Hero

~~~~svelte
<script lang="ts">
    import {
        InformationCircleIcon as Info,
        SparklesIcon as Sparkles
    } from '@hugeicons/core-free-icons';
    import { Button } from '@mielui/svelte/components/button';
    import * as CodeBlock from '@mielui/svelte/components/code-block';
    import * as Tooltip from '@mielui/svelte/components/tooltip';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';
    import csharp from './snippets/quickstart.cs.txt?raw';
    import go from './snippets/quickstart.go.txt?raw';
    import java from './snippets/quickstart.java.txt?raw';
    import javascript from './snippets/quickstart.js.txt?raw';
    import python from './snippets/quickstart.py.txt?raw';
</script>

<CodeBlock.Root value="javascript">
    <CodeBlock.Header>
        <CodeBlock.List>
            <CodeBlock.Trigger value="python">Python</CodeBlock.Trigger>
            <CodeBlock.Trigger value="javascript">JavaScript</CodeBlock.Trigger>
            <CodeBlock.Trigger value="java">Java</CodeBlock.Trigger>
            <CodeBlock.Trigger value="go">Go</CodeBlock.Trigger>
            <CodeBlock.Trigger value="csharp">C#</CodeBlock.Trigger>
        </CodeBlock.List>
        <CodeBlock.Actions>
            <Tooltip.Root>
                <Tooltip.Trigger>
                    <Button variant="ghost" size="icon" aria-label="About this snippet">
                        <HugeiconsIcon icon={Info} size={15} />
                    </Button>
                </Tooltip.Trigger>
                <Tooltip.Content>About this snippet</Tooltip.Content>
            </Tooltip.Root>
            <Tooltip.Root>
                <Tooltip.Trigger>
                    <Button variant="ghost" size="icon" aria-label="Explain with AI">
                        <HugeiconsIcon icon={Sparkles} size={15} />
                    </Button>
                </Tooltip.Trigger>
                <Tooltip.Content>Explain with AI</Tooltip.Content>
            </Tooltip.Root>
        </CodeBlock.Actions>
    </CodeBlock.Header>
    <CodeBlock.Content value="python" code={python} lang="python" />
    <CodeBlock.Content value="javascript" code={javascript} lang="javascript" />
    <CodeBlock.Content value="java" code={java} lang="java" />
    <CodeBlock.Content value="go" code={go} lang="go" />
    <CodeBlock.Content value="csharp" code={csharp} lang="csharp" />
</CodeBlock.Root>
~~~~

### Line Numbers

~~~~svelte
<script lang="ts">
    import * as CodeBlock from '@mielui/svelte/components/code-block';

    const code = `def fib(n):
    a, b = 0, 1
    for _ in range(n):
        a, b = b, a + b
    return a

print([fib(i) for i in range(10)])`;
</script>

<CodeBlock.Root value="py">
    <CodeBlock.Content value="py" {code} lang="python" showLineNumbers />
</CodeBlock.Root>
~~~~

### Multi Language

~~~~svelte
<script lang="ts">
    import { CodeBlock } from '@mielui/svelte/components/code-block';

    const ts = `export async function getUser(id: string) {
  const res = await fetch(\`/api/users/\${id}\`);
  if (!res.ok) throw new Error('Not found');
  return res.json();
}`;

    const py = `import httpx

async def get_user(id: str):
    async with httpx.AsyncClient() as client:
        res = await client.get(f"/api/users/{id}")
        res.raise_for_status()
        return res.json()`;

    const go = `func GetUser(id string) (*User, error) {
    res, err := http.Get("/api/users/" + id)
    if err != nil {
        return nil, err
    }
    defer res.Body.Close()
    return decode(res.Body)
}`;
</script>

<CodeBlock
    class="max-w-xl"
    value="typescript"
    tabs={[
        { label: 'TypeScript', lang: 'typescript', code: ts },
        { label: 'Python', lang: 'python', code: py },
        { label: 'Go', lang: 'go', code: go }
    ]}
/>
~~~~

### Single

~~~~svelte
<script lang="ts">
    import * as CodeBlock from '@mielui/svelte/components/code-block';

    const code = `const greet = (name: string) => {
  return \`Hello, \${name}!\`;
};

console.log(greet('world'));`;
</script>

<CodeBlock.Root value="ts">
    <CodeBlock.Content value="ts" {code} lang="typescript" />
</CodeBlock.Root>
~~~~

For the rendered reference, visit [/docs/components/code-block](/docs/components/code-block).
