# Adaptive Card Designer URL: /docs/web/components/adaptive-card-designer Visual drag-and-drop designer for Microsoft Adaptive Cards 1.5 / 1.6 payloads. Three-pane editor (toolbox · canvas · structure / properties) with paired JSON editors, live preview, undo / redo, and theme / width controls — backed by the in-repo Adaptive Card renderer. **Demo:** ```tsx 'use client'; import { useState } from 'react'; import { AdaptiveCardDesigner } from '@docyrus/ui/components/adaptive-card-designer'; import { type AdaptiveCardPayload } from '@docyrus/ui/components/adaptive-card'; import { Button } from '@docyrus/ui/primitives/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@docyrus/ui/primitives/ui/dialog'; import { LayoutTemplate, Plus } from 'lucide-react'; const STARTER: AdaptiveCardPayload = { type: 'AdaptiveCard', version: '1.5', body: [ { type: 'TextBlock', text: '**Welcome to the Adaptive Card Designer**', size: 'large', weight: 'bolder' }, { type: 'TextBlock', text: 'Pick elements from the toolbox on the left, or edit the JSON below. The canvas updates live.', wrap: true, isSubtle: true }, { type: 'FactSet', facts: [{ title: 'Status', value: 'Draft' }, { title: 'Owner', value: '${user.name}' }] } ], actions: [{ type: 'Action.OpenUrl', title: 'Adaptive Cards spec', url: 'https://adaptivecards.io/' }] }; const STARTER_DATA = { user: { name: 'Alice', role: 'Admin' } }; export function AdaptiveCardDesignerDemo() { const [open, setOpen] = useState(false); const [mode, setMode] = useState<'sample' | 'blank'>('sample'); const openDesigner = (next: 'sample' | 'blank') => { setMode(next); setOpen(true); }; return (
); } ``` ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/ui-adaptive-card-designer ``` **Dependencies:** - [@dnd-kit/core](https://www.npmjs.com/package/@dnd-kit/core) - [@monaco-editor/react](https://www.npmjs.com/package/@monaco-editor/react) - [lucide-react](https://www.npmjs.com/package/lucide-react) The designer renders its canvas with the [`AdaptiveCard`](/docs/web/components/adaptive-card) renderer, which is installed automatically as a registry dependency. ## Overview `AdaptiveCardDesigner` is a full visual authoring surface for [Adaptive Cards](https://adaptivecards.io/) payloads. Where [`AdaptiveCard`](/docs/web/components/adaptive-card) only *renders* a payload, the designer lets users *build* one — dragging elements from a toolbox, reordering and reparenting them in a structure tree, editing element properties in a side panel, and watching the result render live. The raw payload and the templating sample-data are both editable as JSON at the bottom, and the two views stay in sync. Typical use cases: - **Internal card authoring** — a no-code surface for ops / support teams to compose Teams-style cards and agent UI without hand-writing JSON. - **Template tooling** — author the payload that an LLM agent or automation later fills with `${...}` bindings, previewed against sample data. - **Embedded editors** — drop the designer into an automation-node config or a CMS, read the emitted payload back via `onChange`. The designer works on its own normalized node tree internally (stable per-node ids, uniform child *slots*) and serializes back to a standard `AdaptiveCardPayload` on every edit. ## Layout The designer is a three-pane layout with a toolbar above and paired JSON editors below: | Region | Purpose | |--------|---------| | **Toolbar** | New / undo / redo / copy, theme (light / dark / auto), canvas width (standard / wide / full), preview toggle. Buttons are individually hideable via `hideToolbarButtons`. | | **Toolbox** (left) | Draggable element / input / action types grouped into `elements`, `inputs`, `actions`, `advanced`. Append your own via `extraToolboxItems`. | | **Canvas** (center) | Live render of the card via ``. Click an element to select it; drop toolbox items directly onto it. | | **Structure** (right, top) | Collapsible tree of the card. Reorder / reparent by drag, select to edit. | | **Properties** (right, bottom) | Type-aware property editors for the selected node. | | **Payload editor** (bottom) | The live `AdaptiveCardPayload` JSON (Monaco). | | **Sample-data editor** (bottom) | The templating data JSON used to resolve `${...}` bindings in preview. | ## Usage ### Uncontrolled ```tsx import { AdaptiveCardDesigner } from '@docyrus/ui/components/adaptive-card-designer'; import { type AdaptiveCardPayload } from '@docyrus/ui/components/adaptive-card'; const starter: AdaptiveCardPayload = { type: 'AdaptiveCard', version: '1.5', body: [{ type: 'TextBlock', text: 'Hello, **Adaptive Cards**!', weight: 'bolder' }] }; console.log(payload, sampleData)} /> ``` ### Controlled Pass `payload` / `sampleData` to drive the designer from outside. `onChange` fires after every edit: ```tsx const [payload, setPayload] = useState(starter); const [data, setData] = useState({}); { setPayload(next.payload); setData(next.sampleData); }} /> ``` ### Read-only inspection `readOnly` disables every mutation — toolbox drag, drop zones, property edits, JSON edits, undo / redo / new, and keyboard shortcuts. Theme / width / preview / copy stay active, and selection remains enabled so users can click through the tree and inspect properties: ```tsx ``` ### Custom toolbox items Append your own draggable types alongside the built-ins. Each item supplies a `factory` that returns a fresh node on every drop: ```tsx import { Sparkles } from 'lucide-react'; import { type ToolboxItem } from '@docyrus/ui/components/adaptive-card-designer'; const extras: ToolboxItem[] = [ { id: 'Docyrus.UserChip', type: 'Docyrus.UserChip', label: 'User Chip', icon: Sparkles, group: 'advanced', keywords: ['user', 'chip', 'avatar'], factory: () => ({ __designerId: crypto.randomUUID(), type: 'Docyrus.UserChip', props: { userId: '' }, slots: {} }) } ]; }} /> ``` Pair `extraToolboxItems` (authoring) with `customElements` (rendering) so the canvas knows how to draw the type you just added. `customElements` and `hostConfig` are forwarded straight to the inner ``. ### Trimming the toolbar ```tsx ``` ## API Reference ### `AdaptiveCardDesignerProps` | Prop | Type | Default | Description | |------|------|---------|-------------| | `payload` | `AdaptiveCardPayload` | — | Current card payload (controlled). | | `defaultPayload` | `AdaptiveCardPayload` | — | Initial payload when uncontrolled. | | `sampleData` | `unknown` | — | Templating data resolved into the preview canvas (controlled). | | `defaultSampleData` | `unknown` | — | Initial sample data when uncontrolled. | | `onChange` | `(next: { payload: AdaptiveCardPayload; sampleData: unknown }) => void` | — | Fires after every payload or sample-data edit. | | `hostConfig` | `AdaptiveCardHostConfigOverride` | — | Forwarded to the inner ``. | | `customElements` | `Record` | `{}` | Forwarded to the inner ``. Pair with `extraToolboxItems` to author custom types. | | `defaultPreview` | `boolean` | `false` | Start in preview (render-only) mode rather than edit mode. | | `defaultTheme` | `DesignerTheme` | `'light'` | Initial canvas theme — `'light' \| 'dark' \| 'auto'`. | | `defaultWidth` | `DesignerWidth` | `'standard'` | Initial canvas width — `'standard' \| 'wide' \| 'full'`. | | `hideToolbarButtons` | `ToolbarButtonKey[]` | — | Hide specific toolbar buttons — `'new' \| 'theme' \| 'width' \| 'undo' \| 'redo' \| 'copy' \| 'preview'`. | | `extraToolboxItems` | `ToolboxItem[]` | — | Append additional toolbox items alongside the built-ins. | | `height` | `string` | `'70vh'` | Designer chrome height. | | `className` | `string` | — | Forwarded to the root element. | | `readOnly` | `boolean` | `false` | Disable all mutations. Theme / width / preview / copy and selection stay active. | | `aiAssistantOpen` | `boolean` | — | Controlled open state for the AI Assistant drawer. When provided the designer stops managing the open state internally — pair with `onAiAssistantOpenChange`. | | `onAiAssistantOpenChange` | `(open: boolean) => void` | — | Fired when the AI Assistant toolbar button toggles the drawer. | | `renderAiAssistant` | `(ctx: IAdaptiveCardAiAssistantRenderContext) => ReactNode` | — | Mounts a custom AI Assistant drawer body on the left of the designer. When set, the toolbar shows a Bot toggle that opens/closes the drawer; this render fn supplies the body (typically a `` or `` wrapper). Without it the AI button is hidden. | | `aiAssistantWidth` | `number` | `380` | Width in pixels the drawer animates to when open. | ## EditorAgent integration Pair `useApplyAdaptiveCard` with `renderAiAssistant` to let an LLM author Adaptive Card payloads. The hook owns the designer's controlled `payload` + `sampleData` and exposes an `applyAdaptiveCard` tool that the agent calls to commit a new payload. The tool rejects primitives / arrays / null and requires `type: "AdaptiveCard"` at the root so a malformed call cannot crash the designer. ```tsx import { EditorAgent } from '@docyrus/ui/components/editor-agent'; import { AdaptiveCardDesigner, useApplyAdaptiveCard } from '@docyrus/ui/components/adaptive-card-designer'; export function AdaptiveCardPlayground({ client, user, agentId }) { const adaptiveCard = useApplyAdaptiveCard(); const [aiOpen, setAiOpen] = useState(false); return ( { adaptiveCard.setPayload(next.payload); adaptiveCard.setSampleData(next.sampleData); }} aiAssistantOpen={aiOpen} onAiAssistantOpenChange={setAiOpen} renderAiAssistant={({ open, onClose }) => ( )} /> ); } ``` The matching backend agent must register a tool named `applyAdaptiveCard` whose input schema mirrors `{ payload: object (required), sampleData?: any, explanation?: string }`. ## Composition The default `AdaptiveCardDesigner` is a single component that composes a provider + the panes. For custom layouts you can assemble the pieces yourself: | Export | Purpose | |--------|---------| | `DesignerProvider` | Holds reducer state + history. Wrap your own panel composition in it. | | `useDesignerContext()` | Read `state` + `dispatch` inside a custom panel. | | `useApplyAdaptiveCard()` | Owns the controlled `payload` + `sampleData` state and ships the `applyAdaptiveCard` client-side tool for ``. See **EditorAgent integration** above. | ## Helpers The component also exports its tree model utilities for advanced integrations (custom serializers, programmatic edits, validation): | Export | Purpose | |--------|---------| | `cardToTree(payload)` / `treeToCard(node)` | Convert between an `AdaptiveCardPayload` and the designer's `DesignerNode` tree. | | `normalizeForRoundTrip(payload)` | Normalize a payload so tree → card → tree is lossless. | | `slotsFor(type)` / `SLOT_MAP` | The slot names (`items`, `columns`, `actions`, …) a given element type accepts. | | `isLeafType(type)` | Whether a type accepts no children. | | `createDefaultNode(type)` / `defaultChildType(type)` | Factory helpers for new nodes. | | `createDesignerId()` | Generate a stable internal node id. | | `findNode`, `findLocation`, `insertNode`, `removeNode`, `moveNode`, `updateNode`, `isAncestor`, `collectIds`, `countNodes` | Tree traversal + mutation utilities. | ## Type Exports | Type | Description | |------|-------------| | `AdaptiveCardDesignerProps` | Component props. | | `DesignerProviderProps` | Props for `DesignerProvider`. | | `DesignerNode` | A node in the designer's normalized tree — `{ __designerId, type, props, slots }`. | | `DesignerState` / `DesignerAction` | Reducer state + action union. | | `DesignerTheme` | `'light' \| 'dark' \| 'auto'`. | | `DesignerWidth` | `'standard' \| 'wide' \| 'full'`. | | `DesignerFocus` | Which editor was last focused — `'canvas' \| 'payload' \| 'data'`. | | `DesignerDiagnostic` | A validation diagnostic — `{ level, message, nodeId? }`. | | `HistorySnapshot` | An undo / redo history entry. | | `ToolboxItem` / `ToolboxGroup` | A toolbox entry and its group key. | | `ToolbarButtonKey` | Keys accepted by `hideToolbarButtons`. | | `DesignerRootType` | The `'__root'` discriminant for the card root node. | | `IAdaptiveCardAiAssistantRenderContext` | Argument passed to `renderAiAssistant` — `{ open, width, onClose, payload, sampleData }`. | | `IUseApplyAdaptiveCardResult` | Return shape of `useApplyAdaptiveCard` — `{ payload, setPayload, sampleData, setSampleData, tools }`. | ## Related - [`AdaptiveCard`](/docs/web/components/adaptive-card) — the renderer the designer canvas is built on. - [`useAdaptiveCard`](/docs/web/hooks/use-adaptive-card) — the hook behind the renderer. --- # Adaptive Card URL: /docs/web/components/adaptive-card Renderer for Microsoft's Adaptive Cards 1.5 schema. Surfaces LLM-generated and Microsoft Teams payloads as native Docyrus UI with stateful inputs, validation, nested ShowCards, and action dispatch. **Demo:** ```tsx 'use client'; import { useMemo, useState } from 'react'; import { AdaptiveCard, type AdaptiveCardActionEvent, type AdaptiveCardPayload } from '@docyrus/ui/components/adaptive-card'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@docyrus/ui/primitives/ui/select'; import { cn } from '@docyrus/ui/primitives/lib/utils'; import { DEMO_AGENT_MESSAGE, DEMO_FLIGHT_CARD, DEMO_FORM_CARD } from '@/data/adaptive-card-data'; const DEMOS = { flight: { label: 'Flight status', payload: DEMO_FLIGHT_CARD }, form: { label: 'Form card', payload: DEMO_FORM_CARD }, agent: { label: 'Agent message', payload: DEMO_AGENT_MESSAGE } } as const satisfies Record; export function AdaptiveCardDemo() { const [selected, setSelected] = useState('flight'); const [open, setOpen] = useState(false); const [lastEvent, setLastEvent] = useState ))}
{lastEvent ? (
          {JSON.stringify(lastEvent, (_k, v) => v instanceof Object && 'card' in v ? { ...v, card: '<…>' } : v, 2)}
        
) : null} ); } ``` ## Installation ```bash pnpm dlx @docyrus/cli add @docyrus/ui-adaptive-card ``` **Dependencies:** - [lucide-react](https://www.npmjs.com/package/lucide-react) - [react-markdown](https://www.npmjs.com/package/react-markdown) - [remark-gfm](https://www.npmjs.com/package/remark-gfm) ## Overview `AdaptiveCard` consumes Microsoft's [Adaptive Cards 1.5 JSON schema](https://adaptivecards.io/schemas/1.5.0/adaptive-card.json) and renders it using Docyrus primitives (shadcn). The two primary use cases are: - **Agent-native UI** — Adaptive Cards are a JSON structure LLM agents already understand and emit reliably. Use the renderer to surface agent output as actionable UI. - **Microsoft Teams interop** — the same payload a Teams Bot returns can render directly inside Docyrus messages, comments, and notifications. The component is a thin wrapper around [`useAdaptiveCard`](/docs/web/hooks/use-adaptive-card). Drop in a payload, get a fully interactive card back. For advanced flows (server-driven validation, custom toolbars), use the hook directly. ## Usage ### Basic render ```tsx import { AdaptiveCard, type AdaptiveCardPayload } from '@docyrus/ui/components/adaptive-card'; const payload: AdaptiveCardPayload = { type: 'AdaptiveCard', version: '1.5', body: [ { type: 'TextBlock', text: 'Hello, **Adaptive Cards**!', size: 'large', weight: 'bolder' } ], actions: [ { type: 'Action.OpenUrl', title: 'Open docs', url: 'https://adaptivecards.io/' } ] }; console.log(event)} /> ``` ### Handling submit / execute ```tsx { if (event.type === 'submit') { await fetch('/api/forms', { method: 'POST', body: JSON.stringify(event.data) }); } else if (event.type === 'execute') { // event.verb identifies the bot action await invokeBot(event.verb, event.data); } }} /> ``` `onAction` fires for **every** action the user triggers — `submit`, `execute`, `openUrl`, `toggleVisibility`, `showCard` — so consumers can log analytics, persist drafts, or route Teams `Action.Execute` calls. ### Host config override The renderer maps Adaptive Cards' abstract enums (`Good`, `Attention`, `Bleed`, `Medium`) to concrete colors/spacing/sizes via a host config. `defaultHostConfig` reads from the repo's CSS-variable tokens, so dark mode is automatic. Pass `hostConfig` to deep-merge overrides: ```tsx ``` ### Custom element types Register a renderer for an unknown `type` string via `customElements`: ```tsx }} /> ``` Custom renderers take precedence over the built-in registry. The unknown-type fallback continues to honor the schema's `fallback` chain otherwise. ### Hook-driven flow For advanced cases (custom toolbar, programmatic submit, server-side validation), consume the hook directly: ```tsx import { AdaptiveCardView, useAdaptiveCard } from '@docyrus/ui/components/adaptive-card'; function CustomFlow({ payload }) { const adaptive = useAdaptiveCard(payload, { onAction }); return ( <> ); } ``` See [`useAdaptiveCard`](/docs/web/hooks/use-adaptive-card) for the full hook surface. ## Schema coverage The renderer ships **complete coverage** of the Adaptive Cards 1.5 schema: | Category | Elements | |----------|----------| | Text & media | `TextBlock`, `RichTextBlock` (with `TextRun` inlines), `Image`, `ImageSet`, `Media` | | Containers | `Container`, `ColumnSet` (`Column` children), `FactSet`, `Table` (`TableRow`, `TableCell`), `ActionSet` | | Inputs | `Input.Text` (single + multiline + `inlineAction`), `Input.Number`, `Input.Date`, `Input.Time`, `Input.Toggle`, `Input.ChoiceSet` (compact / expanded / filtered × single / multi) | | Actions | `Action.Submit`, `Action.Execute`, `Action.OpenUrl`, `Action.ShowCard` (nested cards), `Action.ToggleVisibility` | | Cross-cutting | `selectAction` on containers/columns/images/text-runs/table cells/root card, `requires` capability gating, `fallback` chains, `isVisible` defaults + toggle overrides, `spacing` + `separator`, markdown in `TextBlock` + `FactSet` values, `backgroundImage` (with safe-URL guard), validation (`isRequired`, `regex`, `min`/`max`, error messages) | Out of scope in v1: live `refresh` polling, server-side `Action.Execute` transport (we hand `verb` + `data` to `onAction`), `authentication` block rendering. ## Action dispatch Every action emits a typed event through `onAction`: | Event `type` | Fields | Triggered by | |--------------|--------|--------------| | `'submit'` | `data`, `action`, `card` | `Action.Submit` (after validation passes) | | `'execute'` | `verb`, `data`, `action`, `card` | `Action.Execute` (after validation passes) | | `'openUrl'` | `url`, `action`, `card` | `Action.OpenUrl` (also navigates the browser via the embedded ``) | | `'toggleVisibility'` | `action`, `card` | `Action.ToggleVisibility` | | `'showCard'` | `isOpen`, `action`, `card` | `Action.ShowCard` (fires for both open and close) | `data` follows the spec's `associatedInputs` contract: `'auto'` (default) collects every visible input in the current scope; `'none'` ships only `action.data`. For `Action.ShowCard` submits, the nested card's inputs merge into the parent's. ## API Reference ### `AdaptiveCardProps` | Prop | Type | Default | Description | |------|------|---------|-------------| | `payload` | `AdaptiveCardPayload` | — | The card JSON. Must have `type: 'AdaptiveCard'` and a `version`. | | `onAction` | `(event: AdaptiveCardActionEvent) => void \| Promise` | — | Fires for every dispatched action. | | `hostConfig` | `AdaptiveCardHostConfigOverride` (deep partial of `AdaptiveCardHostConfig`) | `defaultHostConfig` | Override colors, spacing, sizes, container styles, or action layout. Deep-merged over the default. | | `customElements` | `Record` | `{}` | Renderers for element types unknown to the spec. Take precedence over the built-in registry. | | `className` | `string` | — | Forwarded to the root ``. | ### `AdaptiveCardViewProps` `AdaptiveCardView` is the presentational layer that the hook drives. Use it when you call `useAdaptiveCard` yourself. | Prop | Type | Default | Description | |------|------|---------|-------------| | `cardProps` | `AdaptiveCardContextValue` | — | The `cardProps` projection returned by `useAdaptiveCard`. | | `className` | `string` | — | Forwarded to the root ``. | ## Components | Component | Description | |-----------|-------------| | `AdaptiveCard` | High-level component. Composes `useAdaptiveCard` + `AdaptiveCardView`. | | `AdaptiveCardView` | Presentational layer. Consumes `cardProps` from `useAdaptiveCard`. Use this when wiring the hook manually. | | `ElementNode` / `ElementList` | Recursive renderer. The `customElements` extension point routes through these. | | `ActionBar` | Renders an action list with overflow menu + ShowCard panels. | ## Helpers | Export | Purpose | |--------|---------| | `defaultHostConfig` | The token-driven host config used when no override is passed. | | `mergeHostConfig(base, override)` | Deep-merge a partial override onto a base config. | | `parseAdaptiveCard(payload)` | Validate + normalize an unknown payload. Returns `null` for invalid input. | | `isAdaptiveCard(value)` | Type guard for `AdaptiveCardPayload`. | | `isSafeBackgroundUrl(url)` | Refuses `javascript:` and `data:text/html` URIs; allows `https:`, `http:`, `data:image/*`. | | `validateInput(input, value)` / `validateInputs(inputs, values)` | The same validators the hook runs on submit. | | `collectInputs(elements, visibilityOverrides)` | Walks an element tree for `associatedInputs: 'auto'` collection. | | `buildSubmitData(action, card, values, overrides)` | Assemble the `data` payload for a `Submit` / `Execute` action. | | `registerElement(type, renderer)` | Register a globally-available element renderer. Prefer the per-instance `customElements` prop when possible. | ## Type Exports | Type | Description | |------|-------------| | `AdaptiveCardPayload` | Card root. | | `AdaptiveCardElement` | Discriminated union of every known element type. | | `AdaptiveCardCustomElement` | Open `type: string` shape for renderer extensions. Not in the main union to keep narrowing tight. | | `AdaptiveCardAction` | Discriminated union of every action type. | | `AdaptiveCardSelectAction` | Subset of actions legal as `selectAction` (no `ShowCard`). | | `AdaptiveCardInput` | Discriminated union of every input type. | | `AdaptiveCardActionEvent` | The union of events emitted to `onAction`. | | `AdaptiveCardSubmitEvent`, `AdaptiveCardExecuteEvent`, `AdaptiveCardOpenUrlEvent`, `AdaptiveCardToggleVisibilityEvent`, `AdaptiveCardShowCardEvent` | Individual event shapes. | | `AdaptiveCardHostConfig` / `AdaptiveCardHostConfigOverride` | Host config + deep-partial override. | | `AdaptiveCardInputValue` | `string \| Array \| boolean \| number \| null` — the runtime value an `Input.*` can hold. | | `ElementRenderer` | `(props: { element: T }) => ReactNode`. | | `AdaptiveCardProps` / `AdaptiveCardViewProps` | Component props. | ## Security notes - **No raw HTML** — markdown in `TextBlock` / `FactSet` is rendered with `react-markdown` + `remark-gfm` and the default raw-HTML-disabled config. Card payloads that arrive from LLMs or external bots cannot inject `