Skip to main content

Button Actions and Interactions

Add interactivity to your cards with buttons and action handlers. The SDK automatically routes button clicks to your registered handlers.

Button Types

Action Button

Triggers a callback handler when clicked.
interface ButtonElement {
  type: "button";
  id: string;              // Unique action ID for routing
  label: string;           // Button text
  style?: "primary" | "danger" | "default";
  value?: string;          // Optional payload
  disabled?: boolean;      // Inactive state
}
JSX Example:
<Actions>
  <Button id="approve" style="primary">Approve</Button>
  <Button id="reject" style="danger">Reject</Button>
  <Button id="info" disabled>Not Available</Button>
</Actions>
Function Example:
Actions([
  Button({ id: "approve", label: "Approve", style: "primary" }),
  Button({ id: "reject", label: "Reject", style: "danger" }),
  Button({ id: "delete", label: "Delete", value: "item-123" }),
])
Opens a URL when clicked (no callback handler).
interface LinkButtonElement {
  type: "link-button";
  url: string;
  label: string;
  style?: "primary" | "danger" | "default";
}
Example:
<Actions>
  <LinkButton url="https://docs.example.com" label="View Docs" />
  <LinkButton 
    url="https://example.com/dashboard" 
    label="Open Dashboard"
    style="primary"
  />
</Actions>

Handling Button Clicks

Register handlers for button actions using the action ID:
import { Chat } from "chat";

const chat = new Chat({ /* ... */ });

// Handle a specific action
chat.onAction("approve", async (event) => {
  await event.thread.post(
    `Order approved by ${event.user.userName}! ✓`
  );
});

// Handle multiple actions
chat.onAction(["approve", "reject"], async (event) => {
  if (event.actionId === "approve") {
    await event.thread.post("✓ Approved");
  } else {
    await event.thread.post("✗ Rejected");
  }
});

Action Event

Handlers receive an ActionEvent with full context:
interface ActionEvent {
  actionId: string;        // Button's id
  value?: string;          // Button's value payload
  user: Author;            // Who clicked
  thread: Thread;          // Where it happened
  threadId: string;
  messageId: string;       // Message containing the button
  triggerId?: string;      // For opening modals
  adapter: Adapter;        // Platform adapter
  raw: unknown;            // Platform-specific data
  
  // Open a modal in response
  openModal(modal: ModalElement): Promise<{ viewId: string } | undefined>;
}

Using Button Values

Pass data through button clicks with the value field:
// Post a card with order ID in button value
await thread.post(
  <Card title="Order #1234">
    <Text>Total: $50.00</Text>
    <Actions>
      <Button id="approve" value="order-1234">Approve</Button>
      <Button id="reject" value="order-1234">Reject</Button>
    </Actions>
  </Card>
);

// Handler receives the value
chat.onAction("approve", async (event) => {
  const orderId = event.value; // "order-1234"
  
  // Process the order
  await processOrder(orderId);
  
  await event.thread.post(`Order ${orderId} has been approved!`);
});

Catch-All Handler

Handle all button clicks with a single handler:
chat.onAction(async (event) => {
  console.log(`Action: ${event.actionId}`);
  console.log(`User: ${event.user.userName}`);
  console.log(`Value: ${event.value}`);
});

Actions Container

Buttons must be wrapped in an Actions container:
interface ActionsElement {
  type: "actions";
  children: (
    | ButtonElement
    | LinkButtonElement
    | SelectElement         // From modals
    | RadioSelectElement    // From modals
  )[];
}
Example with mixed elements:
<Actions>
  <Button id="save" style="primary">Save</Button>
  <Button id="cancel">Cancel</Button>
  <LinkButton url="https://example.com" label="Learn More" />
</Actions>

Updating Messages After Actions

Edit the message that contained the button:
chat.onAction("approve", async (event) => {
  // Get the message that contained the button
  const message = await event.adapter.fetchMessage(
    event.threadId,
    event.messageId
  );
  
  if (message) {
    // Create a SentMessage to enable editing
    const sentMessage = event.thread.createSentMessageFromMessage(message);
    
    // Update the card
    await sentMessage.edit(
      <Card title="Order #1234">
        <Text style="bold">Approved by {event.user.userName}</Text>
        <Text style="muted">Action completed</Text>
      </Card>
    );
  }
});

Complete Example

Approval workflow with status updates:
import { Chat, Card, Text, Fields, Field, Actions, Button } from "chat";

const chat = new Chat({ /* ... */ });

// Post approval request
chat.onNewMention(async (thread, message) => {
  const orderId = "ORD-" + Date.now();
  
  await thread.post(
    <Card title="New Order Approval">
      <Fields>
        <Field label="Order ID" value={orderId} />
        <Field label="Amount" value="$50.00" />
        <Field label="Requested by" value={message.author.userName} />
      </Fields>
      <Actions>
        <Button id="approve" value={orderId} style="primary">
          Approve
        </Button>
        <Button id="reject" value={orderId} style="danger">
          Reject
        </Button>
      </Actions>
    </Card>
  );
});

// Handle approval
chat.onAction("approve", async (event) => {
  const orderId = event.value;
  
  await event.thread.post(
    `✓ Order ${orderId} approved by ${event.user.userName}`
  );
  
  // Process approval in your system
  await approveOrder(orderId);
});

// Handle rejection
chat.onAction("reject", async (event) => {
  const orderId = event.value;
  
  await event.thread.post(
    `✗ Order ${orderId} rejected by ${event.user.userName}`
  );
});

Button Styles

Three visual styles are available:
  • primary - Blue/prominent (Slack: primary, Teams: accent)
  • danger - Red/destructive (Slack: danger, Teams: destructive)
  • default - Gray/secondary (default if not specified)
<Actions>
  <Button id="save" style="primary">Save Changes</Button>
  <Button id="delete" style="danger">Delete</Button>
  <Button id="cancel" style="default">Cancel</Button>
</Actions>

Next Steps

Modals

Open forms and dialogs from button clicks

Error Handling

Handle failures in action handlers