Skip to content

Button dropdown โ€‹

A row of actions that stays flat while it's short and collapses behind a โ‹ฎ menu once it isn't. <mono-button-dropdown :buttons.prop="actions" :min="1"> โ€” button plus dropdown in one element.

The menu is Basecoat's .dropdown-menu (see Theme): a bg-popover panel with a ring-1 ring-foreground/10 edge, rounded-md, shadow-md and p-1, holding rounded-sm rows that wash bg-accent under the pointer. Dark mode is built in.

ts
import 'mono-helper/ui/button-dropdown'

const actions = [
  { label: 'Save', icon: 'i-mdi-content-save', color: 'primary', onClick: save },
  { label: 'Delete', icon: 'i-mdi-delete', color: 'danger', variant: 'outline', onClick: remove },
]
vue
<mono-button-dropdown :buttons.prop="actions" :min="1" />

buttons and trigger are objects, so bind them with .prop โ€” a plain attribute would only carry a stringified value.

How min decides โ€‹

min is how many entries may render inline. It's all-or-nothing: while buttons.length <= min every entry is a plain button; past that they all move into the dropdown and only the trigger is left.

minbuttonsrenders
11that button, directly
13โ‹ฎ โ†’ the three in a menu
33all three inline

Entries are real buttons โ€‹

Each entry is rendered as an actual <mono-button>, so every button prop keeps working โ€” variant, color, size, disabled, badge, icon-position, and the behavioural ones like loading, throttle / debounce and handler. Nothing is re-implemented, so a prop added to the button later works here for free.

On top of ButtonProps, an entry takes label, icon and onClick.

Entries look different inside the menu โ€‹

A dropdown of solid, outlined and tonal buttons in assorted colours reads as noise rather than a list. So inside the menu every row is the same flat, borderless strip โ€” no fill, no border, no shadow, no press "twitch", just Basecoat's rounded-sm corner and a bg-accent wash under the pointer. The moment the list is short enough to render inline, each entry goes back to its real button styling: fills, outlines, radius, press effect and all.

The flattening is done with the button's own public custom properties (--mono-button-bg, -hover-bg, -width, -justify, โ€ฆ) set on the <li mono-item>, not with a selector โ€” a custom property is the only thing that also reaches the control in the shadow build, so both builds flatten identically.

The โ‹ฎ trigger is styled separately, with color / variant / size / rounded on the element itself (or the long form through trigger).

Entry colours โ€‹

An entry's color is not a button colour inside the menu. It is Basecoat's [data-variant='destructive'] menu-item treatment generalised to all ten roles: the row's ink โ€” label and icon alike โ€” becomes the role, and hovering lays that same colour down as a 10% wash (20% in dark mode, via --mono-mode-tint).

That is deliberate, not cosmetic: a solid role fill under a role-coloured glyph is exactly the combination that makes the glyph disappear โ€” a green check on a green row. A wash keeps the ink at full strength against its own background at every size, in both modes.

secondary, light and dark are surfaces rather than hues โ€” upstream they are fills paired with a -foreground, and --dark stays a dark grey in dark mode โ€” so a row takes their foreground as ink and hovers on the neutral --accent wash, like an uncoloured row.

Props โ€‹

PropPurpose
buttonsThe actions. Each entry takes every <mono-button> prop plus label / icon / onClick, and the button's other events as on<Event> keys (onLoadingChange, โ€ฆ) attached as listeners. Bind with .prop.
minHow many entries may render inline (default 1).
color / variant / size / roundedStyle the โ‹ฎ trigger. An entry's own color becomes its row's ink in the menu โ€” see Entry colours.
triggerButtonProps for the โ‹ฎ button โ€” size, variant, a different icon, or a label to make it a normal labelled button. Its events are accepted as on<Event> keys too.
placementWhere the panel opens (default bottom-end).
offsetGap in px between trigger and panel (default 4).
model-valueOpen state. Listen for open / close to mirror it.
close-on-selectClose when an entry is clicked (default true).
close-on-outside-click / close-on-escapeBoth default true.
disabledDisables the trigger and every entry.

click fires with { item, index, collapsed } in addition to the entry's own onClick, so you can handle actions per-item or centrally.

Keyboard

โ†‘ / โ†“ move between menu rows, Enter activates, Esc closes and returns focus to the trigger.

Both demos also run under Vue + Shadow DOM โ€” same behaviour, icons included.

CSS Variables โ€‹

The menu is themed through --mono-button-dropdown-* custom properties. Setting one on the element, on any ancestor, or inline all work โ€” custom properties inherit and they pierce the shadow-DOM boundary. To re-skin globally, set the underlying tokens (--popover, --accent, --destructive โ€ฆ) or switch flavor.

VariableDefaultControls
--mono-button-dropdown-bg (alias -surface) / -text--popover / --popover-foregroundThe panel's fill and ink
-ring-width / -ring-color (alias -border)--mono-border-width / --foreground/10The ring-1 edge
-shadow--mono-shadow-mdThe panel's elevation
-radius / -radius-<size>--mono-radius-md at mdPanel corner
-padding / -padding-<size>--mono-spacing (4px) at mdPanel padding (p-1)
-min-width / -min-width-<size>9rem at mdPanel width floor (upstream anchors it to the trigger; here it is a length)
-offset / -offset-<size>--mono-spacing (4px)Static gap between trigger and panel โ€” keep in step with the offset prop
-list-gap0Gap between rows (gap-0)
-gap1.5 ร— --mono-spacingGap between the inline buttons, before they collapse
-item-color / -item-hover-bg / -item-hover-colorโ€”A row's ink, hover wash and hover ink, overriding an entry's color. Set these to paint every row the same whatever role it carries
-item-base-color / -item-base-hover-bg (alias -item-hover) / -item-base-hover-color--popover-foreground / --accent / --accent-foregroundThe same three for an uncoloured row only โ€” the tier a flavor writes, leaving the ten roles intact
-item-radius / -item-radius-<size>--mono-radius-smRow corner (rounded-sm)
-item-font-weight--mono-font-weight-mediumRow label weight
-<role>the role tokenThe ten entry roles (-success, -danger, โ€ฆ) that mono-item-color selects

A row is a real button, so the button's own knobs apply inside the menu as well โ€” --mono-button-width, --mono-button-justify and --mono-button-press-translate are what this component sets to turn one into a full-width, start-aligned, twitch-free strip.

Types โ€‹

Importimport { ButtonDropdownProps } from 'mono-helper'
PropValueDefaultDescription
buttonsButtonDropdownItem[]โ€”The actions. Each entry takes every `<mono-button>` prop.
minnumberโ€”How many entries may render inline. While `buttons.length <= min` they are all shown as plain buttons; past that, ALL of them move into the dropdown and only the trigger remains. Default `1`.
color'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'teal' | 'purple' | 'neutral' | 'dark' | 'light'โ€”Colors the `โ‹ฎ` trigger. Entries are deliberately NOT colored inside the menu โ€” an entry's own `color` tints its ICON there instead, since a list of differently-coloured buttons reads as noise. Outside the menu (inline, when the list is short enough) entries keep their real button styling.
variant'outline' | 'solid' | 'tonal' | 'text'โ€”Visual style of the `โ‹ฎ` trigger โ€” same values `<mono-button>` takes.
size'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'โ€”Size of the `โ‹ฎ` trigger.
rounded'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl' | 'none' | 'full'โ€”Corner radius of the `โ‹ฎ` trigger โ€” the same scale `<mono-button>` takes.
placement'left' | 'right' | 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end' | 'right-start' | 'right-end'โ€”Where the panel opens relative to the trigger. Default `'bottom-end'`.
offsetnumberโ€”Gap in px between trigger and panel. Default `4`.
triggerButtonProps & MonoEventProps<ButtonEvents> & { label?: string | undefined; icon?: string | undefined; }โ€”Props for the `โ‹ฎ` trigger button โ€” same shape as an entry, minus the click. The button's events are accepted as `on<Event>` keys (`onLoadingChange`, โ€ฆ).
modelValuebooleanโ€”Open state. Two-way is opt-in: listen for `open` / `close`.
disabledbooleanโ€”Disables the trigger and every entry.
closeOnSelectbooleanโ€”Close when a menu entry is clicked. Default `true`.
closeOnOutsideClickbooleanโ€”โ€”
closeOnEscapebooleanโ€”โ€”
cssClassButtonDropdownCssClassโ€”โ€”
cssClassNamestringโ€”โ€”