Code
Free · MITHeadless, accessible, and styled entirely through the attribute surface below — bring your own CSS system, or start from the reference theme.
Install
Without a flag you get no styling at all — plain HTML carrying the attribute surface below. --theme copies the same theme in two dialects;--bridge lumos re-points either one onto Lumos for Astro's tokens (it needs a theme to re-point, and Lumos itself, already in your project).
Unstyled — behavior only, you write the CSS
npx astrocraft-ui add display/codePlain-CSS theme — no build step
npx astrocraft-ui add display/code --theme cssTailwind theme — needs Tailwind v4
npx astrocraft-ui add display/code --theme tailwindPlain-CSS theme, re-pointed onto Lumos tokens
npx astrocraft-ui add display/code --theme css --bridge lumosTailwind theme, re-pointed onto Lumos tokens
npx astrocraft-ui add display/code --theme tailwind --bridge lumosLive demo
Code, CodeBlock, Kbd
The block is focusable so it can be scrolled without a mouse, and the copy button announces its result through a live region outside itself — inside, its text would become part of the button's own name. Press Ctrl + C or use the button; on an insecure origin the clipboard is refused and it says so rather than doing nothing.
Inline, it looks like this: run pnpm headless before you call a change done.
pnpm add astrocraft-ui
import "astrocraft-ui/styles/structure.css";Attribute surface
With no class-merging machinery, these attributes are the whole extension point. State is a data-attribute or a native attribute; visibility is the hiddenattribute. Generated from source by pnpm slots.
| Component | Slots | Variants | Runtime state | Native state |
|---|---|---|---|---|
| `Code.astro` | `code` | — | — | — |
| `CodeBlock.astro` | `code-block` `code-block-code` | `data-wrap`: `true` when set | — | — |
| `CodeCopyButton.astro` | `code-copy-button` `code-copy-status` | — | `data-copied` | `[hidden]` |
| `Kbd.astro` | `kbd` | `data-depth`: `single` · `chord` | — | — |
Source
What the command copies — 5 files, alias-free so the copy lands standing alone. This is the whole component, shown without its comments; the files you install keep them, with the reasoning behind every rule.
---
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"code">;
const { class: className, ...rest } = Astro.props;
---
<code class={className} data-slot="code" {...rest}><slot /></code>
---
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"pre"> & {
label?: string;
wrap?: boolean;
};
const { label, wrap = false, class: className, ...rest } = Astro.props;
---
<pre
tabindex="0"
role={label ? "region" : undefined}
aria-label={label}
class={className}
data-slot="code-block"
data-wrap={wrap ? "true" : undefined}
{...rest}><code data-slot="code-block-code"><slot /></code></pre>
---
import "../../../../styles/structure.css";
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"button"> & {
for?: string;
label?: string;
copiedText?: string;
failedText?: string;
};
const {
for: controls,
label = "Copy code",
copiedText = "Copied",
failedText = "Copy failed",
class: className,
...rest
} = Astro.props;
---
<button
type="button"
aria-controls={controls}
aria-label={Astro.slots.has("default") ? undefined : label}
class={className}
data-slot="code-copy-button"
data-copied-text={copiedText}
data-failed-text={failedText}
{...rest}
>
<slot />
</button>
<span role="status" aria-live="polite" data-slot="code-copy-status"></span>
<script>
import { onReadyOnce } from "../../_once";
const FEEDBACK_MS = 2000;
function blockFor(button: HTMLElement): HTMLElement | null {
const id = button.getAttribute("aria-controls");
if (id) return document.getElementById(id);
for (let el = button.parentElement; el; el = el.parentElement) {
const found = el.querySelector<HTMLElement>('[data-slot="code-block"]');
if (found) return found;
}
return null;
}
function wire(button: HTMLElement) {
const sibling = button.nextElementSibling;
const status =
sibling instanceof HTMLElement && sibling.dataset.slot === "code-copy-status"
? sibling
: null;
let clear: ReturnType<typeof setTimeout> | undefined;
const report = (ok: boolean) => {
button.dataset.copied = String(ok);
if (status) {
status.textContent = "";
status.textContent = (ok ? button.dataset.copiedText : button.dataset.failedText) ?? "";
}
clearTimeout(clear);
clear = setTimeout(() => delete button.dataset.copied, FEEDBACK_MS);
};
button.addEventListener("click", async () => {
const block = blockFor(button);
if (!block || !navigator.clipboard) return report(false);
try {
await navigator.clipboard.writeText(block.textContent ?? "");
report(true);
} catch {
report(false);
}
});
}
onReadyOnce('[data-slot="code-copy-button"]', wire);
</script>
---
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"kbd"> & { depth?: "single" | "chord" };
const { depth = "single", class: className, ...rest } = Astro.props;
---
<kbd class={className} data-slot="kbd" data-depth={depth} {...rest}><slot /></kbd>
import Code from "./Code.astro";
import CodeBlock from "./CodeBlock.astro";
import CodeCopyButton from "./CodeCopyButton.astro";
import Kbd from "./Kbd.astro";
export { Code, CodeBlock, CodeCopyButton, Kbd };
export default Code;
What you get
The component source, copied into your project by npx astrocraft-ui add display/code — no package dependency, no CSS to fight. Whatever the paid blocks compose, this is it.