comparison
Two to four things on the same rows, read side by side.
Side by side. comparison lines up two to four things — shoes, plans, hotels, phones — on the same rows, so "which is lighter" is answered by reading across one line rather than two lists.
Each row puts its label above the values, so a value has a whole column even with four things on a phone, and nothing scrolls sideways. Once the things' own header has scrolled away, their names stay pinned at the top while the rows go by.
Re-rendered in the browser from the SDK’s own scales. Not a screenshot.
The best value, and the opinion
best_index marks the best value in a row, and only where the data makes it measurably best: the lowest price, the most storage, the lightest shoe. Which one the user should buy is an opinion, and an opinion goes in summary, under the table, in words.
kind: "check" rows hold "yes" or "no" and are drawn as ✓ and —.
When not to use it
Not for one thing's details, which is key_value. Not for many records sharing columns, which is table. It takes the place of the old plan_comparison: plans are items, features are check rows.
Props
Every block also carries type and a required fallback_text of 1 to 300 characters.
| Prop | Type | Required | Constraints |
|---|---|---|---|
title | string | No | Up to 80 characters. |
items | array | Yes | 2 to 4 entries. |
items[].title | string | Yes | 1 to 60 characters. |
items[].subtitle | string | No | Up to 80 characters. |
items[].image_url | string | No | None |
items[].price | string | No | Up to 30 characters. |
items[].badge | string | No | Up to 24 characters. |
items[].action | action | No | { kind: "reply", text } or { kind: "open_url", url } or { kind: "navigate", route } or { kind: "call", phone } or { kind: "copy", text } or { kind: "add_to_calendar", title, starts_at }. See Actions. |
rows | array | Yes | 1 to 12 entries. |
rows[].label | string | Yes | 1 to 40 characters. |
rows[].values | string[] | Yes | 2 to 4 strings, up to 60 characters each. |
rows[].best_index | integer | No | 0 to 3. |
rows[].kind | text | check | No | None |
summary | string | No | Up to 200 characters. |
On the wire
{
"id": "blk_70",
"type": "comparison",
"fallback_text": "Aero Glide 2 is lighter; Trail Circuit is waterproof.",
"props": {
"items": [
{ "title": "Aero Glide 2", "price": "$129", "image_url": "https://…" },
{ "title": "Trail Circuit", "price": "$149", "image_url": "https://…" }
],
"rows": [
{ "label": "Weight", "values": ["248 g", "296 g"], "best_index": 0 },
{ "label": "Drop", "values": ["8 mm", "6 mm"] },
{ "label": "Waterproof", "values": ["no", "yes"], "kind": "check" }
],
"summary": "On the road the Aero Glide is lighter and softer; on wet trails the Trail Circuit grips."
}
}Accessibility
A real table: the things are column headers, each row's label its row header, and the best value is announced as the best.