# Markdown

Safe GFM markdown renderer for dense agent output, with token-based rendering and Mielui code blocks.

- Package: `@mielui/svelte`
- Component version: `1.1.2`
- Depends on Mielui components: _internal/utils, code-block, typography
- Shared utilities: utils.cn, utils.createContext

## Install

~~~~sh
pnpm dlx @mielui/svelte add markdown
~~~~

## 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.


### Markdown

| Prop | Type | Default | Required | Bindable | Description |
| --- | --- | --- | --- | --- | --- |
| labels | MarkdownLabels \| undefined | - | No | No | Overrides the built-in text and accessible names. Every key is optional and English is the fallback. |
| content | string | - | Yes | No | Markdown source parsed with marked's GFM tokenizer. Raw HTML is rendered as text. |
| streaming | boolean \| undefined | false | No | No | Marks an in-progress response as busy and shows a restrained visual caret. |
| class | string \| undefined | - | No | No | Classes added to the element. |

Also accepts the native HTML attributes and events listed in the rendered API reference.

### Public types

~~~~ts
import type { HTMLAttributes } from 'svelte/elements';
import Markdown from './markdown.svelte';

export type MarkdownLabels = {
    completedTask?: string;
    incompleteTask?: string;
    table?: string;
};

export type MarkdownProps = {
    /**
     * Overrides the built-in text and accessible names. Every key is optional and English is the
     * fallback.
     */
    labels?: MarkdownLabels;
    /** Markdown source parsed with marked's GFM tokenizer. Raw HTML is rendered as text. */
    content: string;
    /** Marks an in-progress response as busy and shows a restrained visual caret. */
    streaming?: boolean;
    /** Classes added to the element. */
    class?: string;
} & Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'class'>;

export { Markdown };
~~~~

## Examples

### Hero

~~~~svelte
<script lang="ts">
    import { Markdown } from '@mielui/svelte/components/markdown';

    const report = [
        '## Edge cache rollout review',
        '',
        'The canary reduced origin traffic by **38%** while keeping `cache_hit_age` within the expected range. [Open the rollout runbook](https://example.com/runbooks/edge-cache) for the full procedure.',
        '',
        '> Recommendation: continue to 50% traffic, but keep image transforms outside the cache until the key audit is complete.',
        '',
        '### Regional health',
        '',
        '| Region | Hit rate | p95 |',
        '| :--- | ---: | ---: |',
        '| iad1 | 91% | 142 ms |',
        '| fra1 | 88% | 156 ms |',
        '| sin1 | 86% | 171 ms |',
        '',
        '### Rollout tasks',
        '',
        '- [x] Confirm cache headers at the edge',
        '- [x] Compare origin error rates',
        '- [ ] Audit image transformation keys',
        '',
        '```ts',
        'const rollout = await edgeCache.promote({',
        '  traffic: 0.5,',
        "  regions: ['iad1', 'fra1', 'sin1']",
        '});',
        '```'
    ].join('\n');
</script>

<Markdown content={report} class="w-full max-w-2xl" />
~~~~

### Safe Html

~~~~svelte
<script lang="ts">
    import { ShieldCheckIcon as ShieldCheck } from '@hugeicons/core-free-icons';
    import { Markdown } from '@mielui/svelte/components/markdown';
    import HugeiconsIcon from '@mielui/svelte/hugeicons-icon';

    const untrustedOutput = [
        '## Imported model output',
        '',
        `<script>document.body.textContent = "changed";<${'/'}script>`,
        '',
        '<button onclick="sendSecrets()">Approve access</button>',
        '',
        '[Open internal report](javascript:sendSecrets())'
    ].join('\n');
</script>

<div class="flex w-full max-w-2xl flex-col gap-4">
    <div class="flex items-start gap-3 border-b border-border pb-4">
        <HugeiconsIcon
            icon={ShieldCheck}
            size={16}
            class="mt-0.5 shrink-0 text-foreground-muted"
            aria-hidden="true"
        />
        <div>
            <p class="[font-weight:var(--font-weight-label)] text-foreground">
                Untrusted content stays inert
            </p>
            <p class="mt-1 text-sm leading-6 text-foreground-muted">
                HTML is shown as source text. Unsafe URL schemes are not turned into links.
            </p>
        </div>
    </div>
    <Markdown content={untrustedOutput} />
</div>
~~~~

### Streaming

~~~~svelte
<script lang="ts">
    import { Button } from '@mielui/svelte/components/button';
    import { Markdown } from '@mielui/svelte/components/markdown';
    import { onDestroy } from 'svelte';

    const response = `## Incident update

The elevated error rate is isolated to image transformations in \`fra1\`. Cached images remain available, and requests in other regions are healthy.

### What we checked

- Cache hit rate remains above **98%**.
- Origin latency is within the normal range.
- Transformation workers restart when memory reaches their limit.

| Signal | Before | Current |
| --- | --- | --- |
| Error rate | 4.2% | 0.8% |
| Worker concurrency | 12 | 8 |
| Queue age | 42 seconds | 9 seconds |

### Apply the change

\`\`\`ts
const worker = {
    concurrency: 8,
    retries: 2,
    timeout: 30_000
};
\`\`\`

> Keep the previous configuration available until the queue has drained.

### Next checks

1. Compare the next five minutes against the baseline.
2. Confirm that worker restarts have stopped.
3. Restore concurrency gradually after memory use stabilizes.

**Owner:** Platform team. The incident stays open until the error rate is back below 0.1%.`;

    let content = $state('');
    let streaming = $state(false);
    let timer: ReturnType<typeof setInterval> | undefined;

    const complete = $derived(content.length === response.length);
    const status = $derived.by(() => {
        if (streaming) {
            return 'Receiving response';
        }

        if (complete) {
            return 'Response complete';
        }

        if (content) {
            return 'Stream stopped';
        }

        return 'Start a response to watch Markdown arrive';
    });

    function stop() {
        clearInterval(timer);
        streaming = false;
    }

    function start() {
        stop();
        content = '';
        streaming = true;
        timer = setInterval(() => {
            content = response.slice(0, content.length + 28);
            if (complete) {
                stop();
            }
        }, 45);
    }

    onDestroy(stop);
</script>

<div class="w-full max-w-2xl space-y-4">
    <div class="flex flex-wrap items-center justify-between gap-3">
        <p role="status" class="text-sm text-foreground-muted">{status}</p>
        <Button variant="secondary" onclick={streaming ? stop : start}>
            {streaming ? 'Stop stream' : content ? 'Replay response' : 'Start response'}
        </Button>
    </div>
    <div class="mielui-plate max-h-112 min-h-52 overflow-y-auto p-5">
        <Markdown {content} {streaming} />
    </div>
</div>
~~~~

For the rendered reference, visit [/docs/components/markdown](/docs/components/markdown).
