Widget reference
← State · Documentation · Next: Actions →
This page is the complete reference for built-in widgets: envelope clicks and modifiers, params, defaults, children, events, write-back, and examples.
Common rules
Envelope fields (id, state, modifier, onClick, params, children, observers, theme, minSpec) are documented in architecture. modifier and onClick are not params.
| Envelope | Type | Default | Meaning |
|---|---|---|---|
modifier | modifier[] | [] | Applied left-to-right; see themes |
onClick | action | absent | Makes any widget clickable. Absent means not clickable |
| Param | Type | Default | Meaning |
|---|---|---|---|
visible | boolean/path | true | false removes the widget from composition and layout |
enabled | boolean/path | true | Disables interaction, including envelope onClick |
Any widget type may set envelope onClick. BDUI applies clickable; it is not a Card/Button-only param. params.enabled: false suppresses it. A child's onClick consumes the click so ancestors do not also fire. Widget-specific flags such as Button loading also suppress it.
- Params may be compatible literals or explicit paths.
- Unknown params are ignored and logged in debug builds.
- Invalid/missing values use the stated safe default.
- Only containers render
children; leaves ignore accidental children. - Unknown/too-new widget types skip their complete subtree.
- Dimensions use dp (including
width/heightmodifiers), not device pixels; text sizes use sp. - User font scale, contrast, and platform accessibility settings win.
Index
| Layout | Content | Input |
|---|---|---|
[Column](#column) | [Text](#text) | [Checkbox](#checkbox) |
[Row](#row) | [Image](#image) | [Switch](#switch) |
[Box](#box) | [Button](#button) | [Chip](#chip) |
[Card](#card) | [CircularProgress](#circularprogress) | [TextField](#textfield) |
[Scaffold](#scaffold) | [LinearProgress](#linearprogress) | [Slider](#slider) |
[TopAppBar](#topappbar) | [FloatingActionButton](#floatingactionbutton) | [RadioGroup](#radiogroup) |
[LazyColumn](#lazycolumn) | [Chart](#chart) | [Radio](#radio) |
[Calendar](#calendar) | [Review](#review) | [Select](#select) |
[Spacer](#spacer) | ||
[Divider](#divider) | ||
[Stepper](#stepper) |
Layout
Column
Vertical container; children render top-to-bottom.
| Param | Type | Default | Values |
|---|---|---|---|
verticalArrangement | string | Top | Top, Center, Bottom, SpaceAround, SpaceBetween, SpaceEvenly |
horizontalAlignment | string | Start | Start, CenterHorizontally, End |
spacing | number | absent | dp gap between children; overrides arrangement if set |
Children: any widgets. Events/write-back: none.
{
"type": "Column",
"modifier": [
{ "name": "fillMaxWidth" },
{ "name": "paddingHorizontal", "value": "16" }
],
"params": {
"verticalArrangement": "Center",
"horizontalAlignment": "CenterHorizontally"
},
"children": [
{ "type": "Text", "params": { "text": "Title" } },
{ "type": "Text", "params": { "text": "Subtitle" } }
]
}
Row
Horizontal container following layout direction.
| Param | Type | Default | Values |
|---|---|---|---|
horizontalArrangement | string | Start | Start, End, Center, SpaceAround, SpaceBetween, SpaceEvenly |
verticalAlignment | string | Top | Top, CenterVertically, Bottom |
spacing | number | absent | dp gap between children; overrides arrangement if set |
Children: any widgets. Events/write-back: none.
{
"type": "Row",
"modifier": [{ "name": "fillMaxWidth" }],
"params": {
"horizontalArrangement": "SpaceBetween",
"verticalAlignment": "CenterVertically"
},
"children": [
{ "type": "Text", "params": { "text": "$root.title" } },
{ "type": "Switch", "params": { "checked": "$root.enabled" } }
]
}
Box
Stack container; later children draw over earlier children.
| Param | Type | Default | Values |
|---|---|---|---|
contentAlignment | string | TopStart | TopStart, TopCenter, TopEnd, CenterStart, Center, CenterEnd, BottomStart, BottomCenter, BottomEnd |
Children: any widgets. Events/write-back: none.
{
"type": "Box",
"modifier": [{ "name": "fillMaxWidth" }],
"params": {
"contentAlignment": "Center"
},
"children": [
{ "type": "Image", "params": { "url": "$root.heroUrl", "contentDescription": "Offer" } },
{ "type": "CircularProgress", "params": { "visible": "$root.loading" } }
]
}
Card
Semantic surface container.
| Param | Type | Default | Meaning |
|---|---|---|---|
variant | string | default | default, outlined, elevated, or host token |
containerColor | color | surface | #RRGGBB / #AARRGGBB card fill |
Children: any widgets. Event: envelope onClick (any widget). Write-back: none.
{
"id": "offer-42",
"type": "Card",
"state": { "title": "Flat", "price": "25M" },
"onClick": { "type": "navigate", "screen": "offer", "json": "$local" },
"params": {
"variant": "elevated"
},
"children": [
{
"type": "Column",
"children": [
{ "type": "Text", "params": { "text": "$parent.title" } },
{ "type": "Text", "params": { "text": "$parent.price" } }
]
}
]
}
Scaffold
Full-screen Material layout with optional topBar, bottomBar, and floatingActionButton slots. Use it as the root of a BduiScreenHost destination. Slot widgets are envelope fields (not params); they participate in $id.* lookup and patch like children.
| Param | Type | Default | Values |
|---|---|---|---|
fabPosition | string | End | End, Center |
| Envelope | Type | Default | Meaning |
|---|---|---|---|
topBar | widget | absent | App bar slot |
bottomBar | widget | absent | Bottom bar slot |
floatingActionButton | widget | absent | FAB slot |
Children: content widgets, laid out inside the scaffold body with system padding. The bottomBar slot is padded above the system navigation bar (gesture handle / three-button nav); a background modifier on that slot still draws behind the inset. Events/write-back: none.
{
"id": "root",
"type": "Scaffold",
"state": { "title": "Choose a city" },
"topBar": {
"type": "TopAppBar",
"params": { "title": "$root.title" }
},
"floatingActionButton": {
"type": "FloatingActionButton",
"onClick": { "type": "popScreen", "result": { "title": "Astana" } },
"children": [{ "type": "Text", "params": { "text": "★" } }]
},
"children": [
{ "type": "Text", "params": { "text": "Pick a city" } }
]
}
TopAppBar
App bar for a Scaffold topBar slot.
| Param | Type | Default | Meaning |
|---|---|---|---|
title | string | "" | Title text |
navigationIcon | string | see below | back, close, or none |
onNavigationClick | action | popScreen | Runs when the navigation icon is pressed |
Default navigationIcon is back on a pushed BDUI screen and none on an embedded root. Children: trailing actions. Write-back: none.
{
"type": "TopAppBar",
"params": {
"title": "$root.title",
"navigationIcon": "back",
"onNavigationClick": { "type": "popScreen" }
}
}
FloatingActionButton
Circular action button, typically in a Scaffold floatingActionButton slot.
| Param | Type | Default | Meaning |
|---|---|---|---|
enabled | boolean/path | true | Suppresses click |
Children: icon/label, usually Text. Event: envelope onClick. Write-back: none.
{
"type": "FloatingActionButton",
"onClick": { "type": "popScreen", "result": { "ok": true } },
"children": [{ "type": "Text", "params": { "text": "+" } }]
}
LazyColumn
Scrollable virtualized vertical list. Each direct child is one item.
| Param | Type | Default | Values |
|---|---|---|---|
verticalArrangement | string | Top | Same as Column |
horizontalAlignment | string | Start | Same as Column |
spacing | number | absent | dp gap between items; overrides arrangement if set |
Children: any widgets as items. Events/write-back: none.
{
"type": "LazyColumn",
"modifier": [{ "name": "fillMaxWidth" }],
"children": [
{
"id": "offer-41",
"type": "Card",
"state": { "title": "First" },
"children": [{ "type": "Text", "params": { "text": "$parent.title" } }]
},
{
"id": "offer-42",
"type": "Card",
"state": { "title": "Second" },
"children": [{ "type": "Text", "params": { "text": "$parent.title" } }]
}
]
}
Dynamic items need stable ids for Compose keys, focus/scroll continuity, and streamed updates.
LazyRow
Scrollable virtualized horizontal list. Each direct child is one item.
| Param | Type | Default | Values |
|---|---|---|---|
horizontalArrangement | string | Start | Same as Row |
verticalAlignment | string | Top | Same as Row |
spacing | number | absent | dp gap between items; overrides arrangement if set |
Children: any widgets as items. Events/write-back: none. Give items a fixed height when LazyRow sits inside LazyColumn.
{
"type": "LazyRow",
"modifier": [{ "name": "paddingHorizontal", "value": "16" }],
"params": { "spacing": 12 },
"children": [
{
"id": "promo-1",
"type": "Card",
"modifier": [{ "name": "width", "value": "220" }, { "name": "height", "value": "132" }],
"children": [{ "type": "Text", "params": { "text": "Мебель" } }]
}
]
}
Stepper
Container for multi-step flows: shows one child (a step) at a time and animates between them. Spec 11. Use it for a booking, a form or a wizard whose steps share state; moving between steps is a patch of one value, instead of a visibility flag per step.
| Param | Type | Default | Meaning |
|---|---|---|---|
step | string/path | 0 | The step to show: a zero-based index, or the id of a child. Bind it to state to move between steps |
animate | string | slide | slide, fade or none. The height change between steps is animated too |
Rules:
- Only the selected child is composed. A step that is not shown keeps no UI state of its own, so keep values on an ancestor (the usual
stateon the flow's root) and bind the step's controls to it. - An index below zero shows the first step, one past the end shows the last, and a missing or unknown
stepshows the first. - There is no swiping: a flow has to validate before it moves on. Change
stepwith apatch, usually inonValidof avalidate. - When
stepis a state path, the system Back press moves to the previous step (a write ofstep - 1), except from the first step and from the last, which is the finished state.
{
"type": "Column",
"state": { "step": 0, "form": { "email": "" }, "errors": {} },
"children": [
{
"type": "Stepper",
"minSpec": 11,
"params": { "step": "$id.signup.step" },
"children": [
{ "type": "Column", "children": [
{ "type": "TextField", "params": { "value": "$id.signup.form.email", "error": "$id.signup.errors.email" } },
{ "type": "Button",
"onClick": {
"type": "validate",
"errorsPath": "$id.signup/state/errors",
"rules": [{ "path": "$id.signup.form.email", "key": "email", "required": true }],
"onValid": { "type": "patch", "patch": [{ "op": "add", "path": "$id.signup/state/step", "value": 1 }] }
},
"children": [{ "type": "Text", "params": { "text": "Next" } }] }
] },
{ "type": "Text", "params": { "text": "Thanks!" } }
]
}
]
}
Children: the steps. Events/write-back: none; Back writes step - 1 to the bound path.
Spacer
Empty fixed-size element.
| Param | Type | Default | Meaning |
|---|---|---|---|
width | number/path | 0 | dp |
height | number/path | 0 | dp |
Children/events/write-back: none.
{ "type": "Spacer", "params": { "height": 16 } }
Negative/non-finite values become zero.
Divider
Decorative separator.
| Param | Type | Default | Meaning |
|---|---|---|---|
thickness | number/path | 1 | dp |
color | color/path | $theme.colors.outline | #RRGGBB or #AARRGGBB |
Children/events/write-back: none. Excluded from accessibility semantics.
{ "type": "Divider", "params": { "thickness": 1, "color": "$theme.colors.outline" } }
Content
Calendar
Month grid for picking one date or a date range. Spec 8. Navigation and selection are local: no action or request is needed to change month or select a date.
| Param | Type | Default | Meaning |
|---|---|---|---|
mode | string | single | single or range |
start | string/path | "" | Selected date (yyyy-MM-dd). Write-back when a state path |
end | string/path | "" | Range end; write-back in range mode. An unset end is written as "" |
month | string | start/today | Initial month, yyyy-MM |
min, max | string/path | absent | Earliest/latest selectable date. min also accepts today |
disabled | array/path | [] | Unavailable dates: "2026-10-15" or { "from", "to" } ranges |
weekStart | string | monday | monday or sunday |
locale | string | en | en or ru month and weekday names |
monthNames | array | by locale | 12 custom month names |
weekdayNames | array | by locale | 7 custom names, Monday first |
unavailableLabel | string | Unavailable | Legend text for unavailable dates |
showLegend | boolean | true | Selected / unavailable legend |
compact | boolean | false | Shorter day cells, for a calendar inside a small card such as the chat |
today | string | system date | Override today, for tests and demos |
onChange | action | absent | After a selection is written. $event = { "start", "end" } |
onMonthChange | action | absent | After month navigation. $event = { "month" } |
Selectable means inside min..max and not in disabled. Unselectable dates ignore taps. In range mode the second tap completes the range; tapping before the start, or after a completed range, starts a new range; a range cannot span an unavailable date (the tap starts a new range instead). Availability always comes from disabled / min / max, which a tool or the backend fills; the widget never invents it.
{
"type": "Calendar",
"params": {
"mode": "range",
"start": "$root.search.depart",
"end": "$root.search.ret",
"min": "today",
"disabled": "$root.availability.disabled",
"onChange": { "type": "invokeTool", "tool": "get_time_slots", "arguments": { "date": "$root.search.depart" } }
}
}
Children/events: none beyond onChange / onMonthChange.
Select
Dropdown that writes the chosen option back to its bound path. Spec 9. Use it for a choice among many options where a RadioGroup would be too long (a specialty, then the doctors of that specialty).
| Param | Type | Default | Meaning |
|---|---|---|---|
options | array/path | [] | Plain strings, or { "value", "label"?, "subtitle"? }. Entries without a value are dropped |
value | string/path | "" | Selected option value. Write-back when a state path. A value not in options shows the placeholder |
label | string | absent | Small caption above the selection |
placeholder | string | Select | Shown while nothing valid is selected |
error | string/path | "" | Validation message; non-empty outlines the field in the error color and shows the message |
enabled | boolean | true | Also disabled while options is empty |
onChange | action | absent | Dispatched after the value is written, so $root paths already hold the new value |
{
"type": "Select",
"params": {
"label": "Specialty",
"options": "$root.options.specialties",
"value": "$root.booking.specialty",
"onChange": { "type": "invokeTool", "tool": "get_doctors", "arguments": { "specialty": "$root.booking.specialty" } }
}
}
Children: none.
Review
Read-only summary the user checks before committing. Spec 8. It renders structured data and has no business logic; confirm and cancel are ordinary Button widgets placed under it, so nothing runs until the user presses one.
| Param | Type | Meaning |
|---|---|---|
title | string/path | Heading |
description | string/path | Explanatory text |
sections | array/path | [{ "title", "editStep"?, "editLabel"?, "rows": [{ "label", "value", "emphasis"? }], "items": [{ "title", "subtitle"?, "image"?, "trailing"? }] }] |
breakdown | array/path | Price lines [{ "label", "value", "kind"? }]; kind: "discount" is highlighted |
total | object/path | { "label", "value" } |
warning | string/path | Highlighted notice |
terms | string/path | Fine print |
onEdit | action | Dispatched when a section's edit link is pressed. $event = that section's editStep |
Bind every param to state ("sections": "$root.review.sections") so a tool or mapper can fill it from verified data. Values are shown as given, so format money and dates before putting them in state.
{
"type": "Review",
"params": {
"title": "Review your booking",
"sections": "$root.review.sections",
"total": "$root.review.total",
"onEdit": { "type": "patch", "patch": [{ "op": "add", "path": "$root/state/ui/step", "value": "form" }] }
}
}
Children/write-back: none.
Chart
Data chart drawn by the SDK: line, area, bar, pie and donut. Use it for numbers the user asked to see (spending by category, price history, monthly totals). Spec 7.
| Param | Type | Default | Meaning |
|---|---|---|---|
kind | string | line | line, area, bar, pie, donut |
labels | array/path | line/area/bar: x-axis label per value | |
values | array/path | line/area/bar, one series: numbers, same length as labels | |
series | array/path | line/area/bar, several series: [{ "name", "values": [..], "color"? }] | |
slices | array/path | pie/donut: [{ "label", "value", "color"? }] | |
valuePrefix | string/path | text before every number | |
valueSuffix | string/path | text after every number, e.g. " ₸" | |
height | number/path | 200 | dp, 80–600; line, area and bar only |
centerText | string/path | total | donut: text in the hole |
centerLabel | string/path | donut: caption under centerText | |
showLegend | boolean | auto | true for pie, donut and multi-series charts |
showValues | boolean | auto | bar: value above each bar; on up to 8 bars |
smooth | boolean | true | line/area: curved lines |
animate | boolean | true | draw-in animation |
Colors default to the theme primary followed by a fixed palette; text and grid use theme colors. A flat values list, series and slices are interchangeable, so a chart still renders if the data arrives in the other shape. Missing or empty data shows No data.
Children/events/write-back: none.
{
"type": "Chart",
"params": {
"kind": "donut",
"valueSuffix": " ₸",
"slices": [
{ "label": "Groceries", "value": 148200 },
{ "label": "Transport", "value": 62400 },
{ "label": "Shopping", "value": 214000 }
]
}
}
{
"type": "Chart",
"params": { "kind": "area", "labels": ["Sep 1", "Sep 15", "Oct 1"], "values": [399990, 372000, 359990], "valueSuffix": " ₸" }
}
Text
| Param | Type | Default | Meaning |
|---|---|---|---|
text | string/path | required | Unresolved path becomes empty |
color | color/path | $theme.colors.onSurface | Text color |
fontSize | number/path | variant value | sp |
fontWeight | integer | variant value | 100–900 |
fontStyle | string | Normal | Normal, Italic |
variant | string | bodyMedium | Typography/component token |
maxLines | integer | unlimited | Positive |
overflow | string | Clip | Clip, Ellipsis |
textAlign | string | Start | Start, Center, End |
Children/events/write-back: none.
{
"type": "Text",
"params": {
"text": "$root.offer.title",
"variant": "titleLarge",
"fontSize": 22,
"maxLines": 2,
"overflow": "Ellipsis"
}
}
Escape a literal prefix: "text": "$$root is a path prefix".
Image
| Param | Type | Default | Meaning |
|---|---|---|---|
contentDescription | string/path/null | required | null only for decorative image |
url | string/path | absent | Remote HTTP(S) image |
assetName | string | absent | Bundled Compose resource |
contentScale | string | Fit | Fit, Crop, FillBounds, Inside |
alpha | number/path | 1 | Clamped to 0..1 |
placeholder | string | absent | Bundled loading asset |
errorAsset | string | absent | Bundled failure asset |
Children/events/write-back: none.
{
"type": "Image",
"modifier": [
{ "name": "fillMaxWidth" },
{ "name": "height", "value": "240" }
],
"params": {
"url": "$root.offer.heroUrl",
"assetName": "offer_placeholder",
"contentDescription": "$root.offer.title",
"contentScale": "Crop"
}
}
url wins over assetName. Load failure displays errorAsset or empty space and logs; it is not an action failure.
Button
| Param | Type | Default | Meaning |
|---|---|---|---|
variant | string | primary | primary, secondary, tertiary, danger, or host token |
loading | boolean/path | false | Displays progress and suppresses envelope onClick |
Children: label/content, usually Text. Event: envelope onClick. Write-back: none.
{
"type": "Button",
"onClick": {
"type": "http",
"method": "POST",
"url": "/v1/form",
"body": { "fromState": "$root.form" },
"success": { "apply": "none" }
},
"params": {
"enabled": "$root.canSubmit",
"loading": "$root.submitting",
"variant": "primary"
},
"children": [{ "type": "Text", "params": { "text": "Submit" } }]
}
No envelope onClick means not clickable. Disabled or loading buttons do not dispatch.
CircularProgress
| Param | Type | Default | Meaning |
|---|---|---|---|
progress | number/path | absent | 0..1; absent is indeterminate |
strokeWidth | number/path | theme value | dp |
color | color/path | $theme.colors.primary | Indicator color |
Children/events/write-back: none.
{ "type": "CircularProgress", "params": { "visible": "$root.loading", "strokeWidth": 3 } }
LinearProgress
| Param | Type | Default | Meaning |
|---|---|---|---|
progress | number/path | absent | 0..1; absent is indeterminate |
color | color/path | $theme.colors.primary | Active color |
trackColor | color/path | component token | Track color |
Children/events/write-back: none. Progress is clamped.
{ "type": "LinearProgress", "params": { "progress": "$root.upload.progress" } }