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.
Installation
pnpm dlx @docyrus/cli add @docyrus/ui-adaptive-cardnpx shadcn@latest add avatar button calendar card collapsible dropdown-menu input label popover radio-group select separator switch textarea tooltippnpm add react-markdown remark-gfmOverview
AdaptiveCard consumes Microsoft's Adaptive Cards 1.5 JSON schema 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. 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
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/' }
]
};
<AdaptiveCard payload={payload} onAction={event => console.log(event)} />Handling submit / execute
<AdaptiveCard
payload={formPayload}
onAction={async (event) => {
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:
<AdaptiveCard
payload={payload}
hostConfig={{
actions: { maxActions: 3, actionsOrientation: 'vertical' },
colors: { good: { default: '#0a7' } }
}} />Custom element types
Register a renderer for an unknown type string via customElements:
<AdaptiveCard
payload={payload}
customElements={{
'Docyrus.UserChip': ({ element }) => <UserChip id={element.userId} />
}} />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:
import { AdaptiveCardView, useAdaptiveCard } from '@docyrus/ui/components/adaptive-card';
function CustomFlow({ payload }) {
const adaptive = useAdaptiveCard(payload, { onAction });
return (
<>
<button onClick={() => adaptive.controls.validateAll()}>Pre-validate</button>
<AdaptiveCardView cardProps={adaptive.cardProps} />
</>
);
}See useAdaptiveCard 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 <a>) |
'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<void> | — | 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<string, ElementRenderer> | {} | Renderers for element types unknown to the spec. Take precedence over the built-in registry. |
className | string | — | Forwarded to the root <Card>. |
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 <Card>. |
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<string> | boolean | number | null — the runtime value an Input.* can hold. |
ElementRenderer<T> | (props: { element: T }) => ReactNode. |
AdaptiveCardProps / AdaptiveCardViewProps | Component props. |
Security notes
- No raw HTML — markdown in
TextBlock/FactSetis rendered withreact-markdown+remark-gfmand the default raw-HTML-disabled config. Card payloads that arrive from LLMs or external bots cannot inject<script>. - Background-image URL allowlist —
parseAdaptiveCardrejectsjavascript:anddata:text/htmlURIs inbackgroundImage. Onlyhttps:,http:, anddata:image/*resolve. - External links —
Action.OpenUrlopens in a new tab withrel="noopener noreferrer".
Overview
General-purpose UI components built with React, TypeScript, Tailwind CSS v4, and CVA variants.
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.