| title | Select Field |
|---|---|
| description | Dropdown selection field with single or multiple options |
The Select Field component provides a dropdown for selecting one or more options from a predefined list.
An option can carry a visibleWhen CEL predicate — it is offered only when the
predicate is TRUE. The predicate is evaluated against the live record plus
current_user, the same engine and binding environment as a field-level
visibleWhen. This single mechanism covers two needs:
- Cascading / dependent options — narrow a child list by a parent field (country → province → city).
- Role / context gating — offer an option only to certain users.
Declare the sibling field(s) a select reacts to with dependsOn. While any is
empty the control is gated (a "Select country first" hint) instead of
showing an unfiltered list; once the parent changes, the list re-evaluates and
any now-invalid selection is cleared automatically (no stale "China +
California" pair).
{
"type": "form",
"fields": [
{ "name": "country", "label": "Country", "type": "select", "options": [
{ "label": "China", "value": "cn" },
{ "label": "United States", "value": "us" }
]},
{ "name": "province", "label": "Province", "type": "select", "dependsOn": "country", "options": [
{ "label": "Zhejiang", "value": "zj", "visibleWhen": "record.country == 'cn'" },
{ "label": "Guangdong", "value": "gd", "visibleWhen": "record.country == 'cn'" },
{ "label": "California", "value": "ca", "visibleWhen": "record.country == 'us'" },
{ "label": "Texas", "value": "tx", "visibleWhen": "record.country == 'us'" }
]}
]
}Chain a third level (city, dependsOn: "province") the same way — the gate and
cascade-clear propagate down the chain.
{ "name": "visibility", "type": "select", "options": [
{ "label": "Private (only me)", "value": "private" },
{ "label": "My team", "value": "team" },
{ "label": "Whole organization", "value": "org" },
{ "label": "Public — external", "value": "public", "visibleWhen": "'admin' in current_user.positions" }
]}Security — hiding is UX, not authorization. A
visibleWhenon an option only removes it from the dropdown on the client; a determined caller can still submit the value. When an option is gated for access-control reasons the server must also reject writes of that value (the rule-validator evaluates the picked value'svisibleWhen). Use optionvisibleWhenfor convenience and cascades freely; for real authorization, pair it with server-side enforcement.
visibleWhen options are for small, static dictionaries (category →
subcategory, a handful of provinces). When the data is large, changes over time,
or is shared across forms (real country/province/city tables, org units, product
catalogs), model each level as a lookup with depends_on instead — the
candidate query is filtered server-side and paginated. See
Lookup Field.
A select field is authored as SelectFieldMetadata (@object-ui/types), which is the
source of truth for the key set: it extends BaseFieldMetadata with the option list
and the multiple/searchable switches. Each option is a SelectOptionMetadata, so the
options are checked by the same compiler that checks the field.
import type { SelectFieldMetadata } from '@object-ui/types';
const status: SelectFieldMetadata = {
type: 'select',
name: 'status',
label: 'Status',
placeholder: 'Select a status',
required: true,
multiple: false,
searchable: true,
options: [
{ label: 'Draft', value: 'draft', color: 'gray' },
{ label: 'Active', value: 'active', color: 'blue' },
// Offered only when the predicate is true, evaluated against the live record.
{ label: 'Archived', value: 'archived', color: 'red', visibleWhen: "current_user.is_admin" },
{ label: 'Locked', value: 'locked', disabled: true },
],
};Cascading option lists are driven by a sibling field's value. The widget reads a
camelCase dependsOn off the metadata, but no exported metadata type declares it —
BaseFieldMetadata declares the snake_case depends_on instead — so the two
spellings disagree and the gap is tracked as
objectui#6153.
The value being edited, and the className / disabled a host supplies, are not
metadata keys — they are runtime widget props. See Field Widget Props.
gray- Default neutral colorred- For errors, urgent itemsorange- For warnings, high priorityyellow- For pending, attention neededgreen- For success, completedblue- For info, in progressindigo- For special itemspurple- For creative, designpink- For featured items
In tables/grids, select values are displayed as colored badges:
import { SelectCellRenderer } from '@object-ui/fields';
// Single value: Colored badge
// Multiple values: Multiple badges in a row- Status Fields: Order status, task status
- Categories: Product categories, content types
- Tags: Multi-tag selection
- Priorities: Task or ticket priorities
- Roles: User roles or permissions