Skip to content

Dropdown table โ€‹

A dropdown that opens a native <table> for picking row(s). controlMonoDataDropdown wraps controlMonoTable (exposed as dd.grid), so the panel's table controls bind with :control-table.prop="dd.grid". Bind the field with :control-data-dropdown.prop="dd".

Control โ€‹

controlMonoDataDropdown owns selection/value (it wraps a controlMonoTable, exposed as dd.grid); bind the field with :control-data-dropdown and read dd.modelValue from outside.

Because the options are forwarded to the wrapped grid, every controlMonoTable option works here too โ€” including searchValue, which names the searched columns and takes an array or a comma string. Omit it and the panel searches '*', every top-level field, like any other grid. The panel searches through a slotted <mono-table-search>, so its element-level search-value applies here as well.

controlMonoDataDropdown(data, { props }) declares every element's props in one place โ€” the same shape controlMonoTable and controlMonoForm use โ€” so the field is wired with only :control-data-dropdown.prop="dd". props.dropdownTable targets the field itself (label, placeholder, clearable, panel sizeโ€ฆ); every other key targets the panel and is forwarded verbatim to the wrapped grid. The panel's mono-table-* still bind :control-table.prop="dd.grid" โ€” the panel really is a grid.

The field's events are accepted there as on<Event> keys โ€” props: { dropdownTable: { onChange, onOpen, onClose } } โ€” and attached to the field as listeners with the same event a template @change would get. The panel's slots take theirs the same way (table โ€บ events from the controller). This is separate from dd.onValueChange, the controller's own value sink.

Basic โ€‹

Single-select over a local array, with everything declared centrally.

Clicking the field opens the panel and a second click closes it (there is no input in the field โ€” the search box lives in the panel โ€” so a click is never "I want to type"); the caret (โŒ„) does the same, and Enter / Space / โ†“ open from the keyboard. Opening the panel puts the caret straight into the <mono-table-search slot="search">; closing it hands focus back to the field. Set :auto-focus-search="false" to keep focus on the field โ€” useful on touch, where focusing an input raises the virtual keyboard over the rows.

Keyboard โ€‹

The panel has two zones โ€” search box and row list. Tab moves between them; โ†‘/โ†“ move the highlight (scrolling it in), Enter selects it, Esc closes. The highlighted row is Basecoat's active option โ€” the --muted row, the same wash as hover (--mono-dropdown-table-row-active-bg retunes it); a picked row is the table's own selected row, wash plus rail. Searching or paging moves the highlight onto the new first row.

Popups inside the panel โ€‹

Anything opened from inside the panel counts as inside it: a <mono-table-th>'s right-click menu and header-filter popup, a <mono-select> in a slot, the search's filter builder. Clicking into them โ€” the filter's search box, a value row, an option โ€” never closes the picker, and an Esc pressed in one of them closes only that popup; the next Esc, from the panel itself, closes the picker. (Every mono popup portals its panel to <body>, so by DOM position alone those clicks would be "outside"; the popups know which element opened them, and the picker asks that instead.) A popup that belongs to something else on the page is still outside, and still dismisses.

Sizes โ€‹

xsโ€“xxl, the same scale as <mono-select> and <mono-tag-input>.

Colors โ€‹

Pick a colour and every variant takes it. Every built-in color tints the focus ring and the open border โ€” and, unless chip.color pins another hue, the chips.

outlined (default), filled and underlined. underlined is sera's chip box: the bottom edge only, which takes the focus colour, and no ring.

States โ€‹

disabled, readonly (neither opens โ€” see Clear and caret) and required.

Validation โ€‹

validation-state (valid / invalid / warning) with a validation-message.

Clearable โ€‹

clearable shows a โœ• that resets the selection (emitting change with an empty value).

Appearance โ€‹

Same size / variant / color / validation-state / label props as <mono-select>, so the two look identical.

Field & panel size โ€‹

The field width is a css-size prop; the panel is sized separately by :dropdown.prop="{ width, maxHeight }" โ€” so a narrow field can open a wide grid.

The object takes all six sizing keys (width, height, minWidth, maxWidth, minHeight, maxHeight) and is the same one <mono-select> and <mono-tag-input> accept.

height changed meaning

It used to be the panel's max-height, because this object had no maxHeight. It now has one, so height is an exact height and maxHeight caps. If you were passing { height } to bound a scrolling panel, pass { maxHeight }.

Placement โ€‹

The panel stays inside the viewport on its own, so a field low on the page โ€” or in a grid row below the fold โ€” is never clipped:

  • flip (default true) opens the panel upward when there isn't room below.
  • shift (default true) slides it along the cross axis so it doesn't overflow a screen edge.
  • When neither side has room for the full panel, it opens on the roomier side and shrinks to fit, scrolling the table internally rather than running off-screen.

placement sets the preferred side + alignment (default bottom-start); with flip on it's a preference, not a guarantee. offset (default 6) is the gap between the field and the panel.

The resolved side is reflected on the root as mono-side="top" / "bottom" (and the classes is-top / is-bottom), so you can style a flipped panel.

Multiple โ€‹

Multi-select with chips + a "+N more" overflow (max-visible, default 5) โ€” its panel is a floating layer like the dropdown's own, portaled to <body> so no overflow: hidden ancestor clips it, flipping at the viewport edge โ€” a checkbox column and a select-all header checkbox. type="all" covers every row, not just the page on screen. The demo also caps the selection at max="4" โ€” tick a fifth row and the box un-ticks itself.

Limits: min and max โ€‹

max caps how many rows the user can select, min how few they can leave. Both are optional; unset means unlimited. Three places can set them, in this precedence:

vue
<mono-dropdown-table :max="4" :min="1" />                    <!-- element attributes -->
<mono-dropdown-table :chip.prop="{ max: 4, min: 1 }" />      <!-- chip object โ€” pins over the attributes -->
ts
const dd = controlMonoDataDropdown(rows, { multiple: true, max: 4, min: 1 }) // controller โ€” the fallback

The limits are enforced by the wrapped grid's check store, which is the one place every selection goes through โ€” a row click, the keyboard, a <mono-table-checkbox> bound to dd.table (row and type="all"), and dd.selectAll(). So nothing is disabled and nothing needs wiring:

  • Past max a pick is rejected โ€” the row stays clickable, the pick does not land, and a native checkbox un-ticks itself. A bulk select (page or server drain) fills only the room left.
  • At min the chips lose their โœ•, un-ticking is rejected, the clear button hides and clear() (or un-ticking the header box) keeps the first min rows.
  • A model-value pushed in (dd.setValue()) is never trimmed โ€” the limits are the user's.

Breaking: max โ†’ max-visible, maxSelect โ†’ max

The old element max โ€” how many chips to draw before "+N more" โ€” is now max-visible, so an existing :max="3" silently became a selection cap; rename it. The controller option maxSelect is gone: pass max (and it now holds for the checkbox column too, which used to bypass it).

Chips โ€‹

The selection chips are Basecoat combobox chips โ€” the same chips mono-tag-input draws, painted by this component's own sheet โ€” configured as one object, the same chip object tag-input takes:

vue
<mono-dropdown-table :chip.prop="{ behaviour: 'inline', shape: 'rounded' }" />

chip takes behaviour, color, rounded, dot, closeLabel and cssClass (size and variant are still accepted and ignored: a combobox chip has one size per field size and one skin). Leave color out and the chips follow the field's color; set it and they are pinned to that hue.

It also carries the four limits โ€” max, min (selection) and maxVisible, minVisible (display: chips drawn before "+N more", and the collapse floor under which every chip draws; kebab 'max-visible' / 'min-visible' work too). A key set here pins over the matching element attribute.

Static HTML can pass the same object as a JSON attribute: chip='{"shape":"rounded"}'.

Changed

The chips are no longer <mono-chip> markup: chip.size, chip.variant and the --mono-chip-* bridge are no-ops, and a chip is the muted combobox chip unless the field (or chip.color) colours it.

Layout: behaviour โ€‹

By default the chips wrap, so the field grows taller as rows are selected. behaviour: 'inline' keeps them on one line in a strip that scrolls sideways, so the field holds its height however many rows are picked.

The strip is moved only by the โ€น / โ€บ buttons that appear next to the clear button. There is no scrollbar, and the wheel, click-dragging and the arrow keys all deliberately leave it alone. Each button appears only when there is somewhere to scroll toward.

max-visible applies here too: the first N chips draw in the strip, the rest collapse into a "+N more" chip at its end, and clicking it opens the same panel listing them. Set max-visible="0" to draw every chip and leave the โ€น / โ€บ buttons as the only overflow mechanism.

Clear and caret โ€‹

One glyph. With something to clear, the clear button (โœ•) stands in for the caret, open or closed; otherwise โ€” nothing selected, or clearable off โ€” the caret (โŒ„) is alone (the same rule as <mono-select> and <mono-tag-input>, so the three line up in a form; here the field body toggles either way, so nothing is lost while the caret is away). In inline mode the scroll buttons sit before it, so the row reads โ€น โ€บ โœ• or โ€น โ€บ โŒ„ โ€” one glyph's width either way, so โœ• sits exactly where the caret was.

A disabled or readonly field shows neither โœ• nor โŒ„: it does not open, so a caret would promise what it never delivers. The gutter stays, so the value does not shift when the state toggles โ€” and in inline mode the โ€น โ€บ pair stays too and keeps paging, so a long selection can still be read.

Custom keys โ€‹

keyExpr picks the value field; displayExpr (field name or (row) => string) picks the text.

Customized โ€‹

cssClass appends a class to each field part (root / label / value / message / trigger / โ€ฆ).

CSS variables โ€‹

Themed through --mono-dropdown-table-* custom properties (they inherit and pierce the shadow boundary). The field is the tag-input's chip box and the panel is its popover, so every knob falls back to the matching --mono-tag-input-* one โ€” a flavour that retunes the tag-input retunes this field, and the two line up in a form with nothing set. The panel's rows are the table's, themed by --mono-table-*. Scroll areas use the shared --mono-scrollbar-* tokens.

VariableDefaultControls
--mono-dropdown-table-primary โ€ฆ -info--mono-tag-input-* โ†’ --ring, --muted-foreground, --success, --destructive, --warning, --infoThe ten color hues โ€” the focus ring, the open border, the chips
--mono-dropdown-table-text / -placeholder / -muted--foreground / --muted-foreground / --muted-foregroundInks
--mono-dropdown-table-border (or -border-color / -rest-border)--inputResting border
--mono-dropdown-table-bg (or -surface)--mono-mode-surfaceField surface
--mono-dropdown-table-shadow--mono-shadow-xs (outlined)Resting shadow
--mono-dropdown-table-ring-color / -ring-width / -ring-alphathe color hue / --mono-ring-width / --mono-ring-alphaThe focus ring
--mono-dropdown-table-height-<size>--mono-control-height-<size>The field's minimum height per size
--mono-dropdown-table-radius / -radius-<size>--mono-radius-mdField corner
--mono-dropdown-table-padding-x / -padding-y (+ -<size>)1.5 ร— --mono-spacingField inset
--mono-dropdown-table-font-<size> / -line-height--mono-text-sm / --mono-text-sm--lhThe value's type
--mono-dropdown-table-text-inset1.5 ร— --mono-spacingExtra start inset for a text value, so it lines up with a <mono-select>
--mono-dropdown-table-chip-gap / -chip-height-<size> / -chip-padding-x / -chip-radius / -chip-bg / -chip-color / -chip-font-size / -chip-font-weight1.5 ร— --mono-spacing / 5.5 ร— --mono-spacing / 1.5 ร— --mono-spacing / --mono-radius-sm / --muted / --foreground / --mono-text-xs / mediumThe chips
--mono-dropdown-table-icon-size / -clear-radius4 ร— --mono-spacing / --radius โˆ’ 5pxThe caret, clear and โ€น โ€บ glyphs
--mono-dropdown-table-outline-* / -filled-bg / -readonly-bg / -disabled-bgtag-input'sPer-variant and per-state presets
--mono-dropdown-table-dropdown-bg / -color / -radius / -ring / -shadow / -min-width--popover / --popover-foreground / --mono-radius-md / 1px --foreground 10% / --mono-shadow-md / 18remThe panel and the "+N more" panel
--mono-dropdown-table-region-padding2 ร— --mono-spacingThe search and footer regions' inset
--mono-dropdown-table-row-hover-bg / -row-active-bg--muted 50% / --mutedA hovered row, the keyboard cursor's row (a picked row is the table's --mono-table-selected-bg)
--mono-dropdown-table-label-* / -message-*tag-input'sLabel and message type
--mono-dropdown-table-gap3 ร— --mono-spacingLabel โ†” field โ†” message

Deprecated and honoured as no-ops until 2.0: --mono-dropdown-table-underline-glow (the underline is sera's line now), --mono-dropdown-table-cursor-width / -color (the keyboard cursor is Basecoat's --muted row), --mono-dropdown-table-background, the --mono-chip-* bridge (the chips are no longer chip.css's).

Event: change โ€‹

change carries modelValue, value and the resolved selectedItems ({ key, text, data }[]).

Event log โ€‹

A live log of change as rows are picked and cleared โ€” clearing emits change with an empty value (there is no separate clear).

Remote DataSource โ€‹

Bound to a remote OData source; selected keys resolve to display text with a filter โ€ฆ in load, and search runs server-side.

Scoping the picker โ€‹

controlMonoDataDropdown wraps a controlMonoTable, so a scope survives the panel's own search, header filters, sort and scroll paging the same two ways: set it on the source (ds.filter([...]) then dd.table.load() โ€” the grid adopts it), or derive it from reactive state with dataSourceOptions / odataOptions, read fresh at every query:

ts
const dd = controlMonoDataDropdown(dataSource, {
  keyExpr: 'Id',
  odataOptions: () => ({
    $filter: `CompanyId eq ${company.value} and DeptKode eq '${dept.value}'`,
    $select: 'Id,CoaNama,PostBudgetNama',
  }),
})
watch([company, dept], () => dd.table.refresh())

Typing in the slotted <mono-table-search> now ANDs onto that scope instead of replacing it, and clearing the box returns to the scoped set. See DataSource options on the table page for the full surface.

Select all (server-side) โ€‹

Multi-select is the wrapped grid's check() store, so <mono-table-checkbox> binds dd.table like every other mono-table-* helper here โ€” no selection glue. type="all" drains the source and selects every row the current search matches, including ones never rendered; dd.selectAll() is the same thing from code.

TIP

max-visible caps the chips drawn โ€” expanding "+N more" renders one per remaining item, so a selection of hundreds will feel slow to expand. (max-visible="0" renders every chip, so it is the wrong choice for a selection that large.) A hard max caps the drain itself: "select all" then fills the room left and stops.

Error bar โ€‹

Same story as the empty state โ€” a failed request in a dropdown panel is otherwise completely silent. It goes in a row of its own (<tbody> only takes rows); the element adopts that row, so the two tags are all you write:

vue
<tbody>
  <tr><td colspan="2"><mono-table-error :control-table.prop="dd.table" /></td></tr>
  <tr v-for="โ€ฆ">โ€ฆ</tr>
</tbody>

See Error bar on the table page for the full surface.

Empty state โ€‹

dd.table is a controlMonoTable โ€” the same object as dd.grid โ€” so every mono-table-* element works inside the panel exactly as it does in a plain table. <mono-table-empty> is no exception: put it in the <table>'s <caption> and bind it like the rest.

The panel is where this matters most: a dropdown you have just typed into is the place a user most often lands on nothing, and until now the grid simply went blank. See Empty state on the table page for the full surface โ€” the icon / title / subtitle props, the slot="body" override, and the reload button.

Scroll paging โ€‹

Swap the numbered pager for a scrolling one on a flat grid โ€” bind <mono-table-paging type="โ€ฆ"> to dd.grid, place it in the body slot right after the <table>, and give the panel a bounded :dropdown.prop="{ maxHeight }".

Infinity scroll โ€‹

type="infinity-scroll" appends the next page onto dd.grid.items as you near the bottom โ€” render it as a plain v-for.

Against a remote source each scroll fetches the next server page.

Virtual scroll โ€‹

type="virtual-scroll" keeps only the visible rows in the DOM; render spacer <tr>s from virtualPadTop / virtualPadBottom and pin a fixed :row-height.

The same windowing over a remote source.

Types โ€‹

controlMonoDataDropdown is also exported as monoDataDropdown, and :control-data-dropdown is also accepted as :data-dropdown โ€” the older spellings still work.

Importimport { DropdownTableProps } from 'mono-helper'
PropValueDefaultDescription
dataDropdownMonoDropdownController<any>โ€”The dropdown controller. Bind with `.prop`: `:data-dropdown.prop="dd"`.
controlDataDropdownMonoDropdownController<any>โ€”Renamed โ€” `:control-data-dropdown` / `:controlDataDropdown` alias `dataDropdown`.
size'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'โ€”Visual size of the field.
color'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'teal' | 'purple' | 'neutral' | 'dark'โ€”Color theme applied to the field (focus ring / accent).
variant'underlined' | 'outlined' | 'filled'โ€”Field style variant.
labelstringโ€”Text label shown above the field.
placeholderstringโ€”Placeholder shown in the field when nothing is selected.
helperTextstringโ€”Helper text shown below the field.
validationState'warning' | 'default' | 'valid' | 'invalid'โ€”Validation state controlling the field's appearance.
validationMessagestringโ€”Validation message shown below the field.
errorMessagestringโ€”Error message shown below the field (forces the invalid state).
successMessagestringโ€”Success message shown below the field (forces the valid state).
requiredbooleanโ€”Mark the field required (shows a `*` next to the label).
disabledbooleanโ€”Disable the control (no open, no selection).
readonlybooleanโ€”Read-only โ€” the panel opens but selection can't change.
clearablebooleanโ€”Show a clear (โœ•) button in the field when there is a value.
multiplebooleanโ€”Multi-select โ€” array value + chips. Falls back to the controller's `multiple`.
maxnumberโ€”Most rows the user can select (multi). Unset = unlimited. A pick past it is rejected โ€” the row stays enabled, the pick just does not land and a `<mono-table-checkbox>` un-ticks itself. Overrides the controller's `max`; `chip.max` overrides this. A `model-value` pushed in is never trimmed.
minnumberโ€”Fewest rows the user can leave selected (multi). Unset = 0. At the floor the chips lose their โœ•, un-ticking is rejected and the clear button hides. Overrides the controller's `min`; `chip.min` overrides this.
maxVisiblenumberโ€”Chips drawn before the rest collapse into a "+N more" chip (multi). Default `5`; `0` = draw all. Applies in `flex` AND `inline` โ€” in the strip the "+N more" chip sits after the visible chips. `chip.maxVisible` overrides.
minVisiblenumberโ€”Collapse floor: while the selection is at or under this, every chip is drawn regardless of `max-visible`. Unset = collapse as soon as `max-visible` is exceeded. `chip.minVisible` overrides.
chipstring | DropdownTableChipPropsโ€”Chip configuration โ€” the `mono-chip` props to apply to every selection chip, plus `behaviour` for the one-line scrolling layout. The same object `<mono-tag-input>` takes. Bind it as a real property (`:chip.prop="{ behaviour: 'inline' }"`); a plain `chip='{"size":"md"}'` JSON attribute also works for static HTML.
modelValueunknownโ€”v-model value โ€” scalar (single) or array (multi). Bind `:model-value` + `@change`.
widthstring | numberโ€”Field sizing (the trigger + wrapper). A CSS length string (`"320px"`, `"80%"`) or a number (px). Distinct from `dropdown`, which sizes the popup panel.
heightstring | numberโ€”โ€”
minWidthstring | numberโ€”โ€”
maxWidthstring | numberโ€”โ€”
minHeightstring | numberโ€”โ€”
maxHeightstring | numberโ€”โ€”
dropdownSizePropsโ€”Sizes the popup panel only (independent of the field). Bind with `.prop`.
placement'left' | 'right' | 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end' | 'left-start' | 'left-end' | 'right-start' | 'right-end'โ€”Preferred side + cross-axis alignment of the panel, e.g. `"bottom-start"` (default) or `"top-end"`. Same 12 values as `<mono-dropdown>`. With `flip` left on this is only a *preference* โ€” the panel still moves out of the way of a viewport edge.
flipbooleanโ€”Open on the opposite side when the preferred one lacks room โ€” e.g. a field near the bottom of the viewport drops UPWARD instead of being clipped. Default `true`.
shiftbooleanโ€”Slide the panel along the cross axis so it stays inside the viewport โ€” e.g. a field near the right edge keeps its full width instead of overflowing. Default `true`.
offsetnumberโ€”Gap between the field and the panel, in px. Default `6`.
autoFocusSearchbooleanโ€”Focus the panel's search box (a `<mono-table-search slot="search">`) as soon as the panel opens, so the user can type straight away. Default `true`. Turn it off when the focus would be unwelcome โ€” e.g. on touch devices, where focusing an input pops the virtual keyboard over the rows.
stayOpenbooleanโ€”Exempt this dropdown from every automatic close โ€” clicking or focusing anything outside it, which includes opening another one. Not a lock: its own trigger, Escape and picking a row still close it.
cssClassDropdownTableCssClassโ€”Per-part class overrides. Object, or a JSON string via the `css-class` attribute.
cssClassNamestringโ€”A single class added to the root (the string form of `css-class`).
visiblebooleanโ€”Whether the control is shown. Defaults to `true`.
visibleType'none' | 'invisible'โ€”How it hides when `visible` is `false`. Defaults to `'none'`.