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.

Client Only

Installation

pnpm dlx @docyrus/cli add @docyrus/ui-adaptive-card-designer
UI Primitives(8 components)
npx shadcn@latest add button dropdown-menu input label select switch textarea tooltip
Required Packages(2 packages)
pnpm add @dnd-kit/core @monaco-editor/react

The designer renders its canvas with the AdaptiveCard renderer, which is installed automatically as a registry dependency.

Overview

AdaptiveCardDesigner is a full visual authoring surface for Adaptive Cards payloads. Where AdaptiveCard 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:

RegionPurpose
ToolbarNew / 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 <AdaptiveCard>. 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

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' }]
};

<AdaptiveCardDesigner
  defaultPayload={starter}
  defaultSampleData={{ user: { name: 'Alice' } }}
  onChange={({ payload, sampleData }) => console.log(payload, sampleData)} />

Controlled

Pass payload / sampleData to drive the designer from outside. onChange fires after every edit:

const [payload, setPayload] = useState<AdaptiveCardPayload>(starter);
const [data, setData] = useState<unknown>({});

<AdaptiveCardDesigner
  payload={payload}
  sampleData={data}
  onChange={next => { 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:

<AdaptiveCardDesigner payload={payload} readOnly />

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:

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: {}
    })
  }
];

<AdaptiveCardDesigner
  extraToolboxItems={extras}
  customElements={{ 'Docyrus.UserChip': ({ element }) => <UserChip id={element.userId} /> }} />

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

Trimming the toolbar

<AdaptiveCardDesigner hideToolbarButtons={['new', 'copy']} />

API Reference

AdaptiveCardDesignerProps

PropTypeDefaultDescription
payloadAdaptiveCardPayload—Current card payload (controlled).
defaultPayloadAdaptiveCardPayload—Initial payload when uncontrolled.
sampleDataunknown—Templating data resolved into the preview canvas (controlled).
defaultSampleDataunknown—Initial sample data when uncontrolled.
onChange(next: { payload: AdaptiveCardPayload; sampleData: unknown }) => void—Fires after every payload or sample-data edit.
hostConfigAdaptiveCardHostConfigOverride—Forwarded to the inner <AdaptiveCard hostConfig />.
customElementsRecord<string, ElementRenderer>{}Forwarded to the inner <AdaptiveCard customElements />. Pair with extraToolboxItems to author custom types.
defaultPreviewbooleanfalseStart in preview (render-only) mode rather than edit mode.
defaultThemeDesignerTheme'light'Initial canvas theme — 'light' | 'dark' | 'auto'.
defaultWidthDesignerWidth'standard'Initial canvas width — 'standard' | 'wide' | 'full'.
hideToolbarButtonsToolbarButtonKey[]—Hide specific toolbar buttons — 'new' | 'theme' | 'width' | 'undo' | 'redo' | 'copy' | 'preview'.
extraToolboxItemsToolboxItem[]—Append additional toolbox items alongside the built-ins.
heightstring'70vh'Designer chrome height.
classNamestring—Forwarded to the root element.
readOnlybooleanfalseDisable all mutations. Theme / width / preview / copy and selection stay active.
aiAssistantOpenboolean—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 <DocyrusAgent> or <EditorAgent> wrapper). Without it the AI button is hidden.
aiAssistantWidthnumber380Width 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.

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 (
    <AdaptiveCardDesigner
      payload={adaptiveCard.payload ?? undefined}
      sampleData={adaptiveCard.sampleData}
      onChange={(next) => {
        adaptiveCard.setPayload(next.payload);
        adaptiveCard.setSampleData(next.sampleData);
      }}
      aiAssistantOpen={aiOpen}
      onAiAssistantOpenChange={setAiOpen}
      renderAiAssistant={({ open, onClose }) => (
        <EditorAgent
          agentId={agentId}
          client={client}
          user={user}
          open={open}
          onClose={onClose}
          clientTools={adaptiveCard.tools}
        />
      )}
    />
  );
}

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:

ExportPurpose
DesignerProviderHolds 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 <EditorAgent>. See EditorAgent integration above.

Helpers

The component also exports its tree model utilities for advanced integrations (custom serializers, programmatic edits, validation):

ExportPurpose
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_MAPThe 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, countNodesTree traversal + mutation utilities.

Type Exports

TypeDescription
AdaptiveCardDesignerPropsComponent props.
DesignerProviderPropsProps for DesignerProvider.
DesignerNodeA node in the designer's normalized tree — { __designerId, type, props, slots }.
DesignerState / DesignerActionReducer state + action union.
DesignerTheme'light' | 'dark' | 'auto'.
DesignerWidth'standard' | 'wide' | 'full'.
DesignerFocusWhich editor was last focused — 'canvas' | 'payload' | 'data'.
DesignerDiagnosticA validation diagnostic — { level, message, nodeId? }.
HistorySnapshotAn undo / redo history entry.
ToolboxItem / ToolboxGroupA toolbox entry and its group key.
ToolbarButtonKeyKeys accepted by hideToolbarButtons.
DesignerRootTypeThe '__root' discriminant for the card root node.
IAdaptiveCardAiAssistantRenderContextArgument passed to renderAiAssistant — { open, width, onClose, payload, sampleData }.
IUseApplyAdaptiveCardResultReturn shape of useApplyAdaptiveCard — { payload, setPayload, sampleData, setSampleData, tools }.

On this page