shadimage
afterglow
uicode-blocksource: ok · hit

Code Block

Shiki wired to the phosphor palette, with a copy button. Takes a string of code and highlights it on the server.

npx shadcn add "https://afterglow.thebuilder.dk/r/code-block.json"
free

Visual style

terminal-mono

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
iconslucide-react

Registry dependencies

@afterglow/theme@afterglow/use-copied

Files3 · source cached 10/8/2026, 5:57:56 PM

registry/terminal/lib/shiki.ts
import type { BundledLanguage, ThemeRegistrationRaw } from "shiki";
import { codeToHtml } from "shiki";

// Shiki writes colors straight onto the tokens, so the theme hands it variable
// references instead of hex. A code block then follows the phosphor the page is
// set to, rather than carrying a second palette of its own.
export const CODE_THEME: ThemeRegistrationRaw = {
  colors: {
    "editor.background": "var(--panel-sunken)",
    "editor.foreground": "var(--phosphor)",
  },
  name: "afterglow",
  settings: [
    {
      scope: ["comment", "punctuation.definition.comment"],
      settings: {
        fontStyle: "italic",
        foreground: "var(--phosphor-dim)",
      },
    },
    {
      scope: ["string", "constant.character", "punctuation.definition.string"],
      settings: { foreground: "var(--warning)" },
    },
    {
      scope: [
        "keyword",
        "storage",
        "storage.type",
        "keyword.control",
        "keyword.operator.new",
        "keyword.operator.expression",
        "variable.language",
      ],
      settings: { foreground: "var(--signal)" },
    },
    {
      scope: [
        "constant.numeric",
        "constant.language",
        "constant.language.boolean",
      ],
      settings: { foreground: "var(--violet)" },
    },
    {
      scope: [
        "entity.name.type",
        "entity.name.class",
        "entity.name.tag",
        "support.type",
        "support.class",
        "support.type.primitive",
      ],
      settings: { foreground: "var(--info)" },
    },
    {
      scope: [
        "entity.name.function",
        "support.function",
        "meta.function-call.generic",
      ],
      settings: { foreground: "var(--phosphor-bright)" },
    },
    {
      scope: [
        "punctuation",
        "meta.brace",
        "keyword.operator",
        "entity.other.attribute-name",
      ],
      settings: { foreground: "var(--muted-foreground)" },
    },
  ],
  type: "dark",
};

// Shiki's shorthand keeps one highlighter for the process and loads a grammar
// the first time a block asks for it.
export function highlightCode(
  code: string,
  lang: BundledLanguage
): Promise<string> {
  return codeToHtml(code, { lang, theme: CODE_THEME });
}
registry/terminal/ui/code-block.tsx
import type * as React from "react";
import type { BundledLanguage } from "shiki";

import { cn } from "@/lib/utils";
import { highlightCode } from "@/registry/terminal/lib/shiki";
import { CodeBlockCopy } from "@/registry/terminal/ui/code-block-copy";

async function CodeBlock({
  className,
  code,
  label,
  lang = "tsx",
  title,
  ...props
}: Omit<React.ComponentProps<"figure">, "children"> & {
  code: string;
  label?: string;
  lang?: BundledLanguage;
  title?: string;
}) {
  const text = code.trimEnd();

  return (
    <figure
      className={cn("border border-line bg-panel-sunken", className)}
      data-slot="code-block"
      {...props}
    >
      {title ? <CodeBlockTitle>{title}</CodeBlockTitle> : null}
      <CodeBlockBody
        html={await highlightCode(text, lang)}
        label={label ?? (title ? `Copy ${title}` : undefined)}
        text={text}
      />
    </figure>
  );
}

function CodeBlockTitle({
  className,
  ...props
}: React.ComponentProps<"figcaption">) {
  return (
    <figcaption
      className={cn(
        "border-line border-b px-3 py-2 font-mono text-2xs text-phosphor-dim tracking-terminal-xs",
        className
      )}
      data-slot="code-block-title"
      {...props}
    />
  );
}

function CodeBlockBody({
  className,
  html,
  label,
  text,
  ...props
}: Omit<React.ComponentProps<"div">, "children"> & {
  html: string;
  label?: string;
  text: string;
}) {
  return (
    <div
      className={cn("relative", className)}
      data-slot="code-block-body"
      {...props}
    >
      <div
        className="[&_pre]:!bg-transparent overflow-x-auto py-3.5 pr-12 pl-4 font-mono text-xs leading-relaxed [&_pre]:outline-none"
        // oxlint-disable-next-line react/no-danger -- Shiki output for code the caller passed in.
        dangerouslySetInnerHTML={{ __html: html }}
      />
      <CodeBlockCopy
        className="absolute top-2.5 right-2.5"
        label={label}
        text={text}
      />
    </div>
  );
}

export { CodeBlock, CodeBlockBody, CodeBlockTitle };
registry/terminal/ui/code-block-copy.tsx
"use client";

import { CheckIcon, CopyIcon } from "lucide-react";
import type * as React from "react";

import { cn } from "@/lib/utils";
import { useCopied } from "@/registry/terminal/hooks/use-copied";

function CodeBlockCopy({
  className,
  label = "Copy the code",
  text,
  ...props
}: Omit<React.ComponentProps<"button">, "children"> & {
  label?: string;
  text: string;
}) {
  const { copied, copy } = useCopied(text);

  return (
    <button
      aria-label={copied ? "Copied" : label}
      className={cn(
        "grid size-7 shrink-0 place-items-center border border-line bg-panel-sunken text-phosphor-dim outline-none transition-colors hover:border-line-strong hover:text-phosphor-bright focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-phosphor-bright",
        className
      )}
      data-slot="code-block-copy"
      onClick={copy}
      type="button"
      {...props}
    >
      {copied ? (
        <CheckIcon className="size-3.5 text-phosphor" />
      ) : (
        <CopyIcon className="size-3.5" />
      )}
    </button>
  );
}

export { CodeBlockCopy };

More from afterglow

Browse all components in this library →