Skip to content
Block Kit for React
Esc
↑↓navigate↵open⌘Jpreview
On this page

Select menus

Dropdowns for a fixed list, an external data source, or Slack's own directory of users, conversations, and channels.

A select menu is a dropdown that reports one chosen value (*_select) or several (multi_*_select). Slack has five data sources: static, external, users, conversations, and channels, each with its own single- and multi-select type, and its own field name in state.values. Use it wherever a button’s fixed set of choices isn’t enough: picking a user to assign, a channel to post to, or an option from a list you don’t want to spell out as buttons.

Your AppAPP
onActionInteract with the preview.

Fields

Every select shares these:

PropType
typestring

One of `static_select`, `external_select`, `users_select`, `conversations_select`, `channels_select`, or the `multi_` prefixed variant of each.

Typestring
action_idstring

Identifies this element in `block_actions` and `state.values`.

Typestring
placeholder?PlainTextElement

Shown in the closed control when nothing is selected.

TypePlainTextElement
confirm?ConfirmationDialog

Shows a confirm dialog before the selection is committed.

TypeConfirmationDialog
focus_on_load?boolean

Focuses this element when the surface loads. Only one element per view should set it.

Typeboolean
Defaultfalse

A multi_*_select additionally accepts:

PropType
max_selected_items?number

Caps how many items can be picked at once. Renders as Slack's "You can select up to N items." hint in an `input` block.

Typenumber

Each data source adds its own fields, covered in its section below.

Examples

Static select

Options come from a fixed list you provide: options, or option_groups to label clusters of them. Slack caps options and option_groups at 100 entries each, and a select can carry one or the other, never both.

Your AppAPP
New issue

As an input block, a multi-static-select shows selected options as removable chips inside the field, and grows to fit them:

New issue

Labels
PropType
options?PlainTextOption[]

Up to 100 options. Omit if `option_groups` is set.

TypePlainTextOption[]
option_groups?OptionGroup[]

Up to 100 labeled groups of options, each up to 100 options. Omit if `options` is set.

TypeOptionGroup[]
initial_option?PlainTextOption

Preselected on load. Must exactly match one entry in `options`/`option_groups`. Single-select only.

TypePlainTextOption
initial_options?PlainTextOption[]

Preselected on load, one per chip. Multi-select only.

TypePlainTextOption[]

External select

Options aren’t provided up front. Slack (and this library) asks your app for them as the person types, via a block_suggestion request. Give the <BlockKitProvider> an onOptions handler; it’s called with the same payload Slack would send your app’s options load URL, and its return value is what fills the menu.

import { BlockKitProvider, Message } from "@nkootstra/block-kit";

const fruits = ["Apple", "Banana", "Cherry", "Grape", "Mango", "Orange", "Peach", "Pear"];

function FruitPicker() {
  return (
    <BlockKitProvider
      onOptions={({ value }) => ({
        options: fruits
          .filter((fruit) => fruit.toLowerCase().startsWith(value.toLowerCase()))
          .map((fruit) => ({
            text: { type: "plain_text", text: fruit },
            value: fruit.toLowerCase(),
          })),
      })}
    >
      <Message
        blocks={[
          {
            type: "actions",
            block_id: "fruit",
            elements: [
              {
                type: "external_select",
                action_id: "fruit",
                placeholder: { type: "plain_text", text: "Favorite fruit" },
                min_query_length: 2,
              },
            ],
          },
        ]}
      />
    </BlockKitProvider>
  );
}

The request looks like this (see Interactivity below for the full shape):

{
  "type": "block_suggestion",
  "action_id": "fruit",
  "block_id": "fruit",
  "value": "ap",
  "container": { "type": "message", "message_ts": "..." }
}

Your handler answers with { options } or { option_groups }, the same shapes as a static select’s, built from SuggestionOption objects (text, value, optional description/url).

The Preview above already answers external_select from a fixed fruit list, so you can try typing without wiring up onOptions yourself.

min_query_length sets how many characters must be typed before the request fires:

PropType
initial_option?PlainTextOption

Preselected on load. Single-select only.

TypePlainTextOption
initial_options?PlainTextOption[]

Preselected on load. Multi-select only.

TypePlainTextOption[]
min_query_length?number

Fewest characters typed before a `block_suggestion` request fires.

Typenumber
Default3

A multi-external-select combines max_selected_items with the same lookup, and can start with options already picked via initial_options:

New issue

Tags (up to 3) (optional)

You can select up to 3 items.

Options load from the app as you type.

Users select

Populated from Slack’s own user directory: there’s nothing to pass for the option list itself, only which user (if any) starts selected.

Your AppAPP
Code review

As an input block, multi_users_select starts with initial_users already selected as chips:

Code review

Reviewers
PropType
initial_user?string

A user ID preselected on load. Single-select only.

Typestring
initial_users?string[]

User IDs preselected on load, one chip each. Multi-select only.

Typestring[]

This library can’t look up a real workspace directory, so an id it doesn’t know (anything besides U0ADA, U0GRACE, U0ALAN) renders as Slack’s own loading skeleton rather than the raw id. Pass a resolvers.user function to <BlockKitProvider> to show real names instead.

Conversations select

Populated from every conversation the person can see: public and private channels, DMs, and group DMs. default_to_current_conversation preselects whatever conversation the surface was opened from, and filter narrows the list Slack offers.

Your AppAPP

A filter object restricts which conversation types Slack shows, and can drop bot users or external shared channels from the list:

Your AppAPP
PropType
initial_conversation?string

A conversation ID preselected on load. Takes precedence over `default_to_current_conversation`. Single-select only.

Typestring
initial_conversations?string[]

Conversation IDs preselected on load. Ignored if `default_to_current_conversation` is set. Multi-select only.

Typestring[]
default_to_current_conversation?boolean

Preselects the conversation the surface was opened from, if any.

Typeboolean
Defaultfalse
filter?ConversationFilter

Narrows the list Slack offers. See below.

TypeConversationFilter

filter’s fields:

PropType
include?("im" | "mpim" | "private" | "public")[]

Only these conversation types are offered. Omit to allow all four.

Type("im" | "mpim" | "private" | "public")[]
exclude_bot_users?boolean

Drops bot users from the list.

Typeboolean
Defaultfalse
exclude_external_shared_channels?boolean

Drops Slack Connect (externally shared) channels from the list. Doesn't exclude external users from shared channels that remain.

Typeboolean
Defaultfalse

Channels select

The same idea as a conversations select, restricted to public channels the person is a member of. It has no filter or default_to_current_conversation.

Post to

Channel

As an actions element, a multi-channels-select with items already picked collapses to a content-sized outline button rather than the 190px dropdown a single-select uses:

Your AppAPP
PropType
initial_channel?string

A channel ID preselected on load. Single-select only.

Typestring
initial_channels?string[]

Channel IDs preselected on load. Multi-select only.

Typestring[]

Interactivity

A select reports its value the moment it changes, with no separate submit step outside a modal. The field name in both the block_actions action and state.values depends on the data source and whether it’s single- or multi-select:

Source Single Multi
static_select selected_option selected_options
external_select selected_option selected_options
users_select selected_user selected_users
conversations_select selected_conversation selected_conversations
channels_select selected_channel selected_channels

A static_select/external_select reports the full option object; the other sources report just the id string.

Single static select, from the priority example above:

{
  "type": "static_select",
  "action_id": "priority",
  "block_id": "assign",
  "selected_option": { "text": { "type": "plain_text", "text": "High" }, "value": "high" }
}

state.values.assign.priority holds the same selected_option shape, tagged with type:

{
  "type": "static_select",
  "selected_option": { "text": { "type": "plain_text", "text": "High" }, "value": "high" }
}

Multi users select, from the reviewers example above, after picking Ada and Grace:

{
  "type": "multi_users_select",
  "action_id": "value",
  "block_id": "reviewers",
  "selected_users": ["U0ADA", "U0GRACE"]
}

A conversations select with a filter, from the private-channels example:

{
  "type": "multi_conversations_select",
  "action_id": "conversations",
  "block_id": "notify",
  "selected_conversations": ["C0DESIGN"]
}

Reading it in React

import { BlockKitProvider, Message } from "@nkootstra/block-kit";

<BlockKitProvider
  onAction={(action) => {
    if (action.action_id === "priority") {
      console.log(action.selected_option.value); // "high"
    }
    if (action.action_id === "reviewer") {
      console.log(action.selected_user); // "U0ADA"
    }
  }}
>
  <Message blocks={blocks} />
</BlockKitProvider>;

block_suggestion (external select)

An external_select/multi_external_select additionally sends a block_suggestion request to onOptions while the person types, once the query reaches min_query_length:

interface BlockSuggestionPayload {
  type: "block_suggestion";
  action_id: string;
  block_id: string;
  value: string; // what's typed so far
  container: { type: "message" | "view" /* ... */ };
  // team, user, api_app_id, token: identity fields, like any Slack payload
}

onOptions returns either flat options or labeled groups:

type OptionsResponse =
  | { options: SuggestionOption[] }
  | { option_groups: { label: PlainTextElement; options: SuggestionOption[] }[] };

Was this page helpful?