Skip to content
shadcn.io

shadcn.io is not affiliated with official shadcn/ui

Multi-Select Labels Command Menu Block for Radix UI

An issue tracker's command menu shown open over an issue: ten labels in Type and Area groups ticked in place, Select all and Clear rows, and a batch bar whose Apply labels button puts the chosen labels on the issue.

Framework
UI library

Preview

Preview width

Installation

Part of Pro
Pro unlocks the code and one-line install of every Pro button and text effect, plus the MCP for your AI editor.
Have Pro already? Sign in to open it.Opens here as soon as you're Pro.

Overview

Tick several labels at once and apply them together with this multi-select command menu for React with Radix UI. It opens from Issue 318 in Labelwell, an issue tracker, whose header holds an outline field with its ⌘ K cap, and the issue is drawn under a soft scrim inside a hairline frame with a plus mark on each corner. The menu is the catalog Command: ten labels in Type and Area groups, each row a check box, the name and how many issues carry it, with Search Highlight Text marking the letters that match. Enter ticks or unticks a label and the menu stays open; while the field is empty two rows at the top select everything or clear the selection, and Ctrl or ⌘ A does the first. A batch bar under the list counts what is selected and holds Clear and an outline Apply labels Button, because a command menu has no primary action: Apply puts the labels on the drawn issue and closes the menu, and Escape closes it and keeps the labels the issue had. For preview the menu is drawn open in place when the page loads; the field, or Ctrl or ⌘ K while focus is in the block, opens the real command dialog with the same labels, selection and query. For issue trackers, tagging screens, bulk edits and any menu whose answer is a set, not a single thing.

FAQ

Why is the menu already open, and what do I keep?

So you can see it without a click. On load the menu is the Command drawn inline over a picture of an issue, in a plain shell that is not modal, so nothing is trapped, hidden or focused and the page around the preview keeps working. The header's field opens the real command dialog, modal, with its focus trap, Escape and focus return, and the inline copy stays closed until a reload. Both show the same Palette and share the block's state, so a selection in the copy is in the dialog. In your app keep the dialog and drop the copy: the "in-place" state, the scrim with its shell and the Escape and blur effects.

How does the multi-select work on each base?

The ticks are drawn by the block from its own list of chosen ids, not by the library's selection, so all four bases behave the same. Each row is an option whose hidden text says "selected" or "not selected", the check box is aria-hidden, and choosing a row only changes the list in state; the library is told to keep nothing chosen, so picking the same row twice always fires. The status line says what changed and how many labels are selected.

What do Apply and Escape do?

Apply copies the selection onto the drawn issue, closes the menu and says what was applied, or that every label was removed when nothing is selected. Escape, a click away, or closing the real dialog drops the selection back to the labels the issue already had, so a half-made choice never leaks into the page. The Apply and Clear buttons sit below the list, outside the command, so Tab reaches them after the field and Enter on the field never presses one by mistake.

Where do Select all and Clear come from?

They are two rows in a Selection group that exists only while the field is empty, because a typed search is about finding one label, not all of them. Ctrl or ⌘ A on the empty field selects every label without taking over text selection once you have typed something. Clear selection empties the set and says that Apply would now remove every label.

What stays local, and how do I wire it?

All of it. The ten labels, their issue counts and the issue's starting labels are typed constants, and Apply changes only the chips on the drawn page. In your app fill LABELS from your tracker, send the chosen ids to your API inside apply, and keep the selection in state until the request is accepted so a failure can leave the menu open.

Is it accessible from the keyboard?

Yes. Type to search, the arrow keys move through the rows, Enter ticks or unticks, Ctrl or ⌘ A selects all from the empty field, and Tab reaches Clear and Apply labels. Escape closes the menu with focus on the field, and Ctrl or ⌘ K opens the dialog while focus is inside the block, and only then. The rows are options of a cmdk listbox; the block turns off its own fuzzy filter and lists the rows itself.