---
title: Quickstart
description: Render an approval message and answer its buttons the way your Slack app would.
sidebar:
  order: 2
---

In this guide you render a message with two buttons, handle the click, and replace the buttons with the outcome, the same flow a Slack app runs with `response_url`.

This guide assumes you've finished [Installation](/installation).

1. **Render the message**

    Pass the `blocks` your app sends to `chat.postMessage` straight to `<Message>`:

    ```tsx Approval.tsx
    import { Message } from "@nkootstra/block-kit";

    const blocks = [
      {
        type: "section",
        text: { type: "mrkdwn", text: "<@U0ADA> wants to deploy *api@4.2.0* to production." },
      },
      {
        type: "actions",
        block_id: "deploy",
        elements: [
          {
            type: "button",
            style: "primary",
            action_id: "approve",
            value: "api@4.2.0",
            text: { type: "plain_text", text: "Approve" },
          },
          {
            type: "button",
            action_id: "reject",
            value: "api@4.2.0",
            text: { type: "plain_text", text: "Reject" },
          },
        ],
      },
    ];

    export function Approval() {
      return <Message app={{ name: "Deploy Bot" }} blocks={blocks} />;
    }
    ```

    <Preview
      payload={{
        blocks: [
          {
            type: "section",
            text: { type: "mrkdwn", text: "<@U0ADA> wants to deploy *api@4.2.0* to production." },
          },
          {
            type: "actions",
            block_id: "deploy",
            elements: [
              {
                type: "button",
                style: "primary",
                action_id: "approve",
                value: "api@4.2.0",
                text: { type: "plain_text", text: "Approve" },
              },
              {
                type: "button",
                action_id: "reject",
                value: "api@4.2.0",
                text: { type: "plain_text", text: "Reject" },
              },
            ],
          },
        ],
      }}
    />

    `<@U0ADA>` renders as a mention. Tell the renderer who that is with [resolvers](/guides/mentions-and-resolvers); until then it shows the raw id, as Slack does for a user it can't find.

2. **Handle the click**

    Wrap the message in a `<BlockKitProvider>` and pass `onAction`. It receives each action in the shape of one entry of a `block_actions` payload's `actions` array:

    ```tsx Approval.tsx
    import { BlockKitProvider, Message, type BlockAction } from "@nkootstra/block-kit";

    function handleAction(action: BlockAction) {
      console.log(action.action_id, action.value);
      // → "approve" "api@4.2.0"
    }

    export function Approval() {
      return (
        <BlockKitProvider onAction={handleAction}>
          <Message app={{ name: "Deploy Bot" }} blocks={blocks} />
        </BlockKitProvider>
      );
    }
    ```

    Click a button below to see the action it sends:

    <Preview
      actions
      payload={{
        blocks: [
          {
            type: "section",
            text: { type: "mrkdwn", text: "<@U0ADA> wants to deploy *api@4.2.0* to production." },
          },
          {
            type: "actions",
            block_id: "deploy",
            elements: [
              {
                type: "button",
                style: "primary",
                action_id: "approve",
                value: "api@4.2.0",
                text: { type: "plain_text", text: "Approve" },
              },
              {
                type: "button",
                action_id: "reject",
                value: "api@4.2.0",
                text: { type: "plain_text", text: "Reject" },
              },
            ],
          },
        ],
      }}
    />

3. **Update the message**

    The second argument to `onAction` lets you answer the way an app would. `message.update` replaces the message the action came from, like posting to `response_url` with `replace_original`:

    ```tsx Approval.tsx
    import {
      BlockKitProvider,
      Message,
      type ActionContext,
      type BlockAction,
    } from "@nkootstra/block-kit";

    function handleAction(action: BlockAction, { message }: ActionContext) {
      const verdict = action.action_id === "approve" ? ":white_check_mark: Approved" : ":x: Rejected";

      message?.update({
        blocks: [
          blocks[0],
          { type: "context", elements: [{ type: "mrkdwn", text: `${verdict} by <@U0GRACE>` }] },
        ],
      });
    }
    ```

    After a click, the message becomes:

    <Preview
      payload={{
        blocks: [
          {
            type: "section",
            text: { type: "mrkdwn", text: "<@U0ADA> wants to deploy *api@4.2.0* to production." },
          },
          {
            type: "context",
            elements: [{ type: "mrkdwn", text: ":white_check_mark: Approved by <@U0GRACE>" }],
          },
        ],
      }}
    />

    The context also has `views`, to open a modal, and `state`, with the current value of every input in the message.

## Already have a Slack app?

If your app already handles interactions, with Bolt or your own request URL, you don't need to rewrite its logic in React. Relay the clicks to it instead, and apply whatever it answers: see [Connecting your app](/guides/connecting-your-app).

## Next steps

**[Surfaces](/surfaces)**

Messages, modals and Home tabs.

**[Interactivity](/guides/interactivity)**

Every action, the state of inputs, and full payloads.

**[Modals](/guides/modals)**

Open, push and update views, and validate submissions.

**[Blocks](/blocks)**

Browse every block.
