Docyrus

Adaptive Card

Render Microsoft Adaptive Cards JSON natively — elements, inputs, actions, charts, and templating.

iOSAndroid
Preview Adaptive Card on your device

Scan with Expo Go

Download Expo Go, then scan the QR code to preview native components.

AdaptiveCard renders an Adaptive Cards JSON document with native React Native primitives — no WebView. It is a 1:1 native port of the web @docyrus/ui Adaptive Card renderer and supports the same payloads produced by the Docyrus Adaptive Card Designer: every element, input, action, the chart family (including Teams' grouped and stacked bars), templating (${...}, $data, $when), requires/fallback, and host-config overrides.

Installation

pnpm dlx @docyrus/cli add @docyrus/rn-adaptive-card
Required Packages(2 packages)
pnpm add react-native-svg @react-native-community/datetimepicker (optional)

react-native-svg powers the chart family, the progress ring, and CDN icons. @react-native-community/datetimepicker is optional — when absent, Input.Date / Input.Time fall back to a plain text field.

Usage

import { AdaptiveCard } from '@/components/docyrus-native/adaptive-card';

const payload = {
  type: 'AdaptiveCard',
  version: '1.5',
  body: [
    { type: 'TextBlock', text: 'Hello **world**', size: 'large', weight: 'bolder', wrap: true },
    { type: 'Input.Text', id: 'name', label: 'Name', isRequired: true },
  ],
  actions: [{ type: 'Action.Submit', title: 'Send', style: 'positive' }],
};

<AdaptiveCard
  payload={payload}
  onAction={(event) => {
    if (event.type === 'submit') console.log(event.data); // { name: '...' }
  }}
/>

With templating

Pass a data context to expand ${...} expressions, $data scoping/repeats, and $when conditions before rendering.

<AdaptiveCard
  payload={{
    type: 'AdaptiveCard',
    version: '1.5',
    body: [
      { type: 'TextBlock', text: '${greeting}, ${name}!', weight: 'bolder' },
      { type: 'TextBlock', $data: '${items}', text: '${title} — ${price}' },
    ],
  }}
  data={{ greeting: 'Hello', name: 'Erhan', items: [{ title: 'Coffee', price: '$3.50' }] }}
/>

Handling submit / execute

onAction fires for every action the user triggers, so you can persist forms, route Teams Action.Execute verbs, or log analytics. Submit/execute events arrive only after input validation passes.

<AdaptiveCard
  payload={formPayload}
  onAction={async (event) => {
    if (event.type === 'submit') {
      await client.post('/forms', event.data); // { name: '...', topic: '...' }
    } else if (event.type === 'execute') {
      await invokeAgent(event.verb, event.data); // event.verb identifies the action
    }
  }}
/>

Host config override

Adaptive Cards' abstract enums (good, attention, medium, vertical actions…) resolve through a host config. defaultHostConfig reads the app's theme tokens, so light/dark is automatic. Pass hostConfig to deep-merge overrides:

<AdaptiveCard
  payload={payload}
  hostConfig={{ actions: { maxActions: 3, actionsOrientation: 'vertical' } }}
/>

Custom / overridden element types

customElements registers a renderer for any type string and takes precedence over the built-ins — use it for host-specific elements or to swap a placeholder (e.g. a real Media player) for a native one:

import { Text } from 'react-native';

<AdaptiveCard
  payload={payload}
  customElements={{
    'Docyrus.UserChip': ({ element }) => <UserChip id={element.userId} />,
    Media: ({ element }) => <VideoPlayer source={element.sources?.[0]?.url} />,
  }}
/>

Teams cards (PascalCase enums)

Microsoft's schema spells enum values in PascalCase ("Large", "Bolder", "Good", "PartToWhole"), and Teams matches them case-insensitively. The native renderer matches the same way, through matchesEnum and lookupEnum from lib/enum-match. So a card copied from Microsoft's documentation renders the same as its camelCase equivalent. This covers text size, weight and font type, colours, alignment, spacing, container, table and badge styles, image size and style, action style and mode, chart colour sets, display mode, thickness and value format. An unrecognized value falls back to the default instead of dropping the styling.

<AdaptiveCard
  payload={{
    type: 'AdaptiveCard',
    version: '1.5',
    body: [
      { type: 'TextBlock', text: 'Deployment finished', size: 'Large', weight: 'Bolder', color: 'Good' },
      { type: 'Container', style: 'Emphasis', items: [{ type: 'TextBlock', text: 'All checks passed', horizontalAlignment: 'Center' }] }
    ],
    actions: [{ type: 'Action.Submit', title: 'Acknowledge', style: 'Positive' }]
  }}
/>

Charts (Teams contract)

Microsoft's chart shapes differ per chart type, and the renderer accepts every spelling:

  • Bar charts take { x, y } points.
  • Pie and donut take { legend, value }.
  • Gauge segments size themselves with size (or value).
  • Chart.Line and Chart.VerticalBar.Grouped take one entry per series, { legend, values: [{ x, y }] }.
  • Chart.HorizontalBar.Stacked nests the segments under a titled bar: { title, data: [{ legend, value, color? }] }.
const charts = {
  type: 'AdaptiveCard',
  version: '1.5',
  body: [
    {
      type: 'Chart.VerticalBar.Grouped',
      title: 'Revenue by region',
      stacked: false,        // true stacks the series on one bar
      showBarValues: true,
      xAxisTitle: 'Quarter',
      yAxisTitle: 'Revenue',
      valueFormat: 'short',
      data: [
        { legend: 'EMEA', values: [{ x: 'Q1', y: 1200 }, { x: 'Q2', y: 1800 }] },
        { legend: 'APAC', values: [{ x: 'Q1', y: 900 }, { x: 'Q2', y: 1400 }] }
      ]
    },
    {
      type: 'Chart.HorizontalBar.Stacked',
      title: 'Tickets by team',
      colorSet: 'diverging',
      data: [
        { title: 'Support', data: [{ legend: 'Open', value: 12 }, { legend: 'Closed', value: 30 }] },
        { title: 'Sales', data: [{ legend: 'Open', value: 5 }, { legend: 'Closed', value: 18 }] }
      ]
    },
    {
      type: 'Chart.HorizontalBar',
      displayMode: 'PartToWhole', // scale each bar against the total; hides the value axis
      data: [{ x: 'Won', y: 42 }, { x: 'Lost', y: 18 }]
    }
  ]
};
Chart optionApplies toValues
valueFormatbar, line, pie / donut labels, gaugeshort, long, percentage / Percentage (a value from 0 to 1 is shown as a %), fraction / Fraction (gauge only: value/max).
displayModeChart.HorizontalBar onlyAbsoluteWithAxis (default), AbsoluteNoAxis (no value axis), PartToWhole (each bar is a % of the total). camelCase spellings are also accepted.
stacked / showBarValuesChart.VerticalBar.GroupedStack the series on one bar instead of side by side, and print each value.
thicknessChart.Donutthin / Thin, medium (default), thick / Thick.
colorSetall chartscategorical (default), sequential, divergent / diverging.
xAxisTitle / yAxisTitlebar, grouped, stacked, lineThe x title is centered under the plot. The y title is a caption above the plot, because rotated text does not lay out reliably on RN.

Headless hook flow

For a custom toolbar, programmatic validation, or a non-default layout, drive the renderer with useAdaptiveCard + AdaptiveCardView. A failed submit focuses the first invalid Input.Text / Input.Number:

import { AdaptiveCardView, useAdaptiveCard } from '@/components/docyrus-native/adaptive-card';

function CustomFlow({ payload }) {
  const adaptive = useAdaptiveCard(payload, { onAction });

  return (
    <>
      <Button onPress={() => adaptive.controls.validateAll()}>Pre-validate</Button>
      <AdaptiveCardView cardProps={adaptive.cardProps} />
    </>
  );
}

Filtered Input.ChoiceSet (Data.Query)

A style: 'filtered' ChoiceSet that declares choices.data calls onChoiceQuery with the dataset + debounced search text; return the matching choices:

<AdaptiveCard
  payload={payload}
  onChoiceQuery={async ({ dataset, search }) => {
    const rows = await client.get(`/lookup/${dataset}?q=${search}`);
    return rows.map(r => ({ title: r.label, value: r.id }));
  }}
/>

API Reference

PropTypeDefaultDescription
payloadAdaptiveCardPayload—The Adaptive Card JSON document to render. Required.
dataunknown—Data context for template expansion (${path}, $data, $when, built-in functions). When provided, the payload is expanded against it before rendering.
onAction(event: AdaptiveCardActionEvent) => void | Promise<void>—Fires for every action: submit, execute, openUrl, toggleVisibility, showCard, resetInputs.
hostConfigAdaptiveCardHostConfigOverride—Deep-partial overrides for spacing, actions orientation/limits, etc. Merged over defaultHostConfig.
customElementsRecord<string, ElementRenderer>—Custom renderers keyed by element type. Takes precedence over the built-ins — use it to override Media, Component, or any element.
onChoiceQuery(request: { dataset, search, inputId }) => Promise<AdaptiveCardChoice[]>—Async resolver for a filtered Input.ChoiceSet declaring choices.data (Data.Query).
classNamestring—Extra classes for the card surface.

Supported elements

GroupTypes
Text & mediaTextBlock, RichTextBlock, Image, ImageSet, Media
ContainersContainer, ColumnSet / Column, FactSet, Table, ActionSet
InputsInput.Text, Input.Number, Input.Date, Input.Time, Input.Toggle, Input.ChoiceSet, Input.Rating
Display (1.6)Badge, CodeBlock, CompoundButton, Icon, ProgressBar, ProgressRing, Rating
Layout (1.6)Carousel, Accordion, TabSet
Charts (1.6)Chart.VerticalBar, Chart.VerticalBar.Grouped, Chart.HorizontalBar, Chart.HorizontalBar.Stacked, Chart.Pie, Chart.Donut, Chart.Line (single or multi-series), Chart.Gauge
ExtensibilityComponent, LoopComponent (placeholders)

Supported actions

TypeBehavior
Action.SubmitValidates inputs, then fires onAction with the collected input map merged with data.
Action.ExecuteSame as submit plus verb.
Action.OpenUrlOpens the URL via Linking and fires onAction.
Action.ShowCardToggles an inline nested card below the action row.
Action.ToggleVisibilityShows/hides target elements by id.
Action.ResetInputsClears the named inputs (or all in scope).

selectAction is supported on Container, Column, ColumnSet, Image, Icon, TableCell, CompoundButton, and the card root.

Action dispatch

Every action emits a typed event through onAction (AdaptiveCardActionEvent, a discriminated union on type):

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 opens the URL via Linking)
'toggleVisibility'action, cardAction.ToggleVisibility
'showCard'isOpen, action, cardAction.ShowCard (fires on open and close)
'resetInputs'action, cardAction.ResetInputs

data follows the spec's associatedInputs contract: 'auto' (default) collects every visible input in scope; 'none' ships only action.data. ${input.<id>} placeholders inside action.data are substituted with the current input values.

Helpers

All exported from @/components/docyrus-native/adaptive-card:

ExportUse
useAdaptiveCard(payload, options)Headless hook — state, validation, action dispatch. Returns { cardProps, controls, state }. Also installable on its own as @docyrus/rn-hooks-use-adaptive-card.
AdaptiveCardViewRenders from a cardProps produced by the hook.
registerElement(type, renderer)Register a global custom element renderer (vs. the per-instance customElements prop).
expandTemplate(payload, data)Stand-alone templating expander (${}, $data, $when).
evaluateExpression(expr, scope) / registerExpressionFunction(name, fn)Evaluate / extend the expression language.
validateInput / validateInputs / collectInputs / buildSubmitDataInput validation + submit-payload helpers.
defaultHostConfig / mergeHostConfigHost config base + deep-merge.
parseAdaptiveCard / isAdaptiveCard / isSafeBackgroundUrlPayload guards.
getElementRenderer / listElementTypes / registerDefaultElementsInspect the element registry or re-register the built-ins.
useAdaptiveCardContext / useAdaptiveCardInput / isElementVisibleRead card state inside a custom element renderer.
ElementNode / ElementList / ActionBar / NativeIconBuilding blocks for custom element renderers.
matchesEnum(value, ...accepted) / lookupEnum(map, key)Case-insensitive enum matching, the same rules Teams uses.
formatChartValue(value, format) / formatGaugeValue(value, min, max, format)Chart value formatting (short / long / percentage / fraction).
buildColoredData / buildGroupedData / buildLineData / buildStackedBarData / pointValue / segmentValueNormalize the Teams chart data shapes.

Native notes

  • Charts render with react-native-svg, without the tooltips of the web charting library. Plain vertical and horizontal bars and the stacked horizontal bar are View-based; the grouped vertical bar, pie, donut, line and gauge are SVG. Horizontal bars show a 0 / ½ / max value axis unless displayMode is AbsoluteNoAxis or PartToWhole, and the grouped chart draws a y-axis with gridlines.
  • Table gives a column with a numeric width a fixed size (width × 50 px, same as web) and scrolls horizontally when the columns are wider than the card.
  • Text sizes never go below text-xs (12 px): badge extraSmall / small, citations, chart labels and compound-button badges were normalized, matching web.
  • Media has no bundled player: it shows the poster (or an audio chip) and opens the source via Linking on tap. Component / LoopComponent render lossless placeholders. Register a customElements override for inline playback.
  • TextBlock / FactSet markdown supports the inline subset: bold, italic, strikethrough, inline code, links. Block lists are not formatted.
  • Icons (Icon, Badge.icon, accordion/tab headers) map Fluent names to Font Awesome glyphs loaded from the Docyrus icon CDN.

Type Exports

TypeDescription
AdaptiveCardPayloadThe card document (type, version, body, actions, …).
AdaptiveCardPropsProps for the AdaptiveCard component.
AdaptiveCardElementUnion of all supported element shapes.
AdaptiveCardAction / AdaptiveCardSelectActionAction unions.
AdaptiveCardActionEventThe onAction event union.
AdaptiveCardHostConfig / AdaptiveCardHostConfigOverrideHost config + deep-partial override.
AdaptiveCardInput / AdaptiveCardInputValueInput element union + value type.
ElementRendererSignature for customElements entries.
AdaptiveCardContextValueThe context value AdaptiveCardView renders from (cardProps).
AdaptiveCardViewPropsProps for AdaptiveCardView (cardProps, className).
UseAdaptiveCardOptions / UseAdaptiveCardReturn / AdaptiveCardChoiceQueryRequestHook option, return and choice-query request types.
ValidationResult{ isValid, errorMessage? } per input.
AdaptiveCardChart*AdaptiveCardChart union, …VerticalBar, …VerticalBarGrouped, …HorizontalBar, …HorizontalBarStacked, …StackedBar, …StackedBarSegment, …Pie, …Donut, …Line, …Gauge, …Point, …DataPoint, …Segment, …ColorSet, …Color, …ValueFormat, …DisplayMode, …Thickness.
ChartSeries / ColoredDatum / LineDataReturn shapes of the chart-data helpers.

Every other schema type from adaptive-card-types.ts is re-exported from the barrel as well. That includes the enums (AdaptiveCardSpacing, …Color, …FontSize, …FontWeight, …FontType, alignments, …ContainerStyle, …ImageSize / …ImageStyle, …ActionStyle / …ActionMode, …), the elements (AdaptiveCardTextBlock, …RichTextBlock, …TextRun, …Image, …Media, …Container, …ColumnSet, …Table*, …Badge, …Carousel*, …Accordion*, …TabSet, …), the inputs (AdaptiveCardInputText, …InputNumber, …InputDate, …InputTime, …InputToggle, …InputChoiceSet, …InputRating, AdaptiveCardChoice), the actions (AdaptiveCardActionSubmit, …Execute, …OpenUrl, …ShowCard, …ToggleVisibility, …ResetInputs), the events (AdaptiveCardSubmitEvent, …) and the host-config parts, so the native barrel matches the web one.

On this page