Reference
Widget reference
Every widget, modifier and role in compose-rust's schema. The schema is closed: the renderer
interprets exactly this list and nothing else. A widget kind it does not recognise produces a
ProtocolError event rather than a crash.
The schema and the renderer half of every widget below exist in 0.0.1
Works. The Rust functions you call to emit them, such as
Column(...) and Text(...), arrive with the authoring API in 1.0.0
Planned. The authoring API adds no widget of its own, so
this page is also the list you will write with.
Widgets
The names are Compose's. Each widget has a fixed tag in WidgetKind, and the
Kotlin interpreter has one renderer function for each.
| Group | Widget | What it is |
|---|---|---|
| Layout | Column, Row | Children along one axis, with an arrangement on the main axis and an alignment on the cross axis. |
Box | Children stacked on top of each other, aligned on both axes. | |
ScrollColumn, ScrollRow | A column or row that scrolls. Every child is a real node; for long lists use the lazy widgets. | |
Spacer | Empty space, usually sized by weight or a fixed size. | |
SplitPane | Two panes with a divider the user can drag or move with the keyboard. | |
Scaffold, ScaffoldSlot | A screen's frame: bars, a page, a floating action. The renderer arranges the slots the application filled for the window's width. | |
FileDropTarget | A region that accepts files dropped from the system and reports their paths. | |
| Text and input | Text | Text, with a type role, colour, alignment, line limit and overflow. Can carry styled runs and accept streamed tails. |
TextField | Uncontrolled text input. The edit buffer and IME composition stay in the renderer. | |
SelectionContainer | Makes the text inside it selectable and copyable. | |
| Controls | Button | A button in one of four variants: Filled, Tonal, Outlined, Text. |
Checkbox, RadioButton, Switch | Fully controlled toggles: what you pass is exactly what is drawn. | |
Slider | A value on a range. The renderer owns the position during a drag. | |
Dropdown | A choice among options in a popup. | |
DatePicker, TimePicker | Date and time choosers. The renderer owns the picking surface, locale and format. | |
Chip | A small choice or filter. It draws the chosen state it was sent and keeps none of its own. | |
ProgressIndicator | Determinate or indeterminate progress. | |
| Containers | Card, Surface | Filled panels that take shape, elevation and material from the design system. |
TopAppBar | A screen's top bar with a title and actions. | |
Divider | A hairline between groups. | |
Tooltip, Badge | A hint on hover or long press; a mark attached to another widget. | |
FloatingAction | The one action a screen is about. The design system decides how it looks and where it sits. | |
| Lists | LazyColumn, LazyRow | Windowed lists. The renderer asks for the visible range and the Host builds only that. See Lists & streaming. |
LazyGrid | A windowed grid with the same contract. | |
LinearProgressIndicator | The worked example of an extension widget (tag 100). See Extensions. | |
| Navigation | Navigation, NavigationItem | A set of destinations. The renderer draws a bar, a rail or a drawer from the width it measured. |
Tabs | Tabs across the top of a page. | |
| Overlays | Dialog, Menu, Sheet | Content over the page. The renderer picks the edge a sheet enters from, and owns the enter and exit. |
| messages | Transient messages are not part of the tree. show_message and Message send them, and they exist in 0.0.1. | |
| Pictures | Image, Icon | An image from a registered asset; an icon by role. |
Canvas | Custom drawing from a DrawList value. Two equal lists send nothing. |
Modifiers
In Compose a Modifier applies to any composable, and the same is true here: every
widget takes every modifier, including Text and Spacer. On the
wire a modifier list is a sequence of fixed-size values, and the renderer rebuilds it into a
Compose Modifier chain.
| Modifier | Value | Effect |
|---|---|---|
Weight | f32 | Share of the leftover main-axis space inside a Row or Column. |
FillMaxWidth, FillMaxHeight | none | Take the whole available width or height. |
Width, Height, Size | dp | A fixed size. |
Padding, PaddingEach | dp | An inset you chose, the same on all sides or per side. |
PaddingRole | SpaceRole | An inset the design system chose. Prefer this one. |
Background | Paint | A fill behind the widget. |
Shape | dp per corner | Corner rounding you chose. |
ShapeRole | ShapeRole | Corner rounding the design system chose. |
Border | dp and Paint | A border of a width and a colour. |
Elevation | dp | Resting height. Each design system draws it its own way. |
Material | MaterialRole | What a surface is made of, such as glass, resolved by the design system. |
Motion | MotionRole | The design system's timing for this widget's own transitions. |
Clickable | a handler id | Makes any widget report clicks. |
ObserveSize | a token | Asks the renderer to report this node's measured size, under a token the application chose, read with node_size. |
Unset is not zero. A modifier you did not write sends nothing at all. No padding and a padding of zero look the same on screen, but only one of them should cost a record.
The role vocabulary
Widgets emit roles, not literals. A role names a job ("the accent colour", "the heading rung", "a medium corner"), and the renderer resolves it against the active design system. That is what lets one tree come out as Material 3 on one machine and Liquid Glass on another. Design systems explains how the system is chosen. All of these types exist in 0.0.1.
Paint and Color
Anywhere a colour is taken, the type is Paint: a role, a literal ARGB value, or
a registered brush such as a gradient.
use compose_rust::{Color, ColorRole, Paint};
let accent = Paint::Role(ColorRole::Primary); // resolved by the design system
let brand = Paint::Literal(Color::rgb(0x1b6ef3)); // opaque, 0xRRGGBB
let veil = Paint::Literal(Color::argb(0x80ff0000)); // 0xAARRGGBB
A literal is the escape hatch. It survives a design-system change unchanged. That is sometimes
what you want, such as a brand colour, and usually not, such as a colour that reads on a light
page and disappears on a dark one. For brand colours, a Palette is better; see
Application colours.
ColorRole: 45 roles
Every role that names ink is paired with the surface it reads on. OnPrimary is
the ink for something filled with Primary, and so on.
| Group | Roles | For |
|---|---|---|
| Accent | Primary, OnPrimary, Secondary, OnSecondary, Tertiary, OnTertiary | The three accents: what the eye goes to first, second and third. |
| Page | Background, OnBackground | The page itself and the ink on it. |
| Surface | Surface, OnSurface, SurfaceVariant, OnSurfaceVariant, SurfaceContainer | Reading surfaces. OnSurfaceVariant is the muted ink, for captions and labels. |
| Container | PrimaryContainer, OnPrimaryContainer, SecondaryContainer, OnSecondaryContainer, TertiaryContainer, OnTertiaryContainer | Quiet fills that read as relatives of their accent, each with its own ink. |
| Line | Outline, OutlineVariant | Borders and hairlines, strong and quiet. |
| Error | Error, OnError | What went wrong, and a destructive action's label. |
| Code | SyntaxKeyword, SyntaxString, SyntaxComment and the other syntax roles, plus DiffAdded, DiffRemoved, DiffModified and their containers and emphasis | Twenty-two roles that paint code. Each system answers them from its platform's own code editor, and every syntax ink reads at 4.5:1 on SurfaceContainer. |
Reach for SurfaceContainer when a panel has to be visible against the
page. Material 3 gives Surface and Background the same
value on purpose and shows depth through tonal containers instead. A panel filled with
Surface on a page of Background is drawn correctly and cannot be
seen. Every supported design system makes SurfaceContainer visible against the
page.
TypeRole: nine rungs
| Rung | For |
|---|---|
Display | The largest thing on a screen, used once. |
Headline | A screen's title. |
Title | A section's title, a bar's title. |
Subtitle | The line under a title. |
Body | Paragraphs. The default rung. |
BodyStrong | Body text that has to carry weight. |
Label | A button's text, a chip, a field label. |
Caption | The smallest readable rung: timestamps, footnotes. |
Mono | Monospace: code, paths, figures that have to line up. |
ShapeRole, SpaceRole and MotionRole
ShapeRole is None, ExtraSmall, Small,
Medium, Large and Full. The radius is the design
system's answer, and Full means a capsule.
SpaceRole is None, Xs, Sm,
Md, Lg, Xl and Xxl. Density differs per
design system, which is the reason to spell a gap as a role rather than a number of dp.
MotionRole is Instant, Quick, Standard,
Slow and Emphasized. Each design system gives them its own duration
and curve.
IconRole
An icon is addressed by what it is for, never by a platform icon name: Back,
Forward, Close, Search, Add,
Check, Settings, More, Home,
List, Inbox, Menu and History. The role is
what lets one declaration come out as an SF Symbol under Apple's systems and a Material Symbol
under Material 3.
Layout and text enums
| Enum | Variants | Where |
|---|---|---|
Arrangement | Start, Center, End, SpaceBetween, SpaceAround, SpaceEvenly | Main axis of Row and Column |
Alignment | TopStart through BottomEnd, nine in all | Cross axis of Row and Column; both axes of Box |
TextAlign | Start, Center, End, Justify | Text |
TextOverflow | Clip, Ellipsis, Visible | Text with a line limit |
ButtonVariant | Filled, Tonal, Outlined, Text | Button |
MessageDuration | Short, Long | Transient messages |
Window size
The UI is described in Rust and measured by the renderer, so the only way Rust learns the
window's width is for the renderer to report it. window_size() returns a
WindowSize with width_dp, height_dp and
class, plus is_compact(), is_medium() and
is_expanded(). It exists in 0.0.1.
| Class | Width | Shape |
|---|---|---|
WindowSizeClass::Compact | under 600 dp | Phone-shaped. One column. |
WindowSizeClass::Medium | 600 to 840 dp | Tablet-shaped. |
WindowSizeClass::Expanded | 840 dp and wider | Desktop-shaped. Room for a permanent sidebar. |
Only a class change is reported. The renderer sends one event when the class
changes and nothing in between. Dragging a window from 700 dp to 800 dp wakes nothing
in Rust, because a size that changed every layout pass would run Rust code every layout pass.
Before the first measurement the value is Compact at 0 by 0, because a
one-column layout works at any width.
Navigation, Sheet and Scaffold already adapt without
being asked, by moving nodes the renderer already has. Use the window size for the decisions
the widgets cannot make for you, such as two columns instead of one.
Where each widget keeps its state
Application state is yours, and UI-local state is the renderer's. These are the places that surprise people, because a widget looks controlled and is not, or the other way round.
| Widget | What the renderer owns | What Rust sends |
|---|---|---|
TextField | The whole edit buffer, the selection, the IME composition | No value. Changes arrive as events; SetText replaces the text after composition ends. |
Dialog, Sheet, Menu | Open state, and the enter and exit animation | An open flag that seeds it and carries an outside change. |
Dropdown | Whether the popup is open | The selected index only. |
Navigation, Tabs | Which one is marked, and the move between them | The selected index, which seeds it. |
Slider | The position a drag is passing through | The value, which seeds it and moves it from outside. |
ScrollColumn, ScrollRow, the lazy widgets | Scroll position, and the read-ahead buffer | The item count and the items in the requested range. |
Checkbox, RadioButton, Switch, Chip | Nothing. These are fully controlled | The checked or selected state, which is exactly what is drawn. |
DatePicker, TimePicker | The picking surface, the locale, the time zone, the format | The value and its bounds. |
| every widget | Focus, hover, press feedback, animation progress | Nothing. |
Extensions
An extension is a compile-time pair of source files, one Rust and one Kotlin, built into both
sides. It is not a runtime plugin: a deployed renderer cannot load a new widget, because the
schema hash would no longer match. The crate ships one worked example,
LinearProgressIndicator, with a single progress: f32 property.
Adding your own means registering the widget and property tags in
compose-rust/src/extensions.rs and rendering the new kind in a paired Kotlin
renderer. Extension widget tags start at 100, so they never collide with a core widget added
later.