PromptUI is a declarative UI DSL that lets LLMs and developers generate real React interfaces from structured text.
It includes:
- A preprocessor that compiles PromptUI syntax into React components.
- A component library for rendering production-ready UI with default styles and a11y-correct behavior.
Input — pricing.promptui:
[Pricing:PricingPage] {
use: library/cards/default
[CardHeader] { [CardTitle] { text: "Pricing" } }
[CardContent] {
[Plan] {
each: plan in plans
use: library/cards/pricing
[CardPricingName] { text: {{plan.name}} }
[CardPricingPrice] { text: {{plan.price}} }
}
}
}
Output — PricingPage.tsx:
import { CardContent, CardDefault, CardHeader, CardPricing, CardPricingName, CardPricingPrice, CardTitle } from "@getpromptui/ui"
export interface PricingPageProps {
plans: unknown[]
}
export function PricingPage({ plans }: PricingPageProps) {
return (
<CardDefault>
<CardHeader><CardTitle>Pricing</CardTitle></CardHeader>
<CardContent>
{plans.map((plan, index) => (
<CardPricing key={index}>
<CardPricingName>{plan.name}</CardPricingName>
<CardPricingPrice>{plan.price}</CardPricingPrice>
</CardPricing>
))}
</CardContent>
</CardDefault>
)
}Real React, fully typed. Imports resolved. State + props inferred. No glue code.
- Built for LLMs. UI is generated from text against an anchored catalog of 109 components, including site chrome (
headers/,footers/,layouts/*) — no hallucinated names, no invented props. - Declarative. No manual component wiring, no JSX scaffolding by hand.
- Serializable. UI lives as text — store it, version-control it, edit it, stream it from a chat.
- Works with the React ecosystem. Compiles to plain
.tsx. Use it with Vite, Next.js, Astro — anything that runs React.
| Feature | PromptUI | shadcn/ui |
|---|---|---|
| Declarative DSL | ✅ | ❌ |
| AI-friendly | ✅ | ❌ |
| Manual control | ✅ | |
| Default styles | ✅ | ✅ |
| Real behavior (a11y) | ✅ | ✅ |
PromptUI trades manual flexibility for AI predictability. Use it where consistency and structured generation matter; reach for raw shadcn/Radix when you want full hand-control.
npm install @getpromptui/core @getpromptui/ui// app entry
import "@getpromptui/ui/styles.css"// any component
import { Card, CardHeader, CardTitle, Button, InputEmail } from "@getpromptui/ui"Both
CardandCardDefaultwork — the short names are aliases of the registry-emitted*Defaultexports (added in 0.4.4). Pick whichever reads better in your code.
Workflow:
- Write PromptUI — Claude generates
.promptuiagainst the typed catalog. - Compile with
@getpromptui/core(CLI, Vite plugin, or programmatic API). - Render with
@getpromptui/uicomponents.
Install the skill once. The skill ships inside @getpromptui/core and is regenerated on every release from LIBRARY_ENTRIES, so the catalog you teach Claude always matches the package version you installed:
mkdir -p .claude/commands
cp node_modules/@getpromptui/core/skill.md .claude/commands/promptui.mdRe-run that line whenever you upgrade @getpromptui/core.
Then /promptui in Claude Code teaches Claude all 109 use-paths, sub-parts, variants, and DSL grammar. Ask "make a signup card with email + destructive alert + loading button" and Claude writes valid .promptui you can compile straight into your project.
.promptui → parse → resolve → emit → .tsx (or .vue)
↓
library registry
(101 anchored entries)
- Parse turns block syntax into an AST.
- Resolve validates
use:paths against the registry, checkslock/ban/allow, validatesvariant:. - Emit walks the tree, collects every component used (main + nested sub-parts), and produces React or Vue with imports at the top.
Use the Vite plugin to import .promptui files directly:
// vite.config.ts
import promptui from "@getpromptui/core/vite"
export default defineConfig({ plugins: [react(), promptui()] })import Pricing from "./pricing.promptui"
<Pricing plans={plans} />- AI-generated dashboards — LLM produces metric cards, charts, tables against a fixed catalog.
- Internal tools — admin panels with consistent, constrained UI.
- Chat-driven UI — interface streams from a model in response to user messages.
- Design-system handoff — designers describe pages in
.promptui, devs own the component implementations.
Live catalog of every registered component. Auto-generated from LIBRARY_ENTRIES plus hand-written stories for compound layouts (Heroes, Cards, Dialog, Combobox, Calendar, Charts).
pnpm install
pnpm --filter @getpromptui/storybook storybook
# → http://localhost:6006| Package | Description |
|---|---|
@getpromptui/core |
Parser, resolver, emitters, CLI, Vite plugin, library registry |
@getpromptui/ui |
React component library — 109 entries, default CSS, Radix-backed behavior |
@getpromptui/storybook |
Storybook 8 playground (private; not published) |
import { compile } from "@getpromptui/core"
const { output, warnings } = compile(source, { target: "react", exportName: "Pricing" })Low-level primitives:
import { parse, resolve, emitReact, emitVue } from "@getpromptui/core"Library introspection:
import { LIBRARY_ENTRIES, lookupByUse, isKnownComponent } from "@getpromptui/core"CLI:
promptui compile pricing.promptui --export Pricing -o src/Pricing.tsx --watchA PromptUI file is a tree of blocks. Each block has a type, an optional name, directives, and optional child blocks.
[BlockType:name] {
directive: value
[ChildType] { ... }
}
| Directive | Purpose |
|---|---|
use |
Library path → component: library/cards/default → CardDefault. Validated against the registry. |
variant |
Skin variant (e.g. destructive, outline). Valid only when the use entry declares variants. Emitted as variant="..." prop. |
each |
Repeat block: each: item in items → React {items.map((item, i) => ...)} / Vue v-for. |
if |
Conditional: if: hasError → React {hasError && (...)} / Vue v-if. |
text |
Literal text or {{expr}} for an expression. |
flow |
Interaction → handler: open-docs → prop onOpenDocs?: () => void. |
bind |
Two-way binding: email → useState + controlled value/onChange. |
look |
Semantic style tokens → className. |
lock |
Frozen aspects: structure spacing hierarchy states layout base-style. |
allow |
What may change: text subtitle buttons image badge icon binding. |
ban |
Forbidden: invent-new-layout custom-css inline-styles extra-wrappers hierarchy-changes unapproved-components. |
role |
Semantic role hint. |
goal |
Natural-language intent (used by AI tooling). |
states |
Supported responsive/interaction states. |
Priority rules: lock > look, ban > allow.
109 anchored use-paths across 50+ categories. Sub-parts are imported automatically when referenced as nested blocks.
| Category | use-paths |
Notes |
|---|---|---|
accordions |
default |
variants: default, bordered, separated |
alertDialogs |
default |
8 sub-parts |
alerts |
default |
variants: default, destructive |
aspectRatios |
default |
|
avatars |
default, withStatus, group |
|
badges |
default |
variants: default, secondary, destructive, outline |
breadcrumbs |
default, withDropdown |
|
buttons |
default, icon, loading + legacy primary, ghost |
6 skin variants on each |
calendars |
default, range, multiple |
|
cards |
default, stat, profile, media, pricing, interactive |
|
carousels |
default |
|
charts |
default, bar, line, area, pie, radar |
SVG-rendered from data prop |
checkboxes |
default |
|
collapsibles |
default |
|
comboboxes |
default, multi |
|
commands |
default |
Command palette pattern |
contextMenus |
default |
|
dataTables |
default |
Generic columns + data |
datePickers |
default, range, multiple |
|
dialogs |
default |
variants: default, fullscreen, compact |
drawers |
default |
side: top | bottom | left | right |
dropdownMenus |
default |
|
footers |
default |
variants: default, minimal, columns, newsletter; 9 sub-parts |
forms |
default |
|
headers |
default |
variants: default, minimal, full, sticky |
headings |
gradientH2 (legacy) |
|
heroes |
split, centered, withVideo, withGradient + legacy primary |
Shared HeroTitle/HeroSubtitle/HeroActions/HeroEyebrow parts |
hoverCards |
default |
|
inputOtps |
default |
|
inputs |
default, email, password, search, number, file, date, withIcon, withPrefix, withSuffix + legacy rounded |
|
labels |
default |
|
layouts |
header, footer, main, nav, section, article |
thin semantic-tag wrappers |
menubars |
default |
|
navigationMenus |
default, mobile, mega |
|
paginations |
default, withPageSize |
variants: default, compact |
popovers |
default |
|
progresses |
default, circular, stepped |
|
radioGroups |
default |
|
resizables |
default |
|
results |
text (legacy) |
|
scrollAreas |
default |
|
selects |
default |
|
separators |
default, withLabel |
variants: default, dashed, dotted |
sheets |
default |
|
sidebars |
default |
|
skeletons |
default, text, circle, card |
|
sliders |
default, range |
|
sonners |
default |
|
switches |
default |
|
tables |
default |
|
tabs |
default, vertical |
variants: default, pills, boxed |
textareas |
default |
|
toasts |
default |
variants: default, destructive |
toggles |
default |
|
toggleGroups |
default |
|
tooltips |
default, rich |
For the full catalog with every sub-part, see packages/core/README.md or inspect LIBRARY_ENTRIES at runtime.
- Anchored, not freeform. Every
use:path is validated against the registry. Unknown paths warn at compile time. - Real behavior. Interactive primitives (Dialog, Popover, Tooltip, Menu, Tabs, Select, Switch, etc.) wrap Radix UI for focus trap, keyboard navigation, scroll lock, and ARIA semantics.
- Default styles.
@getpromptui/ui/styles.cssships ~70 KB of opinionated defaults plus dark mode. Override via CSS custom properties on:root. - Tailwind-friendly.
@getpromptui/ui/tailwindexposes tokens as theme extension.
Does:
- Compiles
.promptuifiles to React (typed.tsxwith imports + props + state hooks) and Vue (full SFC with<script setup>). - 109 anchored
use-paths across 50+ component categories. - Real Radix-backed behavior on 30+ interactive primitives.
- Default styles + Tailwind preset + Vite plugin + CLI watch mode.
- DSL features:
use,variant,each,if,{{expr}},lock/ban/allow,bind,flow.
Doesn't (yet):
- Computed/derived state, data fetching, business logic in DSL — keep it in your code.
- Vue Radix integration (Vue output is structural-only; Radix Vue parity is on the v0.5 list).
- Custom-still: Combobox, Calendar/DatePicker, Carousel, Sidebar, Form (use react-hook-form), Heroes, Cards (variants), Skeletons, Charts.
git clone https://github.com/MartinLilt/promptui
cd promptui
pnpm install
pnpm buildpnpm monorepo with packages under packages/. Open an issue before submitting a PR for non-trivial changes.
30+ interactive primitives now wrap Radix UI for real a11y and behavior. PromptUI is no longer just "structure + imports" — components actually work.
- Stage 4a (0.4.0): Dialog, Popover, Tooltip, TooltipRich.
- Stage 4b (0.4.1): AlertDialog, Sheet, Drawer, HoverCard.
- Stage 4c (0.4.2): DropdownMenu, ContextMenu, Menubar, NavigationMenu, Accordion, Collapsible, Tabs, TabVertical.
- Stage 4d (0.4.3): Select, Switch, Checkbox, RadioGroup, Slider, SliderRange, Toggle, ToggleGroup, ScrollArea, AspectRatio, Avatar, Label, Toast, Separator.
Breaking (composition shift to shadcn-style <Root><Trigger/><Content/></Root>): Dialog, AlertDialog, Sheet, Drawer, Popover, HoverCard, Tooltip. The DSL use paths and sub-part names are unchanged — only manual JSX usage needs updates.
Adds 25 @radix-ui/* deps as dependencies (not peer) so consumers don't need to install them manually.
- Wrapped React output:
--export Name(or root[Block:Name]) →export function Name()withuseStateforbind:,on<Flow>props forflow:. each: item in itemsandif: exprdirectives.{{expr}}for expressions intext:.- Default CSS (
@getpromptui/ui/styles.css). - Tailwind plugin at
@getpromptui/ui/tailwind. - CLI
--watch+--output. Vite plugin at@getpromptui/core/vite.
- Library registry with 101
use-paths. variant:directive for skin variants.- React emitter auto-imports from
@getpromptui/ui. - Vue emitter outputs full SFC.
Initial release — parser, resolver, React/Vue emitters, CLI, 6 legacy components.
MIT © Martin Li. See LICENSE.