---
title: Cards
description: Send rich interactive cards with buttons, fields, and images across all platforms.
type: guide
prerequisites:
  - /docs/usage
related:
  - /docs/actions
  - /docs/modals
  - /docs/api/cards
  - /docs/emoji
---

# Cards



Cards let you send structured, interactive messages that render natively on each platform — Block Kit on Slack, Adaptive Cards on Teams, Discord embeds or Components, and Google Chat Cards.

## Setup

Configure your `tsconfig.json` to use the Chat SDK JSX runtime:

```json title="tsconfig.json"
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "chat"
  }
}
```

Or use a per-file pragma:

```tsx title="lib/bot.tsx"
/** @jsxImportSource chat */
```

## Basic card

```tsx title="lib/bot.tsx" lineNumbers
import { Card, CardText, Button, Actions } from "chat";

await thread.post(
  <Card title="Order #1234">
    <CardText>Your order has been received!</CardText>
    <Actions>
      <Button id="approve" style="primary">Approve</Button>
      <Button id="reject" style="danger">Reject</Button>
    </Actions>
  </Card>
);
```

## Components

### Card

The top-level container. Accepts `title` and optional `subtitle`.

```tsx title="lib/bot.tsx" lineNumbers
<Card title="My Card" subtitle="Optional subtitle">
  {/* children */}
</Card>
```

### CardText

Renders formatted text. Supports a subset of markdown.

```tsx title="lib/bot.tsx" lineNumbers
<CardText>**Bold** and _italic_ text</CardText>
<CardText style="bold">Bold section header</CardText>
```

<Callout type="info">
  Use `CardText` instead of `Text` when using JSX to avoid conflicts with React's built-in types.
</Callout>

### Section

Groups related content together.

```tsx title="lib/bot.tsx" lineNumbers
<Section>
  <CardText>Section content here</CardText>
</Section>
```

### Fields

Renders key-value pairs in a compact layout.

```tsx title="lib/bot.tsx" lineNumbers
<Fields>
  <Field label="Name" value="John Doe" />
  <Field label="Role" value="Developer" />
  <Field label="Team" value="Platform" />
</Fields>
```

### Button

An action button that triggers an `onAction` handler.

```tsx title="lib/bot.tsx" lineNumbers
<Button id="approve" style="primary">Approve</Button>
<Button id="reject" style="danger">Reject</Button>
<Button id="details">View Details</Button>
```

The `id` maps to your `onAction` handler. Optional `value` passes extra data:

```tsx title="lib/bot.tsx"
<Button id="report" value="bug">Report Bug</Button>
```

Set `actionType="modal"` to indicate the button opens a [modal](/docs/modals). The button still triggers your `onAction` handler, where you call `event.openModal()` — this prop tells adapters like Teams to wire up the button for dialog opening:

```tsx title="lib/bot.tsx"
<Button id="open-feedback" actionType="modal">Give Feedback</Button>
```

Optional `callbackUrl` causes the action data to be POSTed to a URL when clicked. See [Callback URLs](/docs/actions#callback-urls) for details.

```tsx title="lib/bot.tsx"
<Button callbackUrl={webhook.url} id="approve" style="primary">Approve</Button>
```

### CardLink

Inline hyperlink rendered as text. Unlike `LinkButton` (which must be inside `Actions`), `CardLink` can be placed directly in a card alongside other content.

```tsx title="lib/bot.tsx"
<CardLink url="https://example.com/order/1234" label="View order details" />
```

Or with children as the label:

```tsx title="lib/bot.tsx"
<CardLink url="https://example.com/docs">Read the docs</CardLink>
```

<Callout type="info">
  `CardLink` renders as a platform-native link: `<url|label>` on Slack, `[label](url)` on Teams/Discord/GitHub/Linear, and `<a href>` on Google Chat.
</Callout>

### LinkButton

Opens an external URL. No `onAction` handler needed for navigation. On platforms
that emit link-button click events, such as Slack, pass `id` when you need a
stable action identifier for routing or analytics.

```tsx title="lib/bot.tsx"
<LinkButton url="https://example.com/order/1234">View Order</LinkButton>
```

```tsx title="lib/bot.tsx"
<LinkButton id="view_order" url="https://example.com/order/1234">
  View Order
</LinkButton>
```

### Actions

Container for buttons and interactive elements.

```tsx title="lib/bot.tsx" lineNumbers
<Actions>
  <Button id="approve" style="primary">Approve</Button>
  <Button id="reject" style="danger">Reject</Button>
  <LinkButton url="https://example.com">View</LinkButton>
</Actions>
```

### Select

Inline dropdown menu.

```tsx title="lib/bot.tsx" lineNumbers
<Actions>
  <Select id="priority" label="Priority" placeholder="Select priority">
    <SelectOption label="High" value="high" description="Urgent tasks" />
    <SelectOption label="Medium" value="medium" />
    <SelectOption label="Low" value="low" />
  </Select>
</Actions>
```

Selection triggers an `onAction` handler with the `id` as the `actionId` and the selected value.

### RadioSelect

Radio button group for mutually exclusive choices.

```tsx title="lib/bot.tsx" lineNumbers
<Actions>
  <RadioSelect id="status" label="Status">
    <SelectOption label="Open" value="open" />
    <SelectOption label="In Progress" value="in_progress" />
    <SelectOption label="Done" value="done" />
  </RadioSelect>
</Actions>
```

### Table

Structured data display with column headers and rows. Renders as a native table on platforms that support it (Slack, Teams, GitHub, Linear), as GFM markdown in Discord card payloads, and as padded ASCII text elsewhere.

```tsx title="lib/bot.tsx" lineNumbers
<Table
  headers={["Name", "Age", "Role"]}
  rows={[
    ["Alice", "30", "Engineer"],
    ["Bob", "25", "Designer"],
  ]}
/>
```

Optional column alignment:

```tsx title="lib/bot.tsx"
<Table
  headers={["Name", "Amount"]}
  rows={[["Alice", "$100"], ["Bob", "$200"]]}
  align={["left", "right"]}
/>
```

On Slack, tables render as paginated, sortable [data tables](https://docs.slack.dev/reference/block-kit/blocks/data-table-block). The optional `caption` (accessible table description) and `pageSize` (rows per page, 1–100) props tune that rendering and are ignored on other platforms:

```tsx title="lib/bot.tsx"
<Table
  headers={["Name", "Score"]}
  rows={[["Alice", "98"], ["Bob", "87"]]}
  caption="Quarterly review scores"
  pageSize={10}
/>
```

### Chart

Data visualization with pie, bar, area, and line charts. Renders as a native [data visualization](https://docs.slack.dev/reference/block-kit/blocks/data-visualization-block) on Slack; other platforms fall back to the chart's data rendered as a text table.

```tsx title="lib/bot.tsx" lineNumbers
<Chart
  title="My Favorite Candy Bars"
  chart={{
    type: "pie",
    segments: [
      { label: "Kit Kat", value: 45 },
      { label: "Twix", value: 28 },
      { label: "Crunch", value: 18 },
    ],
  }}
/>
```

Bar, area, and line charts take named series plotted against shared categories:

```tsx title="lib/bot.tsx" lineNumbers
<Chart
  title="Daily Active Users"
  chart={{
    type: "line",
    categories: ["Mon", "Tue", "Wed"],
    xLabel: "Day",
    yLabel: "Users",
    series: [
      {
        name: "Web",
        data: [
          { label: "Mon", value: 120 },
          { label: "Tue", value: 135 },
          { label: "Wed", value: 128 },
        ],
      },
      {
        name: "Mobile",
        data: [
          { label: "Mon", value: 80 },
          { label: "Tue", value: 95 },
          { label: "Wed", value: 90 },
        ],
      },
    ],
  }}
/>
```

Slack enforces a 50-character title, up to 12 segments or series, up to 20 categories, 20-character labels, and at most 2 charts per message. Charts that exceed these limits fall back to a text rendering of the data instead of being rejected by the API.

### Image

Embeds an image in the card.

```tsx title="lib/bot.tsx"
<Image url="https://example.com/screenshot.png" alt="Screenshot" />
```

### Divider

A visual separator between sections.

```tsx title="lib/bot.tsx" lineNumbers
<CardText>Above the line</CardText>
<Divider />
<CardText>Below the line</CardText>
```

## Full example

```tsx title="lib/bot.tsx" lineNumbers
import {
  Card, CardText, CardLink, Button, LinkButton, Actions,
  Section, Fields, Field, Divider, Image,
  Select, SelectOption, RadioSelect,
} from "chat";

await thread.post(
  <Card title="User Profile" subtitle="Account details">
    <Image url="https://example.com/avatar.png" alt="User avatar" />
    <Fields>
      <Field label="Name" value="Jane Smith" />
      <Field label="Role" value="Engineer" />
      <Field label="Team" value="Platform" />
    </Fields>
    <CardLink url="https://example.com/profile/123">View full profile</CardLink>
    <Divider />
    <Section>
      <CardText>Select an action below to manage this profile.</CardText>
    </Section>
    <Actions>
      <Select id="role" label="Change Role" placeholder="Select role">
        <SelectOption label="Engineer" value="engineer" />
        <SelectOption label="Manager" value="manager" />
        <SelectOption label="Admin" value="admin" />
      </Select>
      <Button id="edit" style="primary">Edit Profile</Button>
      <Button id="deactivate" style="danger">Deactivate</Button>
      <LinkButton url="https://example.com/profile/123">View Full Profile</LinkButton>
    </Actions>
  </Card>
);
```


---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

For agent-facing discovery, including API and MCP surfaces, see [/agents.md](/agents.md)