Design system

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

PartStyle
ButtonButton at size="md": 32px tall, 12px text. secondaryGhost has a border; ghost does not.
Button contentsThe chosen option's icon (16px), the label, the value in weight 500 (.composer-menu__value), then a 14px CaretDown.
ListThe dropdown menu panel: white, --color-grey-200 border, 8px radius, 6px padding, at least as wide as the button and at most 360px.
Option14px label, an optional 12px description in --color-grey-500, the option's 16px icon on the left.
BadgeOptional, after the label: 12px medium text in --color-grey-500 on --color-grey-50, with a --color-grey-100 border and a 6px radius.
SelectedA 16px bold Check in --color-primary-500, 12px from the text.

Props

PropTypeDefaultNotes
labelstringWhat the menu sets. Shown on the button and used as the list's aria-label.
valuestringThe id of the chosen option.
optionsOption[]See below. Must have at least one option.
onChange(id) => voidCalled with the option's id on every pick, including the one already chosen.
variantButton variant"secondaryGhost"The product uses secondaryGhost and ghost.
align"start" | "center" | "end""start"Which edge of the button the list lines up with.
Option fieldTypeNotes
idstringCompared with value.
labelstringShown in the list and, when chosen, on the button.
descriptionstringA second line. When present, the icon aligns to the top of the row.
iconPhosphor icon componentShown in the row and, when chosen, on the button.
badgestringA small grey label after the name. No caller uses it today.
disabledbooleanThe 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-haspopup and aria-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 with DropdownMenuRadioGroup and DropdownMenuRadioItem instead.

When to use it

UseWhen
ComposerMenuA short choice in a toolbar or under a composer, where the button should show both what it sets and the value.
DropdownA field in a form, with a label above it.
List dropdownA borderless value in running text or a header.
Model mode menuThe Sage model choice. Do not rebuild it with ComposerMenu.
Filter dropdownThe Recommendations page's workflow filter, with counts on the right of each row.

Do and don't

  • Do keep label to 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

FileMenus
src/pages/harness/HomePage.jsxOutput in the composer. Type, Style, Format and Template in the tray, with variant="ghost".
src/pages/library/CreativeEditorPage.jsxFormat, Type and Style in the creative editor's toolbar.
src/pages/library/LibraryPage.jsxThe 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.

On this page