Elements and Widgets
In Iced, your user interface is built from widgets that are converted into elements . Understanding the relationship between these two concepts is key to building effective UIs.
What is an Element?
An Element is a generic widget - it’s what your view function must return:
use iced :: Element ;
fn view ( state : & State ) -> Element <' _ , Message > {
// Return any widget converted to an Element
}
Element<'a, Message> is a type alias for a widget that is generic over the message type it produces.
A widget is a specific UI component like a button, text input, or column. Widgets implement layout, rendering, and event handling:
use iced :: widget :: {button, text, column};
// These are all widgets:
let my_button = button ( "Click me" );
let my_text = text ( "Hello" );
let my_column = column! [ my_text , my_button ];
Every widget can be converted to an Element by calling .into():
fn view ( state : & State ) -> Element <' _ , Message > {
button ( "Click me" )
. on_press ( Message :: ButtonPressed )
. into () // Convert widget to Element
}
The .into() call is required because view must return Element, not a specific widget type.
Iced provides many built-in widgets in the widget module:
Text
Button
Text Input
Checkbox
use iced :: widget :: text;
text ( "Hello, world!" )
. size ( 20 )
. color ([ 1.0 , 0.0 , 0.0 ]) // RGB red
Display static or dynamic text. use iced :: widget :: button;
button ( "Click me" )
. on_press ( Message :: ButtonPressed )
. padding ( 10 )
Clickable button that produces messages. use iced :: widget :: text_input;
text_input ( "Placeholder text" , & state . value)
. on_input ( Message :: InputChanged )
. on_submit ( Message :: Submit )
. padding ( 10 )
Single-line text input field. use iced :: widget :: checkbox;
checkbox ( state . checked)
. label ( "Enable feature" )
. on_toggle ( Message :: CheckboxToggled )
Toggle checkbox with optional label.
Layout widgets position and arrange other widgets:
Column
Arranges widgets vertically:
use iced :: widget :: column;
column! [
text ( "Title" ),
text ( "Subtitle" ),
button ( "Action" ),
]
. spacing ( 10 ) // Space between items
. padding ( 20 ) // Outer padding
Row
Arranges widgets horizontally:
use iced :: widget :: row;
row! [
text ( "Left" ),
text ( "Center" ),
text ( "Right" ),
]
. spacing ( 10 )
. align_y ( Center )
Container
Positions or aligns a single widget:
use iced :: widget :: container;
use iced :: Fill ;
container ( text ( "Centered!" ))
. center_x ( Fill )
. center_y ( Fill )
. width ( Fill )
. height ( Fill )
There is no unified layout system in Iced. Each widget implements its own layout strategy.
Builder Pattern
Widgets are configured using the builder pattern - method chaining:
use iced :: widget :: {button, column, text};
column! [
text ( counter . value)
. size ( 20 ) // Configure text size
. color ([ 1.0 , 0.0 , 0.0 ]), // Red color
button ( "Increment" )
. on_press ( Message :: Increment )
. padding ( 10 ), // Button padding
]
. spacing ( 10 ) // Column spacing
. into () // Convert to Element
Each method returns the widget, allowing you to chain multiple configurations.
Widget dimensions are controlled using Length:
use iced :: Shrink ;
button ( "Click me" )
. width ( Shrink ) // Use intrinsic size
. height ( Shrink )
Widget uses its natural/minimum size (default for most widgets). use iced :: Fill ;
button ( "Click me" )
. width ( Fill ) // Take all available width
. height ( Fill ) // Take all available height
Widget expands to fill available space. button ( "Click me" )
. width ( 300 ) // 300 pixels wide
. height ( 50 ) // 50 pixels tall
Widget uses a fixed pixel size.
Most widgets use Shrink by default, but will inherit Fill from their children.
Message Production
Widgets produce messages when users interact with them:
button ( "Click me" )
. on_press ( Message :: ButtonPressed )
Without .on_press(), the button is disabled.
on_input (Text Inputs)
text_input ( "Enter text" , & state . input)
. on_input ( Message :: InputChanged ) // Called on every keystroke
. on_submit ( Message :: Submit ) // Called when Enter is pressed
The on_input closure wraps the String value in your message.
on_toggle (Checkboxes)
checkbox ( state . checked)
. on_toggle ( Message :: Toggled ) // Receives bool
The boolean represents the new checked state.
Generic Message Types
Widgets and elements are generic over the message type:
// A button that produces Message::Increment
let button : Button <' _ , Message > = button ( "Increment" )
. on_press ( Message :: Increment );
// Converted to Element<Message>
let element : Element <' _ , Message > = button . into ();
This type safety ensures widgets can only produce messages your update function can handle.
Message Mapping
You can transform messages from one type to another using .map():
// Widget produces TaskMessage
fn task_view ( task : & Task ) -> Element <' _ , TaskMessage > {
button ( "Delete" )
. on_press ( TaskMessage :: Delete )
. into ()
}
// Map TaskMessage to parent Message
fn view ( state : & State ) -> Element <' _ , Message > {
state . tasks
. iter ()
. map ( | task | {
task_view ( task )
. map ( Message :: TaskMessage ) // Wrap in parent message
})
. collect ()
}
This enables component composition and message nesting.
Advanced Layout Example
Here’s a real layout from the todos example:
use iced :: widget :: {column, container, row, text, text_input};
use iced :: { Center , Fill , Element };
fn view ( state : & State ) -> Element <' _ , Message > {
let title = text ( "todos" )
. width ( Fill )
. size ( 100 )
. align_x ( Center );
let input = text_input ( "What needs to be done?" , & state . input_value)
. on_input ( Message :: InputChanged )
. on_submit ( Message :: CreateTask )
. padding ( 15 )
. size ( 30 );
let controls = view_controls ( & state . tasks, state . filter);
let tasks = view_tasks ( & state . tasks, state . filter);
let content = column! [ title , input , controls , tasks ]
. spacing ( 20 )
. max_width ( 800 );
scrollable ( center_x ( content ) . padding ( 40 )) . into ()
}
Widgets can be styled using the .style() method:
use iced :: widget :: button;
use iced :: Theme ;
button ( "Primary" )
. style ( button :: primary ) // Built-in style function
// Or with a custom closure:
button ( "Custom" )
. style ( | theme : & Theme , status | {
let palette = theme . extended_palette ();
match status {
button :: Status :: Active => {
button :: Style :: default ()
. with_background ( palette . success . strong . color)
}
_ => button :: primary ( theme , status ),
}
})
Most built-in widgets provide styling functions in their respective modules, like button::primary, container::rounded_box, or text::danger.
You can create your own custom widgets by implementing the Widget trait, but that’s an advanced topic. For most use cases, composing built-in widgets is sufficient.
Best Practices
Element Guidelines
Always call .into() to convert widgets to elements
Use Element<'_, Message> as your view return type
Extract complex UI into separate functions that return Element
Widget Guidelines
Use layout widgets (column, row, container) to structure your UI
Configure widgets with the builder pattern
Use Fill sparingly - most widgets should use Shrink
Extract repeated widget patterns into helper functions
Message Guidelines
Only add message handlers (like .on_press()) when needed
Use .map() to transform messages for component composition
Match your message types between view and update
Next Steps