Everything the action bar can do, each as ONE live picker you configure with control switches — built-in & static props, the dynamic callbacks (and their priority over statics), custom actions, icon buttons, layout, and a device-adaptive toolbar.
🌐 client — runs in the browser (element JS API) 🖥️ server — LiveView round-trip
.actionButtons
property — the callbacks (onClick, getTextCallback, …) can't be
serialized as HTML attributes. Each demo below assigns its options and
config to the wrapper element by id after the custom element upgrades.
JS el.actionButtons · static per-button props: tooltip · cssClass · isVisible · isDisabled
The two built-in actions — select-all and clear-all — plus the static
per-button properties: tooltip, cssClass, isVisible, and
isDisabled. Toggle the switches to rebuild the actionButtons array live.
isDisabled (and no getIsDisabledCallback),
Select All auto-disables once every option is selected and
Clear All auto-disables while nothing is selected. Select all colors and watch
Select All disable.
[]
multiselect.actionButtons = [
{ action: 'select-all', text: 'Select All',
tooltip: 'Select all available colors', // static tooltip
cssClass: 'custom-select-btn' }, // needs customStylesCallback (Shadow DOM)
{ action: 'clear-all', text: 'Clear All' },
{ action: 'custom', text: 'Hidden Button', isVisible: false, onClick: (ms) => {} },
{ action: 'custom', text: 'Disabled Button', isDisabled: true, onClick: (ms) => {} },
];
Per-button callbacks on el.actionButtons: getIsVisibleCallback · getIsDisabledCallback · getTextCallback · getClassCallback · getTooltipCallback
Each button property has a dynamic twin that recomputes from the live component:
getIsVisibleCallback, getIsDisabledCallback,
getTextCallback, getClassCallback, and
getTooltipCallback. Pick which one drives the buttons — or choose
all (priority) to see every callback active at once, overriding the static
text / isVisible / isDisabled that are also set.
[]
get*Callback > static > default. Try all (priority) — the
buttons carry isVisible:false / isDisabled:true yet the callbacks keep
them visible and enabled.
Per-button config on el.actionButtons: action: 'custom' + onClick: (ms) => …
Build your own buttons with action: 'custom' and an onClick(ms) handler
that drives the component imperatively (setSelected, selectAll,
getValue, …). Here: pick popular languages, invert the selection, or pick two at
random — plus the built-in Clear All.
[]
multiselect.actionButtons = [
{ action: 'custom', text: 'Select Popular',
onClick: (ms) => ms.setSelected(['js', 'py', 'ts']) },
{ action: 'custom', text: 'Invert Selection',
onClick: (ms) => {
const all = ms.options.options.map(o => o[0]);
const sel = ms.getValue();
ms.setSelected(all.filter(v => !sel.includes(v)));
} },
{ action: 'custom', text: 'Select Random',
onClick: (ms) => {
const shuffled = [...ms.options.options].sort(() => Math.random() - 0.5);
ms.setSelected(shuffled.slice(0, 2).map(o => o[0]));
} },
{ action: 'clear-all', text: 'Clear All' },
];
actions_position="top | bottom" · per-button row · actions_align="stretch | left | center | right | space-between"
Place the actions block at the top (default) or bottom of the dropdown,
flow buttons across multiple rows with the per-button row property, and set horizontal
alignment with actions-align. Open the dropdown to see the bar move.
actions-position:
actions-align:
top renders row 1 first (top), and bottom renders row 1 last (bottom).
JS: import { observeViewport, classifyDevice } → swap el.actionButtons + el.actionsLayout per device/width
Twelve action buttons fit one row on a wide desktop, but there's no room on a phone, a tablet, or a narrowed window. The fix isn't a CSS trick: define your action sets and pick the right one for the space. The library re-exports the same device/viewport signal the component uses internally, so you react to the event that tells you the device and width and choose accordingly — here, granularly:
nowrap).wrap).[]
// Device + viewport signal, re-exported from the component package — one import,
// one dependency, the same signal the component reacts to internally.
import { observeViewport, classifyDevice } from '@keenmate/web-multiselect';
observeViewport((env) => {
const device = classifyDevice(env); // 'mobile' | 'tablet' | 'desktop'
if (device === 'mobile') { // phone: essentials, wrapped
el.actionButtons = mobileActions; el.actionsLayout = 'wrap';
} else if (device === 'tablet') { // tablet: full set, wrapped
el.actionButtons = desktopActions; el.actionsLayout = 'wrap';
} else { // desktop: two rows once ≤ 600px, else one
el.actionButtons = desktopActions;
el.actionsLayout = env.viewportWidth <= 600 ? 'wrap' : 'nowrap';
}
});
action: 'select-all' | 'clear-all' | 'custom' // Required
text: string // Required
row?: number // Optional (default: 1) — which button row
tooltip?: string // Optional
cssClass?: string // Optional
isVisible?: boolean // Optional (default: true)
isDisabled?: boolean // Optional (default: false)
onClick?: (multiselect) => void | Promise<void> // Required for 'custom' action
getIsVisibleCallback?: (multiselect) => boolean // Dynamic visibility
getIsDisabledCallback?: (multiselect) => boolean // Dynamic disabled state
getTextCallback?: (multiselect) => string // Dynamic text
getClassCallback?: (multiselect) => string | string[] // Dynamic CSS classes
getTooltipCallback?: (multiselect) => string // Dynamic tooltip
actions-position // 'top' (default) | 'bottom'
actions-layout // 'nowrap' (default) | 'wrap'
actions-align // 'stretch' (default) | 'left' | 'center' | 'right' | 'space-between'
getIsVisibleCallback > isVisible > default truegetIsDisabledCallback > isDisabled > default falsegetTextCallback > textgetClassCallback > cssClassgetTooltipCallback > tooltipactions_layout="wrap" · actions_layout="nowrap" (default)
When you have many action buttons, use actions-layout="wrap"
to allow buttons to wrap to multiple rows instead of being squeezed into a
single row. Compare the behavior with 12 buttons below.
[]
[]
// Default - buttons squeezed in single row
<web-multiselect actions-layout="nowrap"></web-multiselect>
// Wrap mode - buttons wrap to multiple rows
<web-multiselect actions-layout="wrap"></web-multiselect>
(pick an option anywhere on this page)