Composer menu
A labelled choice, such as Output or Format, that sits under the Home composer and in toolbars and opens a list of options.
import ComposerMenu from "@/pages/harness/ComposerMenu";ComposerMenu is a button that says what it sets and its current value, "Output Auto" or "Format LinkedIn · Square", and opens a list to change it. The button is the product Button and the list is the dropdown menu, so it looks and behaves like every other menu. It started under the Home composer and is now also the filter menu in the Library and the creative editor's toolbar.
Usage
Pass the label, the current value, the options and onChange. Each option has an id and a label, and can have a description and an icon. The icon of the chosen option also shows on the button.
const OUTPUTS = [
{ id: "auto", label: "Auto", description: "Chat, dashboards and answers, from your request", icon: Sparkle },
{ id: "landing", label: "Landing page", description: "Built from your published components", icon: Layout },
{ id: "creative", label: "Ad creative", description: "For LinkedIn and Meta, in your design system", icon: ImageSquare },
];
const [output, setOutput] = useState("auto");
<ComposerMenu label="Output" value={output} options={OUTPUTS} onChange={setOutput} />In the composer tray
Under the white text box, the grey tray holds the options that shape what gets built. They use variant="ghost", so they read as quieter than Output in the box above. Wrap them in composer-tools composer-tools--wrap (from composerMenu.css): a flex row with a 4px gap that wraps when the options do not fit.
<div className="composer-tools composer-tools--wrap px-2.5 py-1.5">
<ComposerMenu label="Type" value={kindId} options={KIND_OPTIONS} onChange={setKindId} variant="ghost" />
<ComposerMenu label="Style" value={styleId} options={STYLE_OPTIONS} onChange={setStyleId} variant="ghost" />
<ComposerMenu label="Format" value={formatId} options={FORMAT_OPTIONS} onChange={setFormatId} variant="ghost" />
</div>The tray changes with Output: Ad creative shows Type, Style and Format, Landing page shows Template, and Auto shows none. For the whole composer, see Chat composer.
As a filter
The Library uses it for its Folder, Status and Type filters, with the default secondaryGhost variant and the count in each label instead of a description.
<ComposerMenu
label="Status"
value={filter}
options={STATUS_FILTERS.map((f) => ({ id: f.id, label: `${f.label} ${count(f.id)}` }))}
onChange={setFilter}
/>Alignment
The list opens below the button, 6px away, lined up with its left edge. Use align="end" when the button sits at the right of its row, so the list lines up with the right edge and stays on screen.
<ComposerMenu label="Output" value={output} options={OUTPUTS} onChange={setOutput} align="end" />Anatomy
| Part | Style |
|---|---|
| Button | Button at size="md": 32px tall, 12px text. secondaryGhost has a border; ghost does not. |
| Button contents | The chosen option's icon (16px), the label, the value in weight 500 (.composer-menu__value), then a 14px CaretDown. |
| List | The dropdown menu panel: white, --color-grey-200 border, 8px radius, 6px padding, at least as wide as the button and at most 360px. |
| Option | 14px label, an optional 12px description in --color-grey-500, the option's 16px icon on the left. |
| Badge | Optional, after the label: 12px medium text in --color-grey-500 on --color-grey-50, with a --color-grey-100 border and a 6px radius. |
| Selected | A 16px bold Check in --color-primary-500, 12px from the text. |
Props
| Prop | Type | Default | Notes |
|---|---|---|---|
label | string | What the menu sets. Shown on the button and used as the list's aria-label. | |
value | string | The id of the chosen option. | |
options | Option[] | See below. Must have at least one option. | |
onChange | (id) => void | Called with the option's id on every pick, including the one already chosen. | |
variant | Button variant | "secondaryGhost" | The product uses secondaryGhost and ghost. |
align | "start" | "center" | "end" | "start" | Which edge of the button the list lines up with. |
| Option field | Type | Notes |
|---|---|---|
id | string | Compared with value. |
label | string | Shown in the list and, when chosen, on the button. |
description | string | A second line. When present, the icon aligns to the top of the row. |
icon | Phosphor icon component | Shown in the row and, when chosen, on the button. |
badge | string | A small grey label after the name. No caller uses it today. |
disabled | boolean | The row drops to 50% opacity and cannot be picked. No caller uses it today. |
There is no disabled prop for the whole menu and no className.
If value matches no option, the button shows the first option's label but the list shows no tick. Keep value in step with options, especially when the options are built from data such as templates or folders.
Accessibility
- The menu is Base UI's. The button gets
aria-haspopupandaria-expanded; arrow keys move through the options, typing jumps to a match, Enter picks one, and Escape closes the list and returns focus to the button. - The button's accessible name is its text, such as "Output Auto", so people hear the current value.
- The chosen option is marked with
aria-current, not as a checked radio item, so screen readers may not say which option is chosen. If a menu needs that announced, build it withDropdownMenuRadioGroupandDropdownMenuRadioIteminstead.
When to use it
| Use | When |
|---|---|
| ComposerMenu | A short choice in a toolbar or under a composer, where the button should show both what it sets and the value. |
| Dropdown | A field in a form, with a label above it. |
| List dropdown | A borderless value in running text or a header. |
| Model mode menu | The Sage model choice. Do not rebuild it with ComposerMenu. |
| Filter dropdown | The Recommendations page's workflow filter, with counts on the right of each row. |
Do and don't
- Do keep
labelto one word that names the setting: Output, Format, Template, Status. - Do put the count in the option label for filters, as the Library does.
- Don't use it for actions. Every option sets a value; for actions, use the dropdown menu directly.
- Don't use it to navigate. The "Design system" button in the Home tray looks like a ComposerMenu but is a plain ghost Button with
composer-menu__value, because it opens the Library.
Where it is used
| File | Menus |
|---|---|
src/pages/harness/HomePage.jsx | Output in the composer. Type, Style, Format and Template in the tray, with variant="ghost". |
src/pages/library/CreativeEditorPage.jsx | Format, Type and Style in the creative editor's toolbar. |
src/pages/library/LibraryPage.jsx | The Folder, Status and Type filters in the Library toolbars. |
The component is src/pages/harness/ComposerMenu.jsx, with its styles in src/pages/harness/composerMenu.css.