# RichMarkdown

> Markdown with sortable and filterable tables, code blocks with line numbers and download, and a frontmatter table.

Source: https://docs.nasaqui.com/components/markdown-extras

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/markdown-extras.json
```

[Markdown](https://docs.nasaqui.com/components/markdown) plus the things people expect when notes and docs hold real data. Tables sort when you click a header, filter as you type and download as CSV. Code blocks get a line-number toggle, a download button and copy. A leading `---` frontmatter block becomes a tidy properties table. The parts are also exported to use on their own.

## When to use

- A notes app, knowledge base or docs page showing user-written Markdown that includes tables and code.
- Anywhere the reader should work with the data (sort, search, take it away) rather than just look at it.

## When not to use

- Short chat text or comments: use [markdown](https://docs.nasaqui.com/components/markdown).
- Large data sets: use [data-table](https://docs.nasaqui.com/components/data-table). Markdown tables are sorted and filtered in the browser.
- Editing Markdown: this renders only.

## Import

```tsx
import { RichMarkdown, MarkdownTable, FrontmatterTable, DownloadableCodeBlock } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"
```

## Quick start

```tsx
import { RichMarkdown } from "@fadymondy/nasaq/web";

const source = `---
title: Q3 numbers
tags: [finance, report]
published: 2026-09-01
---

| Region | Revenue |
| --- | ---: |
| Riyadh | $12,400 |
| Cairo | $8,900 |

\`\`\`ts title="sum.ts" showLineNumbers {2}
const a = 1;
const b = 2;
\`\`\`
`;

export function Note() {
  return <RichMarkdown tables={{ downloadable: true }}>{source}</RichMarkdown>;
}
```

## Anatomy

```
RichMarkdown            data-slot="rich-markdown"
├─ FrontmatterTable     data-slot="frontmatter-table"
└─ Markdown body
    ├─ MarkdownTable    data-slot="markdown-table"  (filter, count, CSV, sortable headers)
    └─ DownloadableCodeBlock  (CodeBlock with line-number toggle, download, copy)
```

## API

### RichMarkdown

Accepts the props of [Markdown](https://docs.nasaqui.com/components/markdown) (`components`, `remarkPlugins`, `className`) plus:

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `string` | required | Markdown source, with an optional frontmatter block. |
| `frontmatter` | `"table" \| "hide"` | `"table"` | Show the frontmatter as a table above the body, or drop it. |
| `tables` | `false \| { sortable?, filterable?, filterMinRows?, downloadable? }` | `{}` | Options for tables. `false` keeps plain tables. |
| `code` | `false \| { lineNumbers?, download? }` | `{}` | Options for code blocks. `false` keeps plain blocks. |
| `labels` | `MarkdownExtrasLabels` | English or Arabic | String overrides. |

Fence meta is read: `ts title="app.ts" showLineNumbers {2,4-6}` sets the file name, turns line numbers on and highlights lines.

### MarkdownTable

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `columns` | `{ header, text?, align? }[]` | required | Rendered header, its plain text, and `start`, `center` or `end`. |
| `rows` | `{ cells, texts? }[]` | required | Rendered cells and their plain text (taken from the cells when omitted). |
| `sortable` | `boolean` | `true` | Click a header to sort ascending, descending, then off. Numbers sort by value, empty cells stay last. |
| `filterable` | `boolean \| "auto"` | `"auto"` | The filter box. `auto` shows it from `filterMinRows` rows. |
| `filterMinRows` | `number` | `6` | Rows from which `auto` shows the filter. |
| `downloadable`, `downloadName` | `boolean`, `string` | `false`, `"table.csv"` | A "Download CSV" button of the rows in view. |
| `defaultSort` | `{ column, direction } \| null` | `null` | Initial sort. |
| `label` | `string` | "Table" | Accessible name. |
| `labels` | `MarkdownExtrasLabels` | | String overrides. |

### FrontmatterTable

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `data` | `[key, value][] \| Record<string, value>` | required | Values are text, numbers, booleans or lists. Lists show as badges, ISO dates are formatted, links are clickable. |
| `title` | `ReactNode` | "Properties" | Heading. |

### DownloadableCodeBlock

Accepts the props of [CodeBlock](https://docs.nasaqui.com/components/code-block) plus `download` (default `true`), `downloadName`, `lineNumberToggle` (default `true`). `lineNumbers` is the starting state.

### Helpers

`parseMarkdownFrontmatter(source)`, `frontmatterLabel(key)`, `nextMarkdownSort`, `sortMarkdownRows`, `filterMarkdownRows`, `markdownCellNumber`, `compareMarkdownCells`, `parseFenceMeta`, `codeDownloadName`.

## Examples

```tsx
<RichMarkdown frontmatter="hide" code={{ lineNumbers: true }}>{md}</RichMarkdown>
```

```tsx
<MarkdownTable
  downloadable
  downloadName="orders.csv"
  columns={[{ header: "Order" }, { header: "Total", align: "end" }]}
  rows={[{ cells: ["A-1", "$40"] }, { cells: ["A-2", "$9"] }]}
/>
```

```tsx
<FrontmatterTable data={{ title: "Plan", tags: ["a", "b"], draft: false }} />
```

## Accessibility

| Key | Action |
| --- | --- |
| Tab | Reach the filter, the sort buttons and code actions. |
| Enter, Space | Sort by the focused header, toggle line numbers, download. |

- Sortable headers carry `aria-sort` (`ascending`, `descending`, `none`) and a button named "Sort by column".
- A polite live region announces the sort and "3 of 12 rows" after a change.
- The filter box and the icon buttons are labelled. Localise `labels` for your wording.

## RTL & i18n

- Tables follow the page direction; cells use `dir="auto"` so mixed Arabic and English rows align.
- Numbers and currency in Arabic-Indic digits sort by value. The filter folds accents, tashkeel and hamza forms.
- Code stays left-to-right inside an Arabic page.
- CSV downloads start with a byte-order mark so Excel opens Arabic text correctly.

## Styling & tokens

Uses `--nq-*` border, surface, selected and focus tokens. Target `data-slot="markdown-table"`, `frontmatter-table` and `rich-markdown`. Extend with `className`.

## Do / Don't

- Do give downloads a meaningful file name.
- Do turn off features you do not need (`tables={false}`) for very small content.
- Don't render untrusted HTML. Raw HTML is never rendered, as in `Markdown`.
- Don't use it for tables with thousands of rows.

## Related

- [markdown](https://docs.nasaqui.com/components/markdown), [code-block](https://docs.nasaqui.com/components/code-block), [table](https://docs.nasaqui.com/components/table), [export-action](https://docs.nasaqui.com/components/export-action)

## Lab

https://docs.nasaqui.com/?path=/docs/components-editors-markdown-extras--docs

## Code

### React

```tsx
import { RichMarkdown } from "@fadymondy/nasaq/web";

const source = `---
title: Q3 numbers
tags: [finance, report]
published: 2026-09-01
---

| Region | Revenue |
| --- | ---: |
| Riyadh | $12,400 |
| Cairo | $8,900 |

\`\`\`ts title="sum.ts" showLineNumbers {2}
const a = 1;
const b = 2;
\`\`\`
`;

export function Note() {
  return <RichMarkdown tables={{ downloadable: true }}>{source}</RichMarkdown>;
}
```

### shadcn

```tsx
import { RichMarkdown } from "@/components/ui/markdown-extras";

const source = `---
title: Q3 numbers
tags: [finance, report]
published: 2026-09-01
---

| Region | Revenue |
| --- | ---: |
| Riyadh | $12,400 |
| Cairo | $8,900 |

\`\`\`ts title="sum.ts" showLineNumbers {2}
const a = 1;
const b = 2;
\`\`\`
`;

export function Note() {
  return <RichMarkdown tables={{ downloadable: true }}>{source}</RichMarkdown>;
}
```

### Vue

```vue
<script setup lang="ts">
import { NqRichMarkdown } from "@fadymondy/nasaq/vue";

const source = `---
title: Q3 numbers
tags: [finance, report]
published: 2026-09-01
---

| Region | Revenue |
| --- | ---: |
| Riyadh | $12,400 |
| Cairo | $8,900 |

\`\`\`ts title="sum.ts" showLineNumbers {2}
const a = 1;
const b = 2;
\`\`\`
`;
</script>

<template>
  <NqRichMarkdown :source="source" :tables="{ downloadable: true }" />
</template>
```

### Blade

````blade
<x-nq::markdown-extras :tables="['downloadable' => true]">
---
title: Q3 numbers
tags: [finance, report]
published: 2026-09-01
draft: false
---

| Region | Revenue |
| --- | ---: |
| Riyadh | $12,400 |
| Cairo | $8,900 |
| Dubai | $100 |

```ts title="sum.ts" showLineNumbers {2}
const a = 1;
const b = 2;
```
</x-nq::markdown-extras>
````

### HTML + Alpine

```html
<div data-slot="rich-markdown" class="flex min-w-0 flex-col gap-4">
            <section data-slot="frontmatter-table" class="flex flex-col gap-2">
        <p class="eyebrow">Properties</p>
        <div class="overflow-hidden rounded-card border border-border">
            <div data-slot="table-container" role="region" tabindex="0"  aria-label="Properties"     class="relative w-full overflow-x-auto outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus">
    <table data-slot="table" data-density="default"
                  class="w-full caption-bottom border-collapse text-body-sm">
        <tbody data-slot="table-body" class="[&_tr:last-child]:border-0"><tr data-slot="table-row"     class="border-b border-border transition-colors duration-150 ease-nq hover:bg-nq-hover data-[state=selected]:bg-nq-selected"><th data-slot="table-head" scope="row" class="text-start align-middle text-caption font-medium text-muted-foreground px-4 h-auto w-1/3 max-w-48 py-2 align-top whitespace-normal"><bdi>Title</bdi></th>
                            <td data-slot="table-cell" class="align-middle px-4 h-auto py-2 whitespace-normal text-foreground"><span dir="auto">Q3 numbers</span></td></tr>
                                            <tr data-slot="table-row"     class="border-b border-border transition-colors duration-150 ease-nq hover:bg-nq-hover data-[state=selected]:bg-nq-selected"><th data-slot="table-head" scope="row" class="text-start align-middle text-caption font-medium text-muted-foreground px-4 h-auto w-1/3 max-w-48 py-2 align-top whitespace-normal"><bdi>Tags</bdi></th>
                            <td data-slot="table-cell" class="align-middle px-4 h-auto py-2 whitespace-normal text-foreground"><span class="flex flex-wrap gap-1"><span data-slot="badge"     class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-border text-muted-foreground"><bdi>finance</bdi></span>
<span data-slot="badge"     class="inline-flex h-5 shrink-0 items-center gap-1 whitespace-nowrap rounded-[4px] border px-1.5 text-caption font-medium [&_svg]:size-3 border-border text-muted-foreground"><bdi>report</bdi></span>
</span></td></tr>
                                            <tr data-slot="table-row"     class="border-b border-border transition-colors duration-150 ease-nq hover:bg-nq-hover data-[state=selected]:bg-nq-selected"><th data-slot="table-head" scope="row" class="text-start align-middle text-caption font-medium text-muted-foreground px-4 h-auto w-1/3 max-w-48 py-2 align-top whitespace-normal"><bdi>Published</bdi></th>
                            <td data-slot="table-cell" class="align-middle px-4 h-auto py-2 whitespace-normal text-foreground"><time data-slot="date-time" datetime="2026-09-01T00:00:00+00:00" dir="auto"     class="tabular-nums [unicode-bidi:isolate]">Sep 1, 2026</time></td></tr>
                                            <tr data-slot="table-row"     class="border-b border-border transition-colors duration-150 ease-nq hover:bg-nq-hover data-[state=selected]:bg-nq-selected"><th data-slot="table-head" scope="row" class="text-start align-middle text-caption font-medium text-muted-foreground px-4 h-auto w-1/3 max-w-48 py-2 align-top whitespace-normal"><bdi>Draft</bdi></th>
                            <td data-slot="table-cell" class="align-middle px-4 h-auto py-2 whitespace-normal text-foreground">No</td></tr></tbody>
    </table>
</div>
        </div>
    </section>
        <div data-slot="markdown" class="flex min-w-0 flex-col gap-3 text-body text-nq-fg-body"><div data-slot="markdown-table" x-data="nqMarkdownTable(JSON.parse('{\u0022headers\u0022:[\u0022Region\u0022,\u0022Revenue\u0022],\u0022texts\u0022:[[\u0022Riyadh\u0022,\u0022$12,400\u0022],[\u0022Cairo\u0022,\u0022$8,900\u0022],[\u0022Dubai\u0022,\u0022$100\u0022]],\u0022sortable\u0022:true,\u0022showFilter\u0022:false,\u0022defaultSort\u0022:null,\u0022downloadName\u0022:\u0022table.csv\u0022,\u0022labels\u0022:{\u0022rowCount\u0022:\u0022{shown} of {total} rows\u0022,\u0022noMatch\u0022:\u0022No rows match \\u201c{query}\\u201d.\u0022,\u0022sorted\u0022:\u0022Sorted by {column}, {direction}\u0022,\u0022ascending\u0022:\u0022ascending\u0022,\u0022descending\u0022:\u0022descending\u0022}}'))" x-effect="render()"
    class="flex min-w-0 flex-col gap-2">
            <div class="flex flex-wrap items-center justify-between gap-2">
                            <span></span>
                        <div class="flex items-center gap-2">
                                                    <button data-slot="button"
     type="button"                         x-on:click="download()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M12 15V3"/>
  <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
  <path d="m7 10 5 5 5-5"/>
</svg>Download CSV</button>
                            </div>
        </div>
        <div data-slot="table-container" role="region" tabindex="0"  aria-label="Table"     class="relative w-full overflow-x-auto outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus">
    <table data-slot="table" data-density="default"
                  dir="auto" class="w-full caption-bottom border-collapse text-body-sm">
        <thead data-slot="table-header" class="[&_tr]:border-b [&_tr]:hover:bg-transparent [&_tr]:even:bg-transparent"><tr data-slot="table-row"     class="border-b border-border transition-colors duration-150 ease-nq hover:bg-nq-hover data-[state=selected]:bg-nq-selected"><th data-slot="table-head" scope="col" x-bind:aria-sort="ariaSort(0)" class="h-row align-middle text-caption font-medium whitespace-nowrap text-muted-foreground px-4 py-3 text-start"><button type="button" title="Sort by Region" x-on:click="toggle(0)" x-bind:class="isSorted(0) ? `text-foreground` : ``"
                                class="-mx-1.5 inline-flex items-center gap-1 rounded-control px-1.5 py-1 font-medium outline-none hover:text-foreground focus-visible:outline-2 focus-visible:outline-nq-focus">
                                Region
                                <svg aria-hidden="true" x-show="isIdle(0)" class="size-3 opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m7 15 5 5 5-5"/>
  <path d="m7 9 5-5 5 5"/>
</svg>                                <svg aria-hidden="true" x-show="isUp(0)" style="display: none" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m5 12 7-7 7 7"/>
  <path d="M12 19V5"/>
</svg>                                <svg aria-hidden="true" x-show="isDown(0)" style="display: none" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M12 5v14"/>
  <path d="m19 12-7 7-7-7"/>
</svg>                            </button></th>
                                    <th data-slot="table-head" scope="col" x-bind:aria-sort="ariaSort(1)" class="h-row align-middle text-caption font-medium whitespace-nowrap text-muted-foreground px-4 py-3 text-end"><button type="button" title="Sort by Revenue" x-on:click="toggle(1)" x-bind:class="isSorted(1) ? `text-foreground` : ``"
                                class="-mx-1.5 inline-flex items-center gap-1 rounded-control px-1.5 py-1 font-medium outline-none hover:text-foreground focus-visible:outline-2 focus-visible:outline-nq-focus">
                                Revenue
                                <svg aria-hidden="true" x-show="isIdle(1)" class="size-3 opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m7 15 5 5 5-5"/>
  <path d="m7 9 5-5 5 5"/>
</svg>                                <svg aria-hidden="true" x-show="isUp(1)" style="display: none" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="m5 12 7-7 7 7"/>
  <path d="M12 19V5"/>
</svg>                                <svg aria-hidden="true" x-show="isDown(1)" style="display: none" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M12 5v14"/>
  <path d="m19 12-7 7-7-7"/>
</svg>                            </button></th></tr></thead>
        <tbody data-slot="table-body" class="[&_tr:last-child]:border-0"><tr data-slot="table-row"     data-row="0" class="border-b border-border transition-colors duration-150 ease-nq hover:bg-nq-hover data-[state=selected]:bg-nq-selected"><td data-slot="table-cell" dir="auto" class="align-middle px-4 h-auto whitespace-normal py-2 text-start">Riyadh</td>
                                            <td data-slot="table-cell" dir="auto" class="align-middle px-4 h-auto whitespace-normal py-2 text-end">$12,400</td></tr>
                            <tr data-slot="table-row"     data-row="1" class="border-b border-border transition-colors duration-150 ease-nq hover:bg-nq-hover data-[state=selected]:bg-nq-selected"><td data-slot="table-cell" dir="auto" class="align-middle px-4 h-auto whitespace-normal py-2 text-start">Cairo</td>
                                            <td data-slot="table-cell" dir="auto" class="align-middle px-4 h-auto whitespace-normal py-2 text-end">$8,900</td></tr>
                            <tr data-slot="table-row"     data-row="2" class="border-b border-border transition-colors duration-150 ease-nq hover:bg-nq-hover data-[state=selected]:bg-nq-selected"><td data-slot="table-cell" dir="auto" class="align-middle px-4 h-auto whitespace-normal py-2 text-start">Dubai</td>
                                            <td data-slot="table-cell" dir="auto" class="align-middle px-4 h-auto whitespace-normal py-2 text-end">$100</td></tr>
                        <tr data-slot="table-row"     data-empty="data-empty" style="display: none" class="border-b border-border transition-colors duration-150 ease-nq hover:bg-nq-hover data-[state=selected]:bg-nq-selected"><td data-slot="table-cell" colspan="2" class="align-middle px-4 h-auto py-6 text-center whitespace-normal text-muted-foreground"><span x-text="emptyText"></span></td></tr></tbody>
    </table>
</div>
    <p class="sr-only" role="status" x-text="status"></p>
</div>
<figure data-slot="code-block" data-language="ts" dir="ltr" x-data="nqCodeBlock(JSON.parse('{\u0022code\u0022:\u0022const a = 1;\\nconst b = 2;\u0022,\u0022language\u0022:\u0022ts\u0022}'))" x-bind:data-highlighted="highlighted ? '' : null"
    class="group/code relative m-0 overflow-hidden rounded-surface border border-border bg-nq-surface-soft text-start [--shiki-foreground:var(--nq-fg)] [--shiki-background:transparent] [--shiki-token-comment:var(--nq-fg-muted)] [--shiki-token-keyword:var(--nq-accent-text)] [--shiki-token-string:var(--nq-success-text)] [--shiki-token-string-expression:var(--nq-success-text)] [--shiki-token-constant:var(--nq-warning-text)] [--shiki-token-function:var(--nq-info-text)] [--shiki-token-parameter:var(--nq-fg-body)] [--shiki-token-punctuation:var(--nq-fg-muted)] [--shiki-token-link:var(--nq-info-text)]">
            <figcaption data-slot="code-block-header" class="flex h-row items-center justify-between gap-2 border-b border-border ps-3 pe-1.5 font-mono text-caption text-muted-foreground">
            <span class="truncate">sum.ts</span>
                            <span class="flex items-center gap-0.5" x-data="nqCodeExtras(JSON.parse('{\u0022code\u0022:\u0022const a = 1;\\nconst b = 2;\u0022,\u0022name\u0022:\u0022sum.ts\u0022,\u0022numbers\u0022:true}'))">
                            <button data-slot="button"
     type="button"                         aria-label="Line numbers" title="Line numbers" x-on:click="toggle()" x-bind:aria-pressed="numbers" x-bind:class="numbers ? `bg-nq-selected` : ``" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover size-control-sm p-0">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M11 5h10"/>
  <path d="M11 12h10"/>
  <path d="M11 19h10"/>
  <path d="M4 4h1v5"/>
  <path d="M4 9h2"/>
  <path d="M6.5 20H3.4c0-1 2.6-1.925 2.6-3.5a1.5 1.5 0 0 0-2.6-1.02"/>
</svg></button>
                                        <button data-slot="button"
     type="button"                         aria-label="Download file" title="Download file" x-on:click="save()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover size-control-sm p-0">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M12 15V3"/>
  <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
  <path d="m7 10 5 5 5-5"/>
</svg></button>
                        <span class="contents" x-data="nqCopyButton('const a = 1;\nconst b = 2;', 1500)">
    <button data-slot="copy-button"
     type="button"                         aria-label="Copy" x-on:click="copy()" x-bind:data-copied="state === &#039;copied&#039; ? &#039;&#039; : null" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover size-control-sm p-0 data-copied:text-nq-success-text">
        <svg aria-hidden="true" x-show="state !== 'copied'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <rect width="14" height="14" x="8" y="8" rx="2" ry="2"/>
  <path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>
</svg>        <svg aria-hidden="true" x-show="state === 'copied'" style="display: none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></button>
    <span data-slot="copy-button-status" role="status" aria-live="polite" class="sr-only"
        data-copied-label="Copied to clipboard" data-failed-label="Could not copy"
        x-text="state === 'copied' ? $el.dataset.copiedLabel : state === 'failed' ? $el.dataset.failedLabel : ''"></span>
</span>
        </span>                    </figcaption>
        <pre data-slot="code-block-pre" role="region" tabindex="0" aria-label="sum.ts"
        class="m-0 overflow-auto bg-transparent py-3 font-mono text-code text-[var(--shiki-foreground)] outline-none focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-nq-focus "><code class="block w-max min-w-full"><span data-line="1"  class="flex min-h-[1lh] border-s-2 border-transparent pe-4 whitespace-pre"><span aria-hidden="true" class="inline-block shrink-0 select-none pe-4 ps-3 text-end text-muted-foreground tabular-nums" style="min-width: 4ch">1</span><span data-code-line>const a = 1;</span></span><span data-line="2"  data-highlighted  class="flex min-h-[1lh] border-s-2 border-transparent pe-4 whitespace-pre border-nq-accent bg-nq-selected"><span aria-hidden="true" class="inline-block shrink-0 select-none pe-4 ps-3 text-end text-muted-foreground tabular-nums" style="min-width: 4ch">2</span><span data-code-line>const b = 2;</span></span></code></pre>
</figure>
</div>
</div>
```
