Prompt template
Fill a prompt template's variables: `{{name}}` and `{{name:default}}` become text, multiline and select fields, with a live preview, required validation, copy and Run.
On this page
Installation
Usage
import { PromptTemplate } from "@/components/ui/chatcn/prompt-template"
<PromptTemplate
template={"Reply to {{customer_name}} in {{tone:a friendly}} tone.\n\n{{customer_message}}"}
variables={{
tone: { options: ["a friendly", "a formal", "an apologetic"] },
customer_message: { type: "multiline" },
}}
onRun={(prompt, values) => run(prompt)}
/>Every variable becomes a field in order of first appearance. The label comes from the name (customer_name is Customer name) unless variables gives one. A variable is required unless the template gives it a default or you set required: false.
The parser is its own registry item, chatcn-prompt-template, shared with the system prompt editor: parseTemplate, templateVariables, fillTemplate and estimateTokens. fillTemplate leaves a missing variable as {{name}} so it is visible, never silently empty.
import { fillTemplate, templateVariables } from "@/lib/chatcn-prompt-template"
templateVariables("Hi {{name}}, tone {{tone:friendly}}")
// [{ name: "name", count: 1 }, { name: "tone", defaultValue: "friendly", count: 1 }]
fillTemplate("Hi {{name}}, tone {{tone:friendly}}", {})
// "Hi {{name}}, tone friendly"Examples
Required variables
Run checks required variables first, focuses the first empty one and marks it in the form and in the preview. Clicking a marked placeholder in the preview jumps to its field. showErrors shows the marks without waiting for Run.
Stacked layout
The split layout puts the preview beside the form once the component is wider than 672 px (a container query, not the viewport). layout="stacked" always puts it underneath.
States
Preview
| Variable | Shown as |
|---|---|
| Filled | The value with a blue tint |
| Empty with a default | The default, with a dotted underline |
| Empty and optional | {{name}} in an amber chip |
| Empty and required, after Run | {{name}} in a red chip |
Props
| Prop | Type | Notes |
|---|---|---|
template | string | With {{name}} and {{name:default}} |
variables | Record<string, PromptVariable> | label, description, placeholder, type, options, required |
values, defaultValues | Record<string, string> | Controlled or uncontrolled |
onValuesChange | (values) => void | |
onRun | (prompt, values) => void | Only called when required variables are filled |
running | boolean | Spinner on Run, form locked |
title, description | ReactNode | Defaults to "Prompt template" |
layout | "split" | "stacked" | Defaults to split |
runLabel | string | Defaults to "Run" |
showErrors | boolean | Mark empty required variables now |
disabled | boolean |
Keyboard
| Key | Action |
|---|---|
Cmd+Enter, Ctrl+Enter | Run, from anywhere inside. Ignored while an IME is composing |
Enter | In a single-line field: Run |
ArrowDown, ArrowUp, Enter | In a select: move and choose |
Accessibility
- Every field has a visible label, its
{{name}}beside it, and its description and error linked witharia-describedby. Required fields setaria-required, empty ones after Runaria-invalid. - Select fields use Base UI Select (Radix Select in the Radix variant): a listbox with typeahead.
- The status line (2 of 5 filled, or what to fill in) is a polite live region. Missing placeholders in the preview are buttons that name the variable.
- Values in right-to-left languages lay out right to left in the form and the preview.
Motion
- The preview updates as you type, with no transition.
- The select popup opens from its trigger in 180 ms, without animation when opened by keyboard. Copy swaps to a check with the 150 ms blur crossfade.