ai-generative-ui
Data-driven generative UI — tool results render as rich React components in chat instead of raw JSON. Uses a registry pattern with _ui field, not createStreamableUI(). Use this skill when the user says "generative ui", "rich tool cards", "custom tool rendering", or "tool components".
Best use case
ai-generative-ui is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Data-driven generative UI — tool results render as rich React components in chat instead of raw JSON. Uses a registry pattern with _ui field, not createStreamableUI(). Use this skill when the user says "generative ui", "rich tool cards", "custom tool rendering", or "tool components".
Teams using ai-generative-ui should expect a more consistent output, faster repeated execution, less prompt rewriting, better workflow continuity with your supporting tools.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
- You already have the supporting tools or dependencies needed by this skill.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/ai-generative-ui/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How ai-generative-ui Compares
| Feature / Agent | ai-generative-ui | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Data-driven generative UI — tool results render as rich React components in chat instead of raw JSON. Uses a registry pattern with _ui field, not createStreamableUI(). Use this skill when the user says "generative ui", "rich tool cards", "custom tool rendering", or "tool components".
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# AI Generative UI
Experience layer that renders tool results as interactive React components inside the chat stream instead of raw JSON. Uses a data-driven approach: tool `execute` functions return a `_ui` field that names a registered component, and the client-side renderer looks it up in a registry.
This does **not** use `createStreamableUI()` (that is an older RSC pattern incompatible with the current architecture). Instead, tool results are plain data objects with a `_ui` type hint that the client uses to select the appropriate React component.
## Prerequisites
- Next.js app with `src/` directory and App Router
- `ai-core` skill installed (provides `getModel()`)
- `ai-chat` skill installed (provides chat UI, route pipeline, message renderer)
- `ai-tools` skill installed (provides tool calling framework and `tool-invocation` rendering)
## Installation
No additional packages required. Uses `ai`, `zod`, and `@ai-sdk/react` already installed by prerequisite skills.
## What Gets Created
```
src/
├── lib/
│ └── ai/
│ └── ui-registry.ts # Component registry — maps tool _ui values to React components
└── components/
└── ai/
└── gen-ui/
├── weather-card.tsx # Example: weather display with temperature + conditions
├── data-card.tsx # Example: structured key-value display card
└── confirmation.tsx # Example: interactive yes/no confirmation card
```
## What Gets Modified
```
src/
├── app/
│ └── api/
│ └── ai/
│ └── chat/
│ └── route.ts # Tool execute functions return _ui field
└── components/
└── ai/
└── message.tsx # Check tool-result for _ui field, render registered component
```
## Comment Slots
- **message.tsx**: `// [ai-generative-ui]: check for _ui field` — checks tool results for `_ui` field and renders registered component
- **message.tsx**: `// [ai-generative-ui]: import gen-ui components to trigger registration` — side-effect imports that register components
## Setup Steps
### Step 1: Create `src/lib/ai/ui-registry.ts`
```typescript
import { type ComponentType } from "react";
/**
* Registry mapping `_ui` field values from tool results to React components.
*
* When a tool's `execute` function returns `{ ...data, _ui: "WeatherCard" }`,
* the message renderer looks up "WeatherCard" in this registry and renders
* the matched component with the tool result as the `data` prop.
*
* If no component is found, the renderer falls back to the default JSON
* tool result card from ai-tools.
*/
type GenUIProps<T = Record<string, unknown>> = {
data: T;
};
type GenUIComponent = ComponentType<GenUIProps>;
const registry = new Map<string, GenUIComponent>();
/**
* Register a component for a given _ui key.
* Call this at module scope in your component files.
*/
export function registerUIComponent(
key: string,
component: GenUIComponent
): void {
registry.set(key, component);
}
/**
* Look up a component by _ui key.
* Returns undefined if no component is registered.
*/
export function getUIComponent(
key: string
): GenUIComponent | undefined {
return registry.get(key);
}
/**
* Check if a tool result has a _ui field that maps to a registered component.
*/
export function hasUIComponent(result: unknown): result is {
_ui: string;
[key: string]: unknown;
} {
return (
typeof result === "object" &&
result !== null &&
"_ui" in result &&
typeof (result as Record<string, unknown>)._ui === "string" &&
registry.has((result as Record<string, unknown>)._ui as string)
);
}
export type { GenUIProps, GenUIComponent };
```
### Step 2: Create `src/components/ai/gen-ui/weather-card.tsx`
```tsx
"use client";
import { memo } from "react";
import { registerUIComponent, type GenUIProps } from "@/lib/ai/ui-registry";
type WeatherData = {
location: string;
temperature: number;
unit: string;
conditions: string;
humidity: number;
windSpeed: number;
windUnit: string;
feelsLike: number;
_ui: string;
};
function getWeatherIcon(conditions: string): string {
const lower = conditions.toLowerCase();
if (lower.includes("sun") || lower.includes("clear")) return "sun";
if (lower.includes("cloud") && lower.includes("part"))
return "cloud-sun";
if (lower.includes("cloud")) return "cloud";
if (lower.includes("rain") || lower.includes("drizzle"))
return "cloud-rain";
if (lower.includes("snow")) return "snowflake";
if (lower.includes("thunder") || lower.includes("storm"))
return "cloud-lightning";
if (lower.includes("fog") || lower.includes("mist")) return "cloud-fog";
return "thermometer";
}
function WeatherIcon({ conditions }: { conditions: string }) {
const icon = getWeatherIcon(conditions);
const icons: Record<string, React.ReactNode> = {
sun: (
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-yellow-500"
>
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2" />
<path d="M12 20v2" />
<path d="m4.93 4.93 1.41 1.41" />
<path d="m17.66 17.66 1.41 1.41" />
<path d="M2 12h2" />
<path d="M20 12h2" />
<path d="m6.34 17.66-1.41 1.41" />
<path d="m19.07 4.93-1.41 1.41" />
</svg>
),
cloud: (
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-gray-400"
>
<path d="M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9Z" />
</svg>
),
"cloud-rain": (
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-blue-400"
>
<path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242" />
<path d="M16 14v6" />
<path d="M8 14v6" />
<path d="M12 16v6" />
</svg>
),
thermometer: (
<svg
xmlns="http://www.w3.org/2000/svg"
width="32"
height="32"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-orange-400"
>
<path d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0Z" />
</svg>
),
};
return <>{icons[icon] ?? icons.thermometer}</>;
}
const WeatherCard = memo(function WeatherCard({ data }: GenUIProps<WeatherData>) {
return (
<div className="overflow-hidden rounded-xl border bg-gradient-to-br from-blue-50 to-sky-50 dark:from-blue-950 dark:to-sky-950">
<div className="p-4">
{/* Location + Icon row */}
<div className="flex items-start justify-between">
<div>
<p className="text-sm font-medium text-muted-foreground">
{data.location}
</p>
<p className="mt-1 text-3xl font-bold tracking-tight">
{Math.round(data.temperature)}{data.unit === "celsius" ? "\u00B0C" : "\u00B0F"}
</p>
</div>
<WeatherIcon conditions={data.conditions} />
</div>
{/* Conditions */}
<p className="mt-1 text-sm capitalize text-muted-foreground">
{data.conditions}
</p>
{/* Details row */}
<div className="mt-4 grid grid-cols-3 gap-3 border-t pt-3">
<div>
<p className="text-xs text-muted-foreground">Feels like</p>
<p className="text-sm font-medium">
{Math.round(data.feelsLike)}{data.unit === "celsius" ? "\u00B0" : "\u00B0"}
</p>
</div>
<div>
<p className="text-xs text-muted-foreground">Humidity</p>
<p className="text-sm font-medium">{data.humidity}%</p>
</div>
<div>
<p className="text-xs text-muted-foreground">Wind</p>
<p className="text-sm font-medium">
{data.windSpeed} {data.windUnit}
</p>
</div>
</div>
</div>
</div>
);
});
// Register the component so the message renderer can find it
registerUIComponent("WeatherCard", WeatherCard as unknown as React.ComponentType<GenUIProps>);
export { WeatherCard };
```
### Step 3: Create `src/components/ai/gen-ui/data-card.tsx`
```tsx
"use client";
import { useId, memo } from "react";
import { registerUIComponent, type GenUIProps } from "@/lib/ai/ui-registry";
type DataCardData = {
title: string;
subtitle?: string;
fields: Array<{
label: string;
value: string | number | boolean;
type?: "text" | "number" | "badge" | "link";
}>;
_ui: string;
};
const DataCard = memo(function DataCard({ data }: GenUIProps<DataCardData>) {
const fieldId = useId();
return (
<div className="overflow-hidden rounded-xl border">
{/* Header */}
<div className="border-b bg-muted/50 px-4 py-3">
<h3 className="text-sm font-semibold">{data.title}</h3>
{data.subtitle && (
<p className="mt-0.5 text-xs text-muted-foreground">
{data.subtitle}
</p>
)}
</div>
{/* Fields */}
<div className="divide-y">
{data.fields.map((field) => (
<div
key={`${fieldId}-${field.label}`}
className="flex items-center justify-between px-4 py-2.5"
>
<span className="text-sm text-muted-foreground">
{field.label}
</span>
<span className="text-sm font-medium">
{field.type === "badge" ? (
<span className="inline-flex items-center rounded-full bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary">
{String(field.value)}
</span>
) : field.type === "link" ? (
<a
href={String(field.value)}
target="_blank"
rel="noopener noreferrer"
className="text-primary underline-offset-4 hover:underline"
>
{String(field.value)}
</a>
) : (
String(field.value)
)}
</span>
</div>
))}
</div>
</div>
);
});
registerUIComponent("DataCard", DataCard as unknown as React.ComponentType<GenUIProps>);
export { DataCard };
```
### Step 4: Create `src/components/ai/gen-ui/confirmation.tsx`
```tsx
"use client";
import { useState, useCallback, memo } from "react";
import { registerUIComponent, type GenUIProps } from "@/lib/ai/ui-registry";
type ConfirmationData = {
title: string;
description: string;
confirmLabel?: string;
cancelLabel?: string;
callbackUrl?: string;
payload?: Record<string, unknown>;
_ui: string;
};
type ConfirmationState = "pending" | "confirmed" | "cancelled" | "loading";
const Confirmation = memo(function Confirmation({ data }: GenUIProps<ConfirmationData>) {
const [state, setState] = useState<ConfirmationState>("pending");
const handleAction = useCallback(
async (action: "confirmed" | "cancelled") => {
if (!data.callbackUrl) {
setState(action);
return;
}
setState("loading");
try {
await fetch(data.callbackUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action,
...(data.payload ?? {}),
}),
});
setState(action);
} catch {
setState(action);
}
},
[data.callbackUrl, data.payload]
);
if (state === "confirmed") {
return (
<div className="flex items-center gap-2 rounded-xl border border-green-200 bg-green-50 px-4 py-3 dark:border-green-800 dark:bg-green-950">
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-green-600 dark:text-green-400"
>
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
<polyline points="22 4 12 14.01 9 11.01" />
</svg>
<p className="text-sm font-medium text-green-700 dark:text-green-300">
Confirmed: {data.title}
</p>
</div>
);
}
if (state === "cancelled") {
return (
<div className="flex items-center gap-2 rounded-xl border border-gray-200 bg-gray-50 px-4 py-3 dark:border-gray-700 dark:bg-gray-900">
<svg
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-gray-400"
>
<circle cx="12" cy="12" r="10" />
<path d="m15 9-6 6" />
<path d="m9 9 6 6" />
</svg>
<p className="text-sm text-muted-foreground">
Cancelled: {data.title}
</p>
</div>
);
}
return (
<div className="overflow-hidden rounded-xl border">
<div className="p-4">
<h3 className="text-sm font-semibold">{data.title}</h3>
<p className="mt-1 text-sm text-muted-foreground">
{data.description}
</p>
</div>
<div className="flex gap-2 border-t bg-muted/30 px-4 py-3">
<button
type="button"
onClick={() => handleAction("confirmed")}
disabled={state === "loading"}
className="inline-flex items-center justify-center rounded-lg bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50"
>
{state === "loading" ? (
<span className="flex items-center gap-2">
<span className="h-3.5 w-3.5 animate-spin rounded-full border-2 border-primary-foreground border-t-transparent" />
Processing...
</span>
) : (
data.confirmLabel ?? "Confirm"
)}
</button>
<button
type="button"
onClick={() => handleAction("cancelled")}
disabled={state === "loading"}
className="inline-flex items-center justify-center rounded-lg border px-4 py-2 text-sm font-medium transition-colors hover:bg-muted disabled:opacity-50"
>
{data.cancelLabel ?? "Cancel"}
</button>
</div>
</div>
);
});
registerUIComponent("Confirmation", Confirmation as unknown as React.ComponentType<GenUIProps>);
export { Confirmation };
```
### Step 5: Modify `src/components/ai/message.tsx`
Update the `tool-invocation` case to check for a `_ui` field on tool results **before** falling back to the default tool card. This must be inserted before any existing artifact check or the default `ToolInvocationCard`.
Find this in `src/components/ai/message.tsx`:
```typescript
case "tool-invocation": {
return (
<ToolInvocationCard
key={`${partId}-${index}`}
part={part}
/>
);
}
```
Replace with:
```typescript
case "tool-invocation": {
// [ai-generative-ui]: check for _ui field → render registered component
if (
part.state === "output-available" &&
part.output &&
hasUIComponent(part.output)
) {
const Component = getUIComponent(part.output._ui);
if (Component) {
return (
<Component
key={`${partId}-${index}`}
data={part.output as Record<string, unknown>}
/>
);
}
}
return (
<ToolInvocationCard
key={`${partId}-${index}`}
part={part}
/>
);
}
```
Add the imports at the top of the file. Find:
```typescript
import { useId } from "react";
```
Replace with:
```typescript
import { useId } from "react";
import { hasUIComponent, getUIComponent } from "@/lib/ai/ui-registry";
// [ai-generative-ui]: import gen-ui components to trigger registration
import "@/components/ai/gen-ui/weather-card";
import "@/components/ai/gen-ui/data-card";
import "@/components/ai/gen-ui/confirmation";
```
**Important:** If `ai-artifacts` is also installed, the generative UI check should come **before** the artifact check, since `_ui` is a more specific match. The combined case would look like:
```typescript
case "tool-invocation": {
// [ai-generative-ui]: check for _ui field → render registered component
if (
part.state === "output-available" &&
part.output &&
hasUIComponent(part.output)
) {
const Component = getUIComponent(part.output._ui);
if (Component) {
return (
<Component
key={`${partId}-${index}`}
data={part.output as Record<string, unknown>}
/>
);
}
}
// [ai-artifacts]: render artifact refs as clickable cards
if (
part.state === "output-available" &&
part.output &&
typeof part.output === "object" &&
"_artifact" in part.output
) {
const artifactResult = part.output as {
id: string;
type: string;
title: string;
_update?: boolean;
};
return (
<ArtifactCard
key={`${partId}-${index}`}
id={artifactResult.id}
type={artifactResult.type}
title={artifactResult.title}
isUpdate={!!artifactResult._update}
onClick={onArtifactClick}
/>
);
}
return (
<ToolInvocationCard
key={`${partId}-${index}`}
part={part}
/>
);
}
```
### Step 6: Modify `src/app/api/ai/chat/route.ts`
Update existing tool definitions to include `_ui` fields in their return values. This example shows how to add `_ui` to the weather tool (from ai-tools). Apply the same pattern to any tool that should render a custom component.
Find this in `src/lib/ai/tools/index.ts` (or wherever the weather tool is defined):
```typescript
export const weather = tool({
description: "Get current weather for a location",
parameters: z.object({
location: z.string().describe("City name or location"),
}),
execute: async ({ location }) => {
```
After the existing `execute` return statement, ensure it returns a `_ui` field:
```typescript
execute: async ({ location }) => {
// ... existing weather fetch logic ...
return {
location,
temperature: data.temperature,
unit: "celsius",
conditions: data.conditions,
humidity: data.humidity,
windSpeed: data.windSpeed,
windUnit: "km/h",
feelsLike: data.feelsLike,
_ui: "WeatherCard",
};
},
```
For tools that should render a structured data card, return `_ui: "DataCard"`:
```typescript
execute: async (params) => {
// ... compute result ...
return {
title: "Calculation Result",
fields: [
{ label: "Expression", value: params.expression },
{ label: "Result", value: result, type: "number" },
],
_ui: "DataCard",
};
},
```
For tools that need user confirmation before proceeding, return `_ui: "Confirmation"`:
```typescript
execute: async (params) => {
return {
title: "Delete all completed tasks?",
description: "This will permanently remove 5 completed tasks.",
confirmLabel: "Delete",
cancelLabel: "Keep",
callbackUrl: "/api/ai/sessions/tasks/bulk-delete",
payload: { status: "done" },
_ui: "Confirmation",
};
},
```
## Usage
### Adding Custom Generative UI Components
To create a new generative UI component:
1. **Create the component** in `src/components/ai/gen-ui/`:
```tsx
"use client";
import { registerUIComponent, type GenUIProps } from "@/lib/ai/ui-registry";
type StockData = {
symbol: string;
price: number;
change: number;
changePercent: number;
_ui: string;
};
function StockCard({ data }: GenUIProps<StockData>) {
const isPositive = data.change >= 0;
return (
<div className="rounded-xl border p-4">
<div className="flex items-center justify-between">
<span className="font-bold">{data.symbol}</span>
<span className={isPositive ? "text-green-600" : "text-red-600"}>
{isPositive ? "+" : ""}{data.changePercent.toFixed(2)}%
</span>
</div>
<p className="text-2xl font-bold">${data.price.toFixed(2)}</p>
</div>
);
}
registerUIComponent("StockCard", StockCard as unknown as React.ComponentType<GenUIProps>);
export { StockCard };
```
1. **Import the component** in `message.tsx` to trigger registration:
```typescript
import "@/components/ai/gen-ui/stock-card";
```
1. **Return `_ui` from the tool** in your tool's `execute` function:
```typescript
return { symbol: "AAPL", price: 182.5, change: 3.2, changePercent: 1.78, _ui: "StockCard" };
```
### Fallback Behavior
When a tool result contains a `_ui` field but no component is registered for that key, or when the result has no `_ui` field at all, the renderer falls back to the default `ToolInvocationCard` from ai-tools (which shows the tool name, parameters, and JSON result in a collapsible card).
This means you can incrementally add generative UI components. Tools without `_ui` fields continue to work exactly as before.
## Acceptance Criteria
- Ask "what's the weather in Tokyo" -- WeatherCard component renders with temperature, conditions, humidity, and wind (not raw JSON)
- Ask a math question that uses the calculator tool -- default tool card renders (calculator has no `_ui` field or no registered component)
- The DataCard component renders structured key-value pairs when a tool returns `_ui: "DataCard"`
- The Confirmation component renders confirm/cancel buttons and transitions to confirmed/cancelled state on click
- Unregistered `_ui` values fall back gracefully to the default JSON tool result card
- Tools without a `_ui` field render the default collapsible tool card
- Adding a new generative UI component requires only: (1) create component file with `registerUIComponent`, (2) import it in message.tsx, (3) return `_ui` from tool
- `tsc` passes with no errorsRelated Skills
ai-tools
Google AI tools integration. Modules: Gemini API (multimodal: audio/image/video/PDF, 2M context), Gemini CLI (second opinions, Google Search, code review), NotebookLM (source-grounded Q&A). Capabilities: transcription, OCR, video analysis, image generation, web search, document queries. Actions: transcribe, analyze, extract, generate, query, search with Google AI. Keywords: Gemini, Gemini API, Gemini CLI, NotebookLM, audio transcription, image captioning, video analysis, PDF extraction, Google Search, second opinion, source-grounded, multimodal, web research. Use when: processing media files, needing second AI opinion, searching current web info, querying uploaded documents, generating images.
aaddyy-ai-tools
Access 100+ AI tools via MCP — image generation, article writing, logo creation, SEO analysis, math solving, video generation. One API key, pay-per-use pricing.
k-skill-korean-ai-tools
AI 에이전트를 위한 한국 서비스 자동화 스킬 모음 — SRT/KTX 예매, KBO, 로또, 카카오톡, 지하철, HWP, 우편번호 등
ai-tools
Reference for all AI tools available in DBX Studio's AI chat system. Use when adding, modifying, or debugging AI tool definitions, tool execution, or provider integrations.
ai-tools
Command-line tools that delegate analysis tasks to AI models. Includes image description, screenshot comparison, smart cropping around people, token counting, essay generation from text, boolean condition evaluation, context gathering, and Android UI interaction via popper. Use for describing images, comparing UI states, cropping photos around faces, counting tokens, generating reports, evaluating conditions, gathering context for analysis, automating Android apps, testing Wear OS, or any task requiring AI inference. Triggers: ai analysis, describe image, compare screenshots, smart crop, crop around people, face crop, count tokens, token count, generate essay, evaluate condition, alt text, image description, UI comparison, visual diff, satisfies condition, boolean evaluation, gemini, context, gather context, research topic, android ui, adb, uiautomator2, popper, automate app, test wear os.
ai-tools
Reference for all AI tools available in DBX Studio's AI chat system. Use when adding, modifying, or debugging AI tool definitions, tool execution, or provider integrations.
camscanner-ai-tools
AI agents and skills for crypto, finance, design, and real estate tools
k-skill-korean-ai-tools
AI 에이전트를 위한 한국 서비스 자동화 스킬 모음 — SRT/KTX 예매, KBO, 로또, 카카오톡, 지하철, HWP, 우편번호 등
SEO GEO (Generative Engine Optimization)
Optimization for AI-powered search engines and generative answer systems as of February 2026.
generative-optimization
Expert guidance for solving optimization problems using generative models (GMM and Flow Matching). Use when users need to solve optimization, inverse problems, or find feasible solutions under constraints using probabilistic sampling approaches.
bgo
Automates the complete Blender build-go workflow, from building and packaging your extension/add-on to removing old versions, installing, enabling, and launching Blender for quick testing and iteration.
asset-creator
This skill helps in drawing any visuals. It is a versatile skill and covers every important aspect to draw anything.