---
title: Cards
description: Rich card components for cross-platform interactive messages.
type: reference
---

# Cards



Card components render natively on each platform — Block Kit on Slack, Adaptive Cards on Teams, Embeds on Discord, and Google Chat Cards.

```typescript
import { Card, Text, CardLink, Button, Actions, Section, Fields, Field, Divider, Image, LinkButton, Table } from "chat";
```

All components support both function-call and JSX syntax. Function-call syntax is recommended for better type inference.

## Card

Top-level container for a rich message.

```typescript
Card({
  title: "Order #1234",
  subtitle: "Pending approval",
  children: [Text("Total: $50.00")],
})
```

<TypeTable
  type={{
  title: {
    description: 'Card title.',
    type: 'string',
  },
  subtitle: {
    description: 'Card subtitle.',
    type: 'string',
  },
  imageUrl: {
    description: 'Header image URL.',
    type: 'string',
  },
  children: {
    description: 'Card content elements.',
    type: 'CardChild[]',
  },
}}
/>

## Text

Text content element. Use `CardText` instead of `Text` in JSX to avoid conflicts with React's built-in types.

```typescript
Text("Hello, world!")
Text("Important", { style: "bold" })
Text("Subtle note", { style: "muted" })
```

<TypeTable
  type={{
  content: {
    description: 'Text content (first argument).',
    type: 'string',
  },
  'options.style': {
    description: 'Text style.',
    type: '"plain" | "bold" | "muted"',
  },
}}
/>

## Button

Interactive button that triggers an `onAction` handler.

```typescript
Button({ id: "approve", label: "Approve", style: "primary" })
Button({ id: "delete", label: "Delete", style: "danger", value: "item-123" })
```

<TypeTable
  type={{
  id: {
    description: 'Unique action ID for callback routing.',
    type: 'string',
  },
  label: {
    description: 'Button label text.',
    type: 'string',
  },
  style: {
    description: 'Visual style.',
    type: '"primary" | "danger" | "default"',
  },
  value: {
    description: 'Optional payload sent with the action callback.',
    type: 'string',
  },
  actionType: {
    description: 'Hints to adapters like Teams that this button will open a modal via event.openModal().',
    type: '"action" | "modal"',
    default: '"action"',
  },
  callbackUrl: {
    description: 'URL to POST action data to when this button is clicked.',
    type: 'string',
  },
}}
/>

## CardLink

Inline hyperlink rendered as text. Can be placed directly in a card alongside other content, unlike `LinkButton` which must live inside `Actions`.

```typescript
CardLink({ url: "https://example.com", label: "Visit Site" })
```

<TypeTable
  type={{
  url: {
    description: 'URL to link to.',
    type: 'string',
  },
  label: {
    description: 'Link label text.',
    type: 'string',
  },
}}
/>

## LinkButton

Button that opens a 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.

```typescript
LinkButton({ url: "https://example.com", label: "View Docs" })
LinkButton({ id: "view_docs", url: "https://example.com", label: "View Docs" })
```

<TypeTable
  type={{
  id: {
    description: 'Optional action identifier emitted by platforms that report link clicks.',
    type: 'string',
  },
  url: {
    description: 'URL to open when clicked.',
    type: 'string',
  },
  label: {
    description: 'Button label text.',
    type: 'string',
  },
  style: {
    description: 'Visual style.',
    type: '"primary" | "danger" | "default"',
  },
}}
/>

## Actions

Container for buttons and interactive elements. Required wrapper around `Button`, `LinkButton`, `Select`, and `RadioSelect`.

```typescript
Actions([
  Button({ id: "approve", label: "Approve", style: "primary" }),
  Button({ id: "reject", label: "Reject", style: "danger" }),
  LinkButton({ url: "https://example.com", label: "View" }),
])
```

## Section

Groups related content together.

```typescript
Section([
  Text("Grouped content"),
  Image({ url: "https://example.com/photo.png" }),
])
```

## Fields

Renders key-value pairs in a compact, multi-column layout.

```typescript
Fields([
  Field({ label: "Name", value: "Jane Smith" }),
  Field({ label: "Role", value: "Engineer" }),
])
```

## Field

A single key-value pair. Must be used inside `Fields`.

<TypeTable
  type={{
  label: {
    description: 'Field label.',
    type: 'string',
  },
  value: {
    description: 'Field value.',
    type: 'string',
  },
}}
/>

## Image

Embeds an image in the card.

```typescript
Image({ url: "https://example.com/screenshot.png", alt: "Screenshot" })
```

<TypeTable
  type={{
  url: {
    description: 'Image URL.',
    type: 'string',
  },
  alt: {
    description: 'Alt text for accessibility.',
    type: 'string',
  },
}}
/>

## Table

Structured data display with column headers and rows.

```typescript
Table({
  headers: ["Name", "Age", "Role"],
  rows: [
    ["Alice", "30", "Engineer"],
    ["Bob", "25", "Designer"],
  ],
})
```

<TypeTable
  type={{
  headers: {
    description: 'Column header labels.',
    type: 'string[]',
  },
  rows: {
    description: 'Data rows (each row is an array of cell strings).',
    type: 'string[][]',
  },
  align: {
    description: 'Column alignment.',
    type: '"left" | "center" | "right"[]',
  },
}}
/>

On platforms with native table support (Teams, GitHub, Linear), tables render as formatted tables. On other platforms (Slack, Google Chat, Discord, Telegram), tables render as padded ASCII text.

## Divider

A visual separator between sections.

```typescript
Divider()
```

## CardChild types

The `children` array in `Card` and `Section` accepts these element types:

| Type             | Created by   |
| ---------------- | ------------ |
| `TextElement`    | `Text()`     |
| `LinkElement`    | `CardLink()` |
| `ImageElement`   | `Image()`    |
| `DividerElement` | `Divider()`  |
| `ActionsElement` | `Actions()`  |
| `SectionElement` | `Section()`  |
| `FieldsElement`  | `Fields()`   |
| `TableElement`   | `Table()`    |
