plan_comparison
Two to four plans side by side.
Tiers, compared. plan_comparison puts two to four plans in a horizontal rail (name, price, features, and one CTA each) all the height of the tallest card, so the buttons line up.
A rail, not a stack, because plans exist to be compared and four stacked columns in a chat thread put the first and last plan a screen apart.
Re-rendered in the browser from the SDK’s own scales. Not a screenshot.
Rendered natively by the Flutter SDK and the Web SDK, both since 0.1.0.
When not to use it
Do not use it for a single offer. One plan is not a comparison: use info_card with a CTA.
Do not use it for products, which is product_cards and has room for an image and a badge, and do not use it as a feature matrix with ticks and crosses, which is table.
Props
Every block also carries type and a required fallback_text of 1 to 300 characters.
| Prop | Type | Required | Constraints |
|---|---|---|---|
plans | array | Yes | 2 to 4 entries. |
plans[].name | string | Yes | 1 to 60 characters. Rendered uppercase. |
plans[].price | string | No | Up to 40 characters. Formatted by you, cadence included. |
plans[].highlight | boolean | No | Raises that card's edge to full ink and fills its CTA with the accent. |
plans[].features | string[] | Yes | 1 to 12 strings, up to 120 characters each. |
plans[].cta | object | No | None |
plans[].cta.label | string | Yes | 1 to 60 characters. |
plans[].cta.action | action | Yes | { kind: "reply", text } or { kind: "open_url", url }. See Actions. |
On the wire
{
"id": "blk_15",
"type": "plan_comparison",
"fallback_text": "Free at $0, Club at $9 a month, Club Plus at $19.",
"props": {
"plans": [
{
"name": "Free",
"price": "$0",
"features": [
"Standard delivery",
"30-day returns"
],
"cta": {
"label": "Current plan",
"action": {
"kind": "reply",
"text": "Stay on Free"
}
}
},
{
"name": "Club",
"price": "$9/mo",
"highlight": true,
"features": [
"Next-day delivery",
"60-day returns",
"Free gait analysis"
],
"cta": {
"label": "Join Club",
"action": {
"kind": "reply",
"text": "Upgrade me to Club"
}
}
}
]
}
}Interaction
Each CTA fires its own action. Legal kinds are reply and open_url. Both execute: open_url opens the page in an in-app browser, or goes to your RendelConfig.onOpenUrl when you set one. https only.
Only the first plan carrying highlight gets it; any later one renders quiet, so the accent budget holds whatever the model sends. It is the block's whole accent budget: mark two and the recommendation stops meaning anything.
States
| State | What the SDK draws |
|---|---|
| Loading | Two column-shaped placeholders in a rail at the real card width. |
| Empty | An empty plans array renders fallback_text bare, without a plate, because this block carries no enclosure of its own. |
| Uneven feature counts | Cards match the tallest, and every CTA sits on the same bottom edge, even where one label wraps to two lines and another does not. |
Accessibility
Each card is read in order (name, price, then features) and each CTA announces its own label, so "Join Club" is unambiguous without the card around it.
fallback_text should list the plans and their prices, which is the comparison in words.