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.
Fields
Every select shares these:
typestring
One of `static_select`, `external_select`, `users_select`, `conversations_select`, `channels_select`, or the `multi_` prefixed variant of each.
stringaction_idstring
Identifies this element in `block_actions` and `state.values`.
stringplaceholder?PlainTextElement
Shown in the closed control when nothing is selected.
PlainTextElementconfirm?ConfirmationDialog
Shows a confirm dialog before the selection is committed.
ConfirmationDialogfocus_on_load?boolean
Focuses this element when the surface loads. Only one element per view should set it.
booleanfalseA multi_*_select additionally accepts:
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.
numberEach 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.
As an input block, a multi-static-select shows selected options as removable chips inside the
field, and grows to fit them:
New issue
options?PlainTextOption[]
Up to 100 options. Omit if `option_groups` is set.
PlainTextOption[]option_groups?OptionGroup[]
Up to 100 labeled groups of options, each up to 100 options. Omit if `options` is set.
OptionGroup[]initial_option?PlainTextOption
Preselected on load. Must exactly match one entry in `options`/`option_groups`. Single-select only.
PlainTextOptioninitial_options?PlainTextOption[]
Preselected on load, one per chip. Multi-select only.
PlainTextOption[]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:
initial_option?PlainTextOption
Preselected on load. Single-select only.
PlainTextOptioninitial_options?PlainTextOption[]
Preselected on load. Multi-select only.
PlainTextOption[]min_query_length?number
Fewest characters typed before a `block_suggestion` request fires.
number3A multi-external-select combines max_selected_items with the same lookup, and can start with
options already picked via initial_options:
New issue
You can select up to 3 items.
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.
As an input block, multi_users_select starts with initial_users already selected as chips:
Code review
initial_user?string
A user ID preselected on load. Single-select only.
stringinitial_users?string[]
User IDs preselected on load, one chip each. Multi-select only.
string[]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.
A filter object restricts which conversation types Slack shows, and can drop bot users or
external shared channels from the list:
initial_conversation?string
A conversation ID preselected on load. Takes precedence over `default_to_current_conversation`. Single-select only.
stringinitial_conversations?string[]
Conversation IDs preselected on load. Ignored if `default_to_current_conversation` is set. Multi-select only.
string[]default_to_current_conversation?boolean
Preselects the conversation the surface was opened from, if any.
booleanfalsefilter?ConversationFilter
Narrows the list Slack offers. See below.
ConversationFilterfilter’s fields:
include?("im" | "mpim" | "private" | "public")[]
Only these conversation types are offered. Omit to allow all four.
("im" | "mpim" | "private" | "public")[]exclude_bot_users?boolean
Drops bot users from the list.
booleanfalseexclude_external_shared_channels?boolean
Drops Slack Connect (externally shared) channels from the list. Doesn't exclude external users from shared channels that remain.
booleanfalseChannels 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
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:
initial_channel?string
A channel ID preselected on load. Single-select only.
stringinitial_channels?string[]
Channel IDs preselected on load. Multi-select only.
string[]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[] }[] };
Related
Elements overview
Where each interactive element can live, and how action_id ties it to state.values.
Checkboxes
A fixed set of options with all of them visible at once, instead of a dropdown.
Radio buttons
Like checkboxes, but only one option can be selected.
Overflow menu
A compact “…” menu for a short list of actions rather than data-backed options.