Data table
Filterable table artifact that renders JSON rows with sort and search.
Overview
A data artifact for when the model produces tabular results. Emits
{ columns, rows }; the artifact renders the Table primitive from
@gremorie/rx-display, wires an Input filter at the top, and
applies status formatting via Badge.
This artifact is what you want any time the model "fetches a list" — team members, transactions, search results — and needs to expose them with a real, accessible table.
Preview
| ID | Name | Role | Status |
|---|---|---|---|
| U-001 | Olivia Martin | Owner | Active |
| U-002 | Jackson Lee | Editor | Active |
| U-003 | Isabella Nguyen | Viewer | Invited |
| U-004 | William Kim | Editor | Suspended |
| U-005 | Sofia Davis | Viewer | Active |
Schema
The LLM returns structured output matching this shape:
{
type: "data-table",
columns: Array<{ key: string; label: string; format?: "badge" | "mono" }>,
rows: Array<Record<string, string | number>>,
}Anatomy
Card
├─ CardHeader title + Badge + filter Input (lucide Search icon)
└─ Table rx-display
├─ TableHeader
└─ TableBody rows with status Badge; empty-row fallback when filtered outInstallation
npx gremorie@latest add artifact-data-tablepnpm dlx gremorie@latest add artifact-data-tableyarn dlx gremorie@latest add artifact-data-tablebunx --bun gremorie@latest add artifact-data-tablePrompt examples
Sample prompts that produce valid output for this artifact:
- "List the top 5 customers by revenue with their plan and status."
- "Show me my team members with their role and last activity."
- "Display the most recent 10 support tickets with priority."
Code
'use client';
import { useState } from 'react';
import {
Badge,
Card,
CardContent,
CardHeader,
CardTitle,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@gremorie/rx-display';
import { Input } from '@gremorie/rx-forms';
import { Search } from 'lucide-react';
type Row = {
id: string;
name: string;
role: string;
status: 'Active' | 'Invited' | 'Suspended';
};
const ROWS: Row[] = [
{ id: 'U-001', name: 'Olivia Martin', role: 'Owner', status: 'Active' },
// ...
];
export function DataTable() {
const [query, setQuery] = useState('');
const filtered = ROWS.filter((r) =>
r.name.toLowerCase().includes(query.toLowerCase()),
);
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between gap-3">
<CardTitle>Team members</CardTitle>
<Badge variant="outline">JSON rows</Badge>
</div>
<div className="relative mt-2">
<Search
className="absolute left-2.5 top-1/2 size-4 -translate-y-1/2 text-muted-foreground"
aria-hidden="true"
/>
<Input
placeholder="Filter members..."
value={query}
onChange={(e) => setQuery(e.target.value)}
className="pl-8"
/>
</div>
</CardHeader>
<CardContent>
<Table>
<TableHeader>
<TableRow>
<TableHead>ID</TableHead>
<TableHead>Name</TableHead>
<TableHead>Role</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filtered.map((row) => (
<TableRow key={row.id}>
<TableCell className="font-mono text-xs">{row.id}</TableCell>
<TableCell className="font-medium">{row.name}</TableCell>
<TableCell>{row.role}</TableCell>
<TableCell>
<Badge variant="default">{row.status}</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
);
}The Angular edition of this component is planned; the React edition is production-ready today.
Streaming behavior
This artifact renders after the JSON payload parses. Streaming row appends are buffered and flushed in batches to avoid jank in long result sets.