ReferenceComponents

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.

Free$0
Standard delivery30-day returns
Current plan
Club$9/mo
Next-day delivery60-day returnsFree gait analysis
Join Club

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.

PropTypeRequiredConstraints
plansarrayYes2 to 4 entries.
plans[].namestringYes1 to 60 characters. Rendered uppercase.
plans[].pricestringNoUp to 40 characters. Formatted by you, cadence included.
plans[].highlightbooleanNoRaises that card's edge to full ink and fills its CTA with the accent.
plans[].featuresstring[]Yes1 to 12 strings, up to 120 characters each.
plans[].ctaobjectNoNone
plans[].cta.labelstringYes1 to 60 characters.
plans[].cta.actionactionYes{ 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

StateWhat the SDK draws
LoadingTwo column-shaped placeholders in a rail at the real card width.
EmptyAn empty plans array renders fallback_text bare, without a plate, because this block carries no enclosure of its own.
Uneven feature countsCards 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.