microsoft / microsoft/AdaptiveCards
Adaptive Components
@rahulamlekar is already working on this.
Since Jun 11, 2021.
- Dominant language
- C#
- Stars
- 2k
- Forks
- 595
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 1
Description
Adaptive Components
Enabling the creation of purely declarative high-level components powered by templating and native Adaptive Card elements. You can think of them like “molecules”, built by arranging Adaptive Card elements (“atoms”) into unique and helpful ways.
Status: Draft Approved
9/25/2020: Draft approved per the changes made in the comment below from 9/25. Root spec to be updated shortly.
9/9/2020: Initial draft proposed.
Try it yourself
Interested in trying it out? You're in luck! We have an early prototype to play with, note that it is pre-alpha and will be buggy!

- Load up the Designer from the above URL.
- Drag over a Component from Card Elements toolbox on the left.
- On the designer surface, select the Component, and click Choose...
- A dialog should open with a list of available components, this might take ~30 seconds or so due to the service starting up.
- Click on the iextrading.com/stockQuote component.
- Change the Element Properties and observe the component UI updating. E.g., Set Change value to -2.69
- Play around with it by mix-and-matching different components that are currently available.
Objectives
- Allow anyone to define, use, and share higher-level "component" abstractions (with the general public or within their organization)
- Tooling integration: Component authoring should be a first-class experience in the Designer.
- Composable: Components can be used anywhere inside the card body, and chained together when necessary.
- Responsive: Components should be fully responsive and support different viewports as appropriate.
- Local and Remote Component registries: Host Apps can register local components, but they can also enable support for remote registies to "learn" new components dynamically, without requiring client-side app updates.
- [P2] Actionable: Components should be able to provide actions that work on Hosts that have enabled "Universal Actions" (Lots of details to work out here)
Open issues
- Need to consider versioning the components, which use different data types or significantly change the view
- Need to consider how namespacing and component names should be done
- What about arrays of objects, e.g., multiple Files and multiple Persona's?
- Fetching Authenticated images
- Can the view definition start with an array instead of just an object? Would make converting from
bodyto avieweasier - Fetching remote potentially authenticated data via an identifier (such as URL or UPN)
- concerns about security with remotely downloadable components
- look into custom html elements in BF cards and if this could be handled with components
- fallback implementation could get complicated
- latency considerations, especially when components rely on other components
- scrolling in teams will be negatively impacted by async component loading
- look into offline registries such as npm packages
- We need guidance or a policy on what should be modified by Components in other registries. E.g., if a custom registry changes the
schema.org/thingcomponent, it can add/remove/change views, but should it be able to change the schema? I'm leaning towards no, but should we make that a policy or just strong guidance?- What good is a component if there is no guarantee that it will work across hosts, even when the host implements its own version?
- Isn't the whole point of components to offer a way for card authors to be able to use an always growing collections of, well, components that are guaranteed to work across all hosts?
- A component's schema should be contractual, that is core to ensuring that it'll work everywhere
- And as a result, if the schema is contractual, why aren't views?
Examples
Example: "File Chicklet" Component
The file chicklet is a way to represent basic file metadata somewhere inside the card.

Component Usage:
{
"type": "AdaptiveCard",
"body": [
{
"type": "Component",
"name": "graph.microsoft.com/file",
"properties": {
"name": "FY2020-Contoso.docx",
"fileType": "docx",
"webUrl": "https://adaptivecardsblob.blob.core.windows.net/assets/AdaptiveCardsSpec.docx"
}
}
]
}
Component Definition: Component.File.json
{
"type": "AdaptiveComponent",
"componentName": "graph.microsoft.com/file",
"schema": {
"properties": {
"name": {
"type": "string"
},
"fileType": {
"type": "string"
},
"webUrl": {
"type": "string"
}
},
"required": [ "title", "fileType" ]
},
"views": {
"default": {
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"width": "35px",
"items": [
{
"type": "Image",
"$when": "${fileType == 'docx'}",
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4f/Microsoft_Word_2013_logo.svg/782px-Microsoft_Word_2013_logo.svg.png"
},
{
"type": "Image",
"$when": "${fileType == 'xlsx'}",
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/7/7f/Microsoft_Office_Excel_%282018%E2%80%93present%29.svg/1101px-Microsoft_Office_Excel_%282018%E2%80%93present%29.svg.png"
}
]
},
{
"type": "Column",
"width": "stretch",
"items": [
{
"type": "TextBlock",
"text": "${name}",
"weight": "bolder"
},
{
"type": "TextBlock",
"text": "${webUrl}",
"isSubtle": true,
"spacing": "none"
}
]
}
]
}
}
}
Example: "Thing" Component

"Things" are all over the web today, and represent the basic metadata for objects. They are broadly categorized by these pieces of metadata:
- Name
- Description
- Image
- Url
- Actions
- (Sometimes) Category
Component Usage:
{
"type": "AdaptiveCard",
"body": [
{
"type": "Component",
"name": "schema.org/thing",
"properties": {
"name": "Golden Gate Bridge",
"description": "Suspension bridge in San Francisco, California",
"image": "..."
}
}
]
}
Component Definition: Component.Thing.json
{
"type": "AdaptiveComponent",
"componentName": "schema.org/thing",
"schema": {
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"image": {
"type": "uri-template"
}
},
"required": [ "name" ]
},
"views": {
"default": {
"type": "Container",
"items": [
{
"type": "Image",
"url": "${image}",
"size": "Medium"
},
{
"type": "TextBlock",
"text": "${name}",
"size": "Medium"
},
{
"type": "TextBlock",
"text": "${description}",
"size": "Default"
}
]
}
}
}
Example: User component
{
"type": "Component",
"name": "graph.microsoft.com/user",
"properties": {
"displayName": "Megan Bowen",
"givenName": "Megan",
"jobTitle": "Auditor",
"mail": "MeganB@M365x214355.onmicrosoft.com",
"mobilePhone": null,
"officeLocation": "12/1110",
"preferredLanguage": "en-US",
"surname": "Bowen",
"userPrincipalName": "MeganB@M365x214355.onmicrosoft.com",
"id": "48d31887-5fad-4d73-a9f5-3c356e68a038"
}
}
Example: Composition
Components can be used alongside any native Adaptive Card elements, or other Components.
{
"type": "AdaptiveCard",
"body": [{
"type": "TextBlock",
"text": "Here are 3 famous bridges"
}, {
"type": "ColumnSet",
"columns": [{
"type": "Component",
"name": "schema.org/thing",
"properties": {
"name": "Golden Gate Bridge",
"description": "Suspension Bridge in California",
"image": "..."
}
}, {
"type": "Component",
"name": "schema.org/thing",
"properties": {
"name": "Brooklyn Bridge",
"description": "Cable-stayed bridge in New York",
"image": "..."
}
}, {
"type": "Component",
"name": "schema.org/thing",
"properties": {
"name": "London Bridge",
"description": "Box girder bridge in London",
"image": "..."
}
}
]}
]}
Requirements
- All renderers will support local and remote component lookups (configurable by the Host). Details below.
- Components must be able to bind to Host-specific properties, like viewport dimensions (e.g.
"$when": "${$host.width > 300}")
Renderer behavior and component registries
The renderer will loop through all elements in the body as normal. When it encounters a type of Component then a component lookup occurs.
- First check the local component registry, provided by the Host via Host Config or platform API.
- If not found there, check the remote component registry. Remote registries are also configurable by the Host, and can be disabled if required.
Example remote registry lookup
HTTP GET https://api.adaptivecards.io/components/<COMPONENT-NAME>.json
(NOTE: Hosts can configure this via host config)
Based on the schema.org/thing component above, the renderer would request the component at the following URL:
HTTP GET https://api.adaptivecards.io/components/schema.org/thing.json
{
"type": "AdaptiveComponent",
"name": "schema.org/thing",
"views": {
"default": {
"type": "Container",
"items": [{
"type": "Image",
"url": "${image}"
}, {
"type": "TextBlock",
"text": "${name}",
"size": "large"
}, {
"type": "TextBlock",
"text": "${description}"
}
]
}
}
}
Multiple "views" for Components
In many cases, a single layout many not suffice for a given component. For example, if a Component contains an image, the desired UI may place the image above the other fields, or it may want a small thumbnail of the image to the left of the other content, or perhaps as the background image behind the content.
This becomes increasingly important in a world where a single payload roams between hosts with vastly different UI constraints or characteristics.
Imagine if the schema.org/thing component wanted to provide 3 different "layouts" or "views":

The ultimate UI that gets rendered would be determined by multiple factors:
- Implicitly
- Based on renderer version of the client (e.g., a 1.0 "Thing" view, or a 2.0 "Thing" view)
- Based on display constraints (e.g., a narrow screen vs wide screen)
- Others?
- Explicitly
- By name (e.g., the card author chooses a particular layout that works best for them)
Card Author explicitly selecting a particular view
If we allow authors to pick a particular view for a given component, we need a standardized set of properties on the component definitions.
| Property | Type | Required | Description |
|---|---|---|---|
| type | "Component" |
Yes | Type must be Component |
| name | string |
Yes | The name of the Component to load from the local or remote component registry. The recommended format is [DOMAIN]/[COMPONENT-NAME], e.g., graph.microsoft.com/file |
| view | string[] |
No | The desired view for the component, first match wins. |
| properties | object |
No | Passed directly to the component template |
If we apply this to our schema.org/thing, it becomes:
{
"type": "AdaptiveCard",
"body": [
{
"type": "Component",
"name": "schema.org/thing",
"properties": {
"name": "Golden Gate Bridge",
"description": "Suspension bridge in San Francisco, California",
"image": "...",
},
"view": [ "hero", "thumnbail" ]
}
]
}
In this example, the card author is asking for a view in a specific order, where the first match will be used.
- hero
- thumbnail
Why would a match not be found?
Great question! The reason for the array is because we will allow hosts to modify the remote component registries, which would let them define a custom version of a well-known component, or replacing the entire registry and all of its Components. If they do choose to modify a well-known component (like schema.org/thing), they may want to modify, add, or even remove support for particular views.
Example: "Thing" component with multiple views
Let's apply this concept to our schema.org/thing component.

HTTP GET https://api.adaptivecards.io/components/schema.org/thing.json
{
"type": "AdaptiveComponent",
"name": "schema.org/thing",
"schema": ...,
"views": {
"default": {
"type": "Container",
"items": [{
"type": "Image",
"url": "${image}"
}, {
"type": "TextBlock",
"text": "${name}",
"size": "large"
}, {
"type": "TextBlock",
"text": "${description}"
}
]
},
"thumbnail": {
"type": "ColumnSet",
"columns": [{
"type": "Column",
"items": [{
"type": "Image",
"url": "${image}"
}
]
}, {
"type": "Column",
"items": [{
"type": "TextBlock",
"text": "${name}"
},
{
"type": "TextBlock",
"text": "${description}"
}
]
}
]
},
"stack": {
"type": "Container",
"backgroundImage": "${image}",
"items": [{
"type": "TextBlock",
"text": "${name}",
"size": "large"
}, {
"type": "TextBlock",
"text": "${description}"
}
]
}
}
}
Supporting multiple renderer versions in the same component
As we release new versions of Adaptive Card schema, it's important that components are compatible with multiple versions.
For example, maybe the "Thing" template wants to use new 1.6 elements if the client supports them, but still has a great experience for 1.3, or even 1.0.
Thankfully, we get this support for free, thanks to the existing fallback model in 1.2! 👍
{
"type": "AdaptiveComponent",
"views": {
"default": {
"type": "Container",
"requires": {
"version": "1.6",
},
"items": [ /* 3.0 elements */ ],
"fallback": {
"type": "Container",
"items": [ /* fallback elements */ ]
}
},
"thumbnail": {
...
}
}
}
Designer Integration

We should make working with components and component registries a first-class experience.
- Authoring a component that includes multiple views over the data
- Connecting to a component registry to browse, edit, and even save changes back to the registry
- Seamless experience to author a card that uses remote component registries, selects a particular view, and simulates the ultimate rendered UI based on a given host (which may have their own registries and a particular renderer version)
Components can declare their expected schema and sample data
Components have a first-class way to describe the schema of the data/properties it expects, which enables the Designer to generate the Property Sheet automatically. This is also useful for tooling more advanced component resolution, and even validation.
Thing example
The schema.org/thing component expects data as follows:
{
"name": "Golden Gate Bridge",
"description": "San Francisco, CA",
"image": "..."
}
Thing declaration:
{
"type": "AdaptiveComponent",
"name": "schema.org/thing",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The most important piece of information"
},
"description": {
"type": "string",
"description": "The second most important piece of information"
},
"image": {
"type": "string",
"format": "uri",
"description": "Image associated wih the information"
}
}
},
"views": {
...
}
}
Appendix
Other component-lookup approaches
There are other ways we can explore Component-resolution.
For example, schema.org has a clear hierachy for their objects. "Thing" is the base type of everything, but maybe instead of a Thing I have a LocalBusiness, which includes all the properties of Thing and some addition ones.
If a Restaurant component doesn't exist, could we make a best-effort to find a base type the hierarchy until a match is found, such as LocalBusiness:
{
"type": "Component",
"name": "schema.org/restaurant",
"properties": {
"name": "Malt and Vine",
"description": "A fine place for beer",
"address": "....",
"openingHours": "...."
}
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.