Skip to content
Sign in
PlaygroundPrompt templateMenu

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.

Text, multiline and select fields from one template, with defaults and a live preview.

Installation

$ pnpm dlx shadcn@latest add https://chatcn.dev/r/prompt-template.json

Usage

tsx
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.

ts
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.

Empty required variables marked in the form and the preview.

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.

Preview under the form for narrow panels, with long values.

States

No variables, running and disabled.

Preview

VariableShown as
FilledThe value with a blue tint
Empty with a defaultThe default, with a dotted underline
Empty and optional{{name}} in an amber chip
Empty and required, after Run{{name}} in a red chip

Props

PropTypeNotes
templatestringWith {{name}} and {{name:default}}
variablesRecord<string, PromptVariable>label, description, placeholder, type, options, required
values, defaultValuesRecord<string, string>Controlled or uncontrolled
onValuesChange(values) => void
onRun(prompt, values) => voidOnly called when required variables are filled
runningbooleanSpinner on Run, form locked
title, descriptionReactNodeDefaults to "Prompt template"
layout"split" | "stacked"Defaults to split
runLabelstringDefaults to "Run"
showErrorsbooleanMark empty required variables now
disabledboolean

Keyboard

KeyAction
Cmd+Enter, Ctrl+EnterRun, from anywhere inside. Ignored while an IME is composing
EnterIn a single-line field: Run
ArrowDown, ArrowUp, EnterIn a select: move and choose

Accessibility

  • Every field has a visible label, its {{name}} beside it, and its description and error linked with aria-describedby. Required fields set aria-required, empty ones after Run aria-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.