Adaptive Cards
Author Adaptive Cards that render as native UI in Teams, Outlook, and bots. A live card designer with a JSON editor and starter gallery, plus filterable cheat sheets for every element, container, input, and action — and the rules for posting cards from Power Automate and embedding them in Outlook actionable messages.
Requester header, FactSet, and Approve / Reject submit actions — the classic “wait for a response” card.
Preview approximates the default host config. Inputs and actions don’t submit; Action.ShowCard expands inline. Use the official Adaptive Cards Designer for pixel-exact, full-schema rendering.
What Adaptive Cards are
An Adaptive Card is a chunk of UI described in JSON. You author the card once; each host — Microsoft Teams, Outlook, a bot, Copilot Studio, Viva Connections — renders it as native UI that adapts to its own look and feel. No HTML or CSS, no per-client tweaking: the same payload becomes a Teams message, an email card, or a bot reply.
Every card is an object with type: "AdaptiveCard", a version, a body array of elements, and an optional actions array of buttons. The host supplies the styling (fonts, colours, spacing) through a host config, so you describe *intent* (“this is a heading”, “this is an accent button”) rather than pixels.
- Teams — flow-bot messages, bot replies, message-extension and dialog (task module) UI. Supports schema 1.5.
- Outlook actionable messages — a card embedded in an email so recipients act without leaving the inbox. Separate, more conservative schema track (baseline 1.0).
- Power Automate — posts cards to Teams (display-only or “wait for a response”) via the Teams connector.
- Bots / Copilot Studio / Bot Framework Web Chat — rich replies and forms (Web Chat tracks 1.6).
- Design + validate in the official Adaptive Cards Designer, then paste the JSON into your flow or bot.
Try the designer above
The designer at the top of this page ships starter cards — approval, notification, form, receipt, and more. Switch to JSON to edit the payload and watch the preview update live, then Copy it straight into a “Post adaptive card” action or an Outlook actionable message.
Card anatomy
Start from this skeleton. type and version are required; $schema enables editor IntelliSense; everything visible lives in body; buttons live in actions.
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.5",
"body": [
{ "type": "TextBlock", "text": "Order shipped", "size": "Large", "weight": "Bolder", "wrap": true },
{ "type": "TextBlock", "text": "Tracking number TN-4821 is on its way.", "wrap": true, "isSubtle": true }
],
"actions": [
{ "type": "Action.OpenUrl", "title": "Track package", "url": "https://contoso.com/track/TN-4821" }
]
}| Property | Type | Since | Notes |
|---|---|---|---|
| `type` | `"AdaptiveCard"` | 1.0 | Required. Always the literal string. |
| `version` | string | 1.0 | Required. Schema the card needs (e.g. `"1.5"`). Below the host version, `fallbackText` renders instead. |
| `$schema` | uri | 1.0 | Editor hint only; ignored at render time. |
| `body` | Element[] | 1.0 | Ordered list of elements shown in the card. |
| `actions` | Action[] | 1.0 | Buttons in the card’s action bar. |
| `selectAction` | ISelectAction | 1.1 | Invoked when the whole card is tapped. `Action.ShowCard` not allowed here. |
| `backgroundImage` | uri / object | 1.0 / 1.2 | Background fill; object form adds fillMode + positioning. |
| `minHeight` | string | 1.2 | e.g. `"160px"`. Pairs with `verticalContentAlignment`. |
| `verticalContentAlignment` | string | 1.1 | `top` / `center` / `bottom` — only for fixed/min-height cards. |
| `rtl` | boolean | 1.5 | Right-to-left layout for the whole card. |
| `lang` | string | 1.0 | 2-letter ISO code; localizes date/time functions. |
| `fallbackText` | string | 1.0 | Shown (as markdown) when the host is older than `version`. |
| `refresh` | Refresh | 1.4 | Auto-refresh via `Action.Execute` (Universal Action Model). |
| `authentication` | Authentication | 1.4 | SSO / just-in-time OAuth for Universal Actions. |
| `metadata` | Metadata | 1.6 | Non-rendered metadata (e.g. `webUrl`). |
Version is a contract, not a wish
The host renders at most the version it supports. Set version to the lowest version whose features you actually use — declaring "1.5" on a card aimed at Outlook (1.0) can blank the card on older clients. Always provide fallbackText.
Elements
Elements are the leaf content you stack in body (or inside a container). TextBlock and Image cover most cards.
| Element | Purpose | Key properties |
|---|---|---|
| `TextBlock` | A run of text (the workhorse). | `text`, `wrap`, `size`, `weight`, `color`, `isSubtle`, `spacing`, `horizontalAlignment`, `maxLines`, `style:"heading"` |
| `RichTextBlock` | Mixed inline formatting in one paragraph. | `inlines[]` of `TextRun` (`text`, `weight`, `color`, `italic`, `strikethrough`, `selectAction`) |
| `Image` | A single image from a URL. | `url`, `altText`, `size` (small/medium/large/auto/stretch), `width`/`height`, `style:"person"`, `horizontalAlignment`, `selectAction` |
| `Media` | Audio / video clip with a poster. | `poster`, `sources[]` (`url`, `mimeType`), `altText` (host support varies — Teams yes, Outlook no) |
| `Icon` | A Fluent UI icon (1.6). | `name`, `size`, `style`, `color` |
| `CodeBlock` | Monospace, syntax-aware code (Teams ext). | `codeSnippet`, `language`, `startLineNumber` |
TextBlock honours a small markdown subset across most hosts: **bold**, _italic_, [links](https://…), bulleted / numbered lists, and line breaks. Use wrap: true almost everywhere — text is truncated to one line by default.
{
"type": "ColumnSet",
"columns": [
{ "type": "Column", "width": "auto", "items": [
{ "type": "Image", "url": "https://contoso.com/avatar.png", "size": "Small", "style": "Person", "altText": "Priya N." }
] },
{ "type": "Column", "width": "stretch", "verticalContentAlignment": "Center", "items": [
{ "type": "TextBlock", "text": "Priya Nair", "weight": "Bolder", "wrap": true },
{ "type": "TextBlock", "text": "Requested 3 days of leave", "isSubtle": true, "spacing": "None", "wrap": true }
] }
]
}Containers & layout
Containers group and arrange elements. Container stacks vertically; ColumnSet lays out side by side; FactSet renders a tidy label/value grid; Table (1.5) draws real rows and columns.
| Container | Purpose | Key properties |
|---|---|---|
| `Container` | Group elements; tint a region. | `items[]`, `style` (default/emphasis/good/attention/warning/accent), `bleed`, `minHeight`, `verticalContentAlignment`, `selectAction` |
| `ColumnSet` | Horizontal row of columns. | `columns[]`, `horizontalAlignment`, `spacing`, `style`, `bleed` |
| `Column` | One column within a ColumnSet. | `width` (`auto` / `stretch` / number / `"80px"`), `items[]`, `style`, `verticalContentAlignment` |
| `FactSet` | Label/value pairs (e.g. metadata). | `facts[]` of `{ "title", "value" }` |
| `ImageSet` | A thumbnail gallery. | `images[]`, `imageSize` |
| `Table` | Tabular data with real columns (1.5). | `columns[]` (`width`), `rows[]` of `TableRow` → `cells[]` of `TableCell`, `firstRowAsHeaders`, `gridStyle` |
| `ActionSet` | Buttons inside the body, not the action bar. | `actions[]` |
Spacing, separator & bleed
Every element takes spacing (none/small/default/medium/large/extraLarge/padding) controlling the gap above it, and separator: true to draw a divider line above it. bleed: true lets a styled container extend to the card edge, ignoring the card padding — great for coloured headers.
{
"type": "AdaptiveCard",
"version": "1.5",
"body": [
{ "type": "Container", "style": "Emphasis", "bleed": true, "items": [
{ "type": "TextBlock", "text": "Expense report #1042", "weight": "Bolder", "size": "Large", "wrap": true }
] },
{ "type": "FactSet", "facts": [
{ "title": "Submitted by", "value": "Dana Wu" },
{ "title": "Amount", "value": "$486.20" },
{ "title": "Status", "value": "Pending approval" }
] }
]
}Inputs (collecting data)
Inputs turn a card into a form. Each input needs a unique `id` — that id becomes the key in the data sent back when the user submits. Pair inputs with an Action.Submit (or Action.Execute) to gather their values.
| Input | Renders as | Key properties |
|---|---|---|
| `Input.Text` | Single- or multi-line text box. | `placeholder`, `value`, `isMultiline`, `maxLength`, `style` (text/tel/url/email), `regex` (1.3) |
| `Input.Number` | Numeric field. | `min`, `max`, `value`, `placeholder` |
| `Input.Date` | Date picker. | `min`, `max`, `value` (ISO `YYYY-MM-DD`) |
| `Input.Time` | Time picker. | `min`, `max`, `value` (`HH:MM`) |
| `Input.Toggle` | On/off checkbox. | `title`, `value`, `valueOn`, `valueOff` |
| `Input.ChoiceSet` | Dropdown, radio, or checkbox list. | `choices[]` (`title`/`value`), `style` (compact/expanded), `isMultiSelect`, `value`, `placeholder` |
{
"type": "AdaptiveCard",
"version": "1.5",
"body": [
{ "type": "Input.Text", "id": "comments", "label": "Comments", "isMultiline": true, "isRequired": true,
"errorMessage": "Please add a comment." },
{ "type": "Input.ChoiceSet", "id": "priority", "label": "Priority", "style": "Compact", "value": "normal",
"choices": [
{ "title": "Low", "value": "low" },
{ "title": "Normal", "value": "normal" },
{ "title": "High", "value": "high" }
] }
],
"actions": [
{ "type": "Action.Submit", "title": "Send", "style": "positive" }
]
}Inputs only return data on a “wait for response” action
In Power Automate, a plain “Post adaptive card” is display-only — Action.Submit raises an error and you get nothing back. To collect input you must use a “…and wait for a response” action (see below). Validation (isRequired, regex, errorMessage) needs schema 1.3+.
Actions
Actions are the buttons. They sit in the card-level actions array, inside an ActionSet, or as a selectAction on an element. There are six types.
| Action | Does | Key properties / notes |
|---|---|---|
| `Action.OpenUrl` | Opens a URL. | `url`. The only action that works on a *display-only* Teams card. |
| `Action.Submit` | Sends inputs + `data` back to the host/bot. | `data` (string or object), merged with all input values. |
| `Action.ShowCard` | Reveals a nested card inline. | `card` (an AdaptiveCard). Not allowed as a `selectAction`. |
| `Action.ToggleVisibility` | Shows/hides elements by id. | `targetElements[]` (id strings or `{ elementId, isVisible }`). |
| `Action.Execute` | Universal Action — sends to a bot, can return a new card. | Since **1.4**. Replaces Submit/Http across Teams + Outlook; needs a bot. |
| `Action.ResetInputs` | Clears input values (Teams). | `targetInputIds[]`. Teams-specific. |
Universal Action Model (Action.Execute)
Action.Execute (schema 1.4) unifies Teams Action.Submit and Outlook Action.Http into one model that a bot handles and that can return an updated card. For backward compatibility, wrap Action.Execute in an ActionSet and give it a fallback of Action.Submit so older Teams clients still render a button.
Teams drops positive/destructive styling
The Teams host config does not honour style: "positive" / "destructive" on actions — buttons render in the default accent. Don’t rely on colour alone to signal “Approve” vs “Reject”; put it in the button text too.
Posting from Power Automate
The Microsoft Teams connector offers several “Post adaptive card” actions. The split that matters is display-only vs wait for a response:
- Post adaptive card in a chat or channel / Post your own adaptive card as the Flow bot — display only. Only
Action.OpenUrlworks;Action.Submiterrors. - Post adaptive card and wait for a response (to a user or channel) — the flow pauses until someone responds, then exposes each input value as dynamic content for later steps.
- A card in a *channel* can be answered by anyone in that channel; the flow continues on the first response and ignores the rest.
- After a response, set Update message (and Should update card) so the card visibly changes — otherwise the form just resets and people may submit again.
Power Automate does NOT support card templating
The ${...} templating language is not evaluated by the Teams connector. Instead, drop Power Automate dynamic content and expressions directly into the JSON values (see the example below). Keep every value in straight double quotes — numbers included — and watch for curly quotes pasted in from Word.
{
"type": "AdaptiveCard",
"version": "1.4",
"body": [
{ "type": "TextBlock", "text": "Leave request", "size": "Large", "weight": "Bolder", "wrap": true },
{ "type": "FactSet", "facts": [
{ "title": "Requester", "value": "@{triggerBody()?['requester']}" },
{ "title": "Dates", "value": "@{triggerBody()?['dates']}" }
] },
{ "type": "Input.ChoiceSet", "id": "decision", "style": "Expanded", "label": "Decision",
"choices": [
{ "title": "Approve", "value": "approve" },
{ "title": "Reject", "value": "reject" }
] }
],
"actions": [
{ "type": "Action.Submit", "title": "Submit decision" }
]
}After the action, the input ids (decision) and a submitActionId are available as dynamic content. Use them to branch the flow — e.g. a Condition on decision equals approve.
Prerequisite: the Workflows app
Flow-bot cards need the Workflows (Power Automate) app installed in Teams. “Card didn’t post” is almost always a missing app, malformed JSON (curly quotes), or an image URL that isn’t publicly reachable.
Outlook actionable messages
Outlook can render an Adaptive Card inside an email so the recipient acts without leaving the inbox. The card JSON is wrapped in a <script> tag in the email’s <head>. Outlook is a separate, more conservative schema track — design for 1.0 and test before using newer features.
<html>
<head>
<script type="application/adaptivecard+json">
{
"type": "AdaptiveCard",
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"version": "1.0",
"originator": "<your-provider-id-from-the-dashboard>",
"hideOriginalBody": true,
"body": [
{ "type": "TextBlock", "text": "Approve expense #1042?", "size": "Large", "weight": "Bolder", "wrap": true }
],
"actions": [
{ "type": "Action.OpenUrl", "title": "Review in browser", "url": "https://contoso.com/expenses/1042" }
]
}
</script>
</head>
<body>
<!-- Plain-text/HTML fallback for clients that can't render the card -->
Approve expense #1042: https://contoso.com/expenses/1042
</body>
</html>- The email body must be HTML (plain text can’t carry the
<script>), and recipients must be individual mailboxes on Exchange Online / Outlook.com — not groups or shared mailboxes. originatormust hold a valid provider ID from the Actionable Email Developer Dashboard whenever you send to anyone other than yourself; without it the card is stripped.- Interactive actions historically used
Action.Http; the modern path isAction.Execute(1.4+) via a bot on the Outlook Actionable Messages channel. - Actions can’t be taken on messages older than ~1 month, and there’s a limit on how many actionable messages render at once.
Legacy auth retired — use Entra ID tokens
Actionable Messages moved from legacy (EAT) authentication to Microsoft Entra ID tokens; the legacy phase-out completed June 8, 2026. Integrations that still rely on legacy tokens no longer function — confirm your action endpoint validates Entra ID bearer tokens.
Templating & data binding
The Adaptive Cards Templating SDK separates layout from data: author a template with ${...} binding expressions, then merge it with a JSON data object at render time. Great for bots and custom apps that bind cards in code.
{
"type": "AdaptiveCard",
"version": "1.5",
"body": [
{ "type": "TextBlock", "text": "Hello ${user.name}", "weight": "Bolder", "wrap": true },
{ "type": "Container", "$data": "${orders}", "items": [
{ "type": "TextBlock", "text": "${id} — ${status}", "wrap": true,
"$when": "${status != 'cancelled'}" }
] }
]
}import * as ACData from "adaptivecards-templating";
const template = new ACData.Template(cardTemplateJson);
const card = template.expand({
$root: { user: { name: "Priya" }, orders: [{ id: "A-1", status: "shipped" }] }
});Not in Power Automate
The Teams connector does not evaluate ${...} templating. In flows, bind values with dynamic-content expressions inside the JSON (see the Power Automate section). Reserve ${...} templating for bots / custom code that run the templating SDK.
Versions & host support
Schema support varies by host, so a feature that works in Teams may be ignored in Outlook. Target the version your host supports, and provide fallbacks.
| Host | Schema version | Notes |
|---|---|---|
| Microsoft Teams (desktop, web, mobile) | 1.5 | Most common target. No positive/destructive action styling; no file/image upload. |
| Microsoft 365 Copilot Chat | 1.5 | Primary surface for M365 agent scenarios. |
| Outlook Actionable Messages | 1.0 (–1.2) | Separate track; baseline 1.0. Action.Execute needs 1.4+ via a bot. |
| Bot Framework Web Chat | 1.6 | Action.Execute not supported in Web Chat. |
| Viva Connections / Webex | 1.2 | Adaptive Card Extensions / partner hosts. |
| Current schema | 1.6 | Latest published; adds Icon, metadata, and more. |
- `fallbackText` on the card renders when the host is older than
version— always set a meaningful sentence. - `fallback` on an element/action swaps in a simpler alternative (or
"drop") when the host can’t render it — e.g.Action.Execute→Action.Submit. - `requires` (1.2) declares a minimum feature version on an element so the host can trigger that element’s fallback.
- When in doubt, set the Designer’s Target version to your host and let it flag unsupported elements before you ship.
Styling, accessibility & gotchas
- Describe intent, not pixels. Use
size/weight/color/isSubtleand containerstyletokens; the host config maps them to its theme so cards look native in light and dark mode. - Host images on a public HTTPS URL. Outlook and Teams won’t render data-URI / base64 images, and a URL that needs auth shows nothing. Always set
altText. - Always `wrap: true` on
TextBlock, or text clips to one line. Test long values and narrow widths (Teams mobile, meeting side panel). - Accessibility: meaningful
altText, real label text on inputs (label), and don’t signal meaning with colour alone — Teams ignores button colour styling anyway. - Dark mode: container
styletokens adapt automatically; avoid hard-codedcolorhacks. Outlook actionable messages historically don’t restyle for dark mode. - Validate the JSON in the Designer before pasting into a flow — most “card didn’t render” issues are curly quotes, a trailing comma, or a missing
version. - Keep cards short. Long cards get truncated in compact surfaces; link out with
Action.OpenUrlfor detail rather than cramming everything in.
Build a card library
Save one tested base per scenario — approval, notification, receipt, form — and clone it per flow. The designer above is a starting gallery; copy a template, swap the text/ids, and drop it into your “Post adaptive card” action.