Skip to main content
Gremorie

Code block

Syntax-highlighted code artifact with copy-to-clipboard, streamed by the model.

Overview

A code artifact for when the model produces source code. Built on the CodeBlock primitive from @gremorie/rx-artifacts (Shiki under the hood) with a CodeBlockCopyButton overlay. The model emits { code, language, filename }; the artifact handles highlighting, line wrapping, and the copy affordance.

This is the artifact you want every time the model "writes a snippet" in chat — production-grade markup, accessible copy button, no manual plumbing.

Preview

SaveBar.tsx
Streamed code emitted by the model with Shiki highlighting.
TSX + Shiki

Schema

The LLM returns structured output matching this shape:

{
  type: "code-block",
  code: string,
  language: string,    // "tsx", "ts", "js", "py", "rs", ...
  filename?: string,
}

Anatomy

Card
├─ CardHeader            filename + Badge (outline) — artifact type
└─ CodeBlock             rx-artifacts primitive (Shiki highlight against `code`)
   └─ CodeBlockCopyButton overlay copy-to-clipboard control

Installation

npx gremorie@latest add artifact-code-block
pnpm dlx gremorie@latest add artifact-code-block
yarn dlx gremorie@latest add artifact-code-block
bunx --bun gremorie@latest add artifact-code-block

Prompt examples

Sample prompts that produce valid output for this artifact:

  • "Write me a React save-bar component in TSX."
  • "Show the SQL to create a users table with email + role."
  • "Give me a Python snippet that calls the OpenAI API."

Code

'use client';

import {
  Badge,
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from '@gremorie/rx-display';
import { CodeBlock, CodeBlockCopyButton } from '@gremorie/rx-artifacts';

const CODE = `import { Button } from "@gremorie/rx-forms";

export function SaveBar() {
  return (
    <div className="sticky bottom-0 flex justify-end gap-3 border-t bg-background/90 p-3 backdrop-blur">
      <Button variant="ghost">Cancel</Button>
      <Button>Save changes</Button>
    </div>
  );
}`;

export function CodeBlockArtifact() {
  return (
    <Card>
      <CardHeader>
        <div className="flex items-center justify-between gap-3">
          <div>
            <CardTitle>SaveBar.tsx</CardTitle>
            <CardDescription>
              Streamed code emitted by the model with Shiki highlighting.
            </CardDescription>
          </div>
          <Badge variant="outline">TSX + Shiki</Badge>
        </div>
      </CardHeader>
      <CardContent>
        <CodeBlock code={CODE} language="tsx">
          <CodeBlockCopyButton />
        </CodeBlock>
      </CardContent>
    </Card>
  );
}
npx gremorie@latest add ng-code-block
import { Component } from '@angular/core';
import {
  Badge,
  Card,
  CardHeader,
  CardTitle,
  CardDescription,
  CardContent,
} from '@gremorie/ng-display';
import { CodeBlock, CodeBlockCopyButton } from '@gremorie/ng-artifacts';

@Component({
  selector: 'app-code-artifact',
  standalone: true,
  imports: [
    Badge,
    Card,
    CardHeader,
    CardTitle,
    CardDescription,
    CardContent,
    CodeBlock,
    CodeBlockCopyButton,
  ],
  template: `
    <gr-card class="w-full max-w-2xl">
      <gr-card-header>
        <div class="flex items-center justify-between gap-2">
          <div>
            <gr-card-title>SaveBar.tsx</gr-card-title>
            <gr-card-description>
              Streamed code emitted by the model with Shiki highlighting.
            </gr-card-description>
          </div>
          <gr-badge variant="outline">TSX + Shiki</gr-badge>
        </div>
      </gr-card-header>
      <gr-card-content>
        <code-block [code]="code" language="tsx">
          <code-block-copy-button />
        </code-block>
      </gr-card-content>
    </gr-card>
  `,
})
export class CodeArtifactComponent {
  readonly code = 'import { Button } from "@gremorie/rx-forms";';
}

Streaming behavior

This artifact supports partial schema. As the model streams code, Shiki re-highlights on each chunk so the user sees colorized output appear progressively. The copy button stays armed against the latest buffered value.

On this page