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.

Client Only

Installation

pnpm dlx @docyrus/cli add @docyrus/ui-adaptive-card
UI Primitives(15 components)
npx shadcn@latest add avatar button calendar card collapsible dropdown-menu input label popover radio-group select separator switch textarea tooltip
Required Packages(2 packages)
pnpm add react-markdown remark-gfm

Overview

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:

CategoryElements
Text & mediaTextBlock, RichTextBlock (with TextRun inlines), Image, ImageSet, Media
ContainersContainer, ColumnSet (Column children), FactSet, Table (TableRow, TableCell), ActionSet
InputsInput.Text (single + multiline + inlineAction), Input.Number, Input.Date, Input.Time, Input.Toggle, Input.ChoiceSet (compact / expanded / filtered × single / multi)
ActionsAction.Submit, Action.Execute, Action.OpenUrl, Action.ShowCard (nested cards), Action.ToggleVisibility
Cross-cuttingselectAction 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 typeFieldsTriggered by
'submit'data, action, cardAction.Submit (after validation passes)
'execute'verb, data, action, cardAction.Execute (after validation passes)
'openUrl'url, action, cardAction.OpenUrl (also navigates the browser via the embedded <a>)
'toggleVisibility'action, cardAction.ToggleVisibility
'showCard'isOpen, action, cardAction.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

PropTypeDefaultDescription
payloadAdaptiveCardPayload—The card JSON. Must have type: 'AdaptiveCard' and a version.
onAction(event: AdaptiveCardActionEvent) => void | Promise<void>—Fires for every dispatched action.
hostConfigAdaptiveCardHostConfigOverride (deep partial of AdaptiveCardHostConfig)defaultHostConfigOverride colors, spacing, sizes, container styles, or action layout. Deep-merged over the default.
customElementsRecord<string, ElementRenderer>{}Renderers for element types unknown to the spec. Take precedence over the built-in registry.
classNamestring—Forwarded to the root <Card>.

AdaptiveCardViewProps

AdaptiveCardView is the presentational layer that the hook drives. Use it when you call useAdaptiveCard yourself.

PropTypeDefaultDescription
cardPropsAdaptiveCardContextValue—The cardProps projection returned by useAdaptiveCard.
classNamestring—Forwarded to the root <Card>.

Components

ComponentDescription
AdaptiveCardHigh-level component. Composes useAdaptiveCard + AdaptiveCardView.
AdaptiveCardViewPresentational layer. Consumes cardProps from useAdaptiveCard. Use this when wiring the hook manually.
ElementNode / ElementListRecursive renderer. The customElements extension point routes through these.
ActionBarRenders an action list with overflow menu + ShowCard panels.

Helpers

ExportPurpose
defaultHostConfigThe 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

TypeDescription
AdaptiveCardPayloadCard root.
AdaptiveCardElementDiscriminated union of every known element type.
AdaptiveCardCustomElementOpen type: string shape for renderer extensions. Not in the main union to keep narrowing tight.
AdaptiveCardActionDiscriminated union of every action type.
AdaptiveCardSelectActionSubset of actions legal as selectAction (no ShowCard).
AdaptiveCardInputDiscriminated union of every input type.
AdaptiveCardActionEventThe union of events emitted to onAction.
AdaptiveCardSubmitEvent, AdaptiveCardExecuteEvent, AdaptiveCardOpenUrlEvent, AdaptiveCardToggleVisibilityEvent, AdaptiveCardShowCardEventIndividual event shapes.
AdaptiveCardHostConfig / AdaptiveCardHostConfigOverrideHost config + deep-partial override.
AdaptiveCardInputValuestring | Array<string> | boolean | number | null — the runtime value an Input.* can hold.
ElementRenderer<T>(props: { element: T }) => ReactNode.
AdaptiveCardProps / AdaptiveCardViewPropsComponent 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 <script>.
  • Background-image URL allowlist — parseAdaptiveCard rejects javascript: and data:text/html URIs in backgroundImage. Only https:, http:, and data:image/* resolve.
  • External links — Action.OpenUrl opens in a new tab with rel="noopener noreferrer".

On this page