Artifact Renderer
Generative UI from a schema. Turns an agent's JSON into a card, table, chart, Markdown, code, stats, action buttons or picker, validated and safe. HTML is off by default and only shown in a sandboxed frame.
Code
<ArtifactRenderer artifact={{ kind: "table", title: "Overdue invoices", columns: [{ key: "n", label: "No." }, { key: "amt", label: "Amount", align: "end" }], rows: [{ n: "INV-1", amt: 1200 }] }} onAction={(id, artifact) => run(id)} onPick={(values, artifact) => choose(values)}/>;AI Assistant · beta
Live examples and controls: Artifact Renderer in the lab.
Install
npx shadcn@latest add https://docs.nasaqui.com/r/artifact-renderer.jsonAn agent answers with data, not markup. You hand its JSON to ArtifactRenderer, it checks the shape, and renders a
Nasaq component. Anything it does not understand becomes a small warning instead of a crash or an injection.
When to use
- Agent or copilot output that should be a table, chart, card, picker or set of buttons instead of prose.
- Answers that contain fenced
```artifactblocks, whichextractArtifactspulls out.
When not to use
- Plain answers: render Markdown with
Markdown. - UI you author yourself. Use the real components directly.
Import
import { ArtifactRenderer, ArtifactList, extractArtifacts } from "@fadymondy/nasaq/web";Quick start
<ArtifactRenderer
artifact={{ kind: "table", title: "Overdue invoices", columns: [{ key: "n", label: "No." }, { key: "amt", label: "Amount", align: "end" }], rows: [{ n: "INV-1", amt: 1200 }] }}
onAction={(id, artifact) => run(id)}
onPick={(values, artifact) => choose(values)}
/>;Answer with blocks in it:
const { text, artifacts } = extractArtifacts(answer);
<Markdown>{text}</Markdown>
<ArtifactList artifacts={artifacts} />Kinds
kind | Fields |
|---|---|
card | title, description, badges, fields, items (label, description, value, tone), body (Markdown, string or { en, ar }), footer (a muted note), actions |
table | columns (key, label, align), rows |
chart | chart (bar, line, area, pie, donut), xKey, series (key, label, color), data. Pie and donut use the first series as slice sizes; past 8 slices the smallest fold into "Other" (pieSlices). |
markdown | text |
code | code, language, filename (shown with CodeBlock) |
stats | items (label, value, delta, invert, tone, sparkline: up to 60 numbers) |
actions | actions (id, label, variant, confirm) |
picker | mode (single, multiple), options, defaultValue, submitLabel |
html | html, height. See Safety. |
Text fields take a string or { en, ar }. The older title_en and title_ar keys are accepted.
API
| Prop | Type | Notes |
|---|---|---|
artifact | unknown | Validated. ArtifactView takes an already typed Artifact instead. |
allowHtml | boolean | Default false. See Safety. |
onAction | (actionId, artifact) => void or Promise<{ error? }> | Buttons. An action with confirm asks in a dialog first. |
onPick | (values, artifact) => void or Promise<{ error? }> | Picker submit. |
labels | Partial<ArtifactRendererLabels> |
parseArtifact(input) returns { ok: true, artifact } or { ok: false, error, kind? } and never throws.
Safety
- The renderer builds React elements from validated data. It never uses
dangerouslySetInnerHTML. - Limits apply: 200 rows, 20 columns, 8 series, 200 points, 50 options, 8 actions.
- Colours must be
var(--token). Hex values,url()and expressions are dropped. - Series keys are remapped before they become CSS variable names, so a key cannot inject CSS.
- Markdown and card bodies go through
Markdown, which drops raw HTML. - A
toneshows as a dot with the tone named for screen readers ("Critical: …"), so it never relies on colour alone. htmlis shown as a code block unlessallowHtmlis set. Then it goes in<iframe sandbox="" srcDoc>: no scripts, no same-origin access, no forms, and a Content Security Policy that blocks network. Only enable it for output you would show in a preview, and never give the frameallow-same-origin.- Links open with
rel="noopener noreferrer". Onlyhttp,httpsandmailtoare followed.
Accessibility
- Tables are real tables with header cells. Charts have a text title and a legend.
- Picker uses a radio group or checkboxes with the option label and description associated.
- Action confirmation is a focus-trapping dialog. A sent picker announces "Sent".
RTL and i18n
- Labels come from
useOptionalNasaq()and the{ en, ar }fields pick by locale. - Table alignment uses
startandend. Charts flip the x axis in RTL via the chart primitives. - Numbers use the locale digits through
Num.
Styling and tokens
Everything is a Nasaq component, so tokens are inherited. Series colours default to the --nq-chart-* palette.
Do / Don't
- Do validate on every render path.
ArtifactRendererdoes it for you. - Do keep
allowHtmloff unless a sandboxed preview is the point. - Don't pass model output to
ArtifactViewwithoutparseArtifact. - Don't add a kind that runs code.
Related
AI States
The states of an AI feature. Smart actions (sparkle button, split button, suggestion chips, Cmd or Ctrl J menu), loading (thinking indicator, shimmer, step labels), generating (streaming text with a caret, stop and regenerate, safe partial Markdown) and summarized (TL;DR card with key points, sources, confidence, feedback).
Ask AI and Insight Card
Ask AI about selected text with a popover that appears on selection, quick actions and a question box. Plus an inline AI insight card with finding, metric, reasoning, sources, confidence and next actions.