Themes and styling
Iced lets you customize the look and feel of your app on multiple levels:
- Singular widget instances can have unique looks.
- Themes can define their own colorschemes and base widget styles, which are used by default.
Themes
You can set the theme used by your app using Application::theme:
fn main() -> iced::Result {
iced::application(...)
.theme(|_| iced::Theme::Dracula)
.run()
}
A nicer way to do this is by storing the theme in your app struct, then implementing a theme method (or something similar) for it which looks something like this:
use iced::{Element, Theme, widget::pick_list};
fn main() -> iced::Result {
iced::application(App::default, App::update, App::view)
.theme(App::theme)
.run()
}
#[derive(Default)]
struct App {
theme: Option<Theme>,
}
#[derive(Debug, Clone)]
enum Message {
ThemeChanged(Theme),
}
impl App {
fn theme(&self) -> Option<Theme> {
self.theme.clone()
}
fn update(&mut self, message: Message) {
match message {
Message::ThemeChanged(theme) => self.theme = Some(theme),
};
}
fn view(&self) -> Element<'_, Message> {
pick_list(Theme::ALL, self.theme.clone(), Message::ThemeChanged)
.placeholder("Choose a theme...")
.into()
}
}
NOTE: Returning
Option<Theme>allows us to fall back to the default theme (this depends on the user's OS settings) when we returnNone.This is reactive, i.e. changing that preference will cause our app to react as well, so long as we still return
None.
Then you can use it like so:
use iced::{Element, Theme, widget::pick_list};
fn main() -> iced::Result {
iced::application(App::default, App::update, App::view)
.theme(App::theme)
.run()
}
#[derive(Default)]
struct App {
theme: Option<Theme>,
}
#[derive(Debug, Clone)]
enum Message {
ThemeChanged(Theme),
}
impl App {
fn theme(&self) -> Option<Theme> {
self.theme.clone()
}
fn update(&mut self, message: Message) {
match message {
Message::ThemeChanged(theme) => self.theme = Some(theme),
};
}
fn view(&self) -> Element<'_, Message> {
pick_list(Theme::ALL, self.theme.clone(), Message::ThemeChanged)
.placeholder("Choose a theme...")
.into()
}
}
Complete example
main.rs
use iced::{Element, Theme, widget::pick_list};
fn main() -> iced::Result {
iced::application(App::default, App::update, App::view)
.theme(App::theme)
.run()
}
#[derive(Default)]
struct App {
theme: Option<Theme>,
}
#[derive(Debug, Clone)]
enum Message {
ThemeChanged(Theme),
}
impl App {
fn theme(&self) -> Option<Theme> {
self.theme.clone()
}
fn update(&mut self, message: Message) {
match message {
Message::ThemeChanged(theme) => self.theme = Some(theme),
};
}
fn view(&self) -> Element<'_, Message> {
pick_list(Theme::ALL, self.theme.clone(), Message::ThemeChanged)
.placeholder("Choose a theme...")
.into()
}
}
Custom themes
The built-in iced::Theme type has a Custom variant which can be created using Theme::custom (or Theme::custom_with_fn for greater control over the generated color palette).
If you need even more customization, you can create your own Theme type. The requirements are:
- Implement
Basefor your custom type. - For each widget you plan to support, implement its
Catalogtrait (if it has one) and the dependencies of that trait.
For a reference custom theme, see iced_material.
Styling
Per-widget styling is done via styling methods. These take &Theme (usually) and return a widget Style. The following examples are made with the button widget in mind, but most things apply to other widgets as well.
Most widgets include a default styling method, but some also have extras. You can use these by passing them to the widget's style method:
use iced::{
Element, Theme, border, color,
widget::{button, row},
};
fn main() -> iced::Result {
iced::run(App::update, App::view)
}
#[derive(Default)]
struct App;
#[derive(Debug, Clone)]
enum Message {
Noop,
}
impl App {
fn update(&mut self, message: Message) {
match message {
Message::Noop => {}
}
}
fn view(&self) -> Element<'_, Message> {
row![
button("Built-in 'danger' style")
.on_press(Message::Noop)
.style(button::danger),
button("User defined inline style")
.on_press(Message::Noop)
.style(|_, _| button::Style {
background: Some(color!(0x1e1e2e).into()),
text_color: color!(0xc0ffee),
border: border::rounded(10),
..Default::default()
}),
button("User defined style method")
.on_press(Message::Noop)
.style(button_danger_text)
]
.spacing(10)
.into()
}
}
// This is a *slightly* modified version of button::text
fn button_danger_text(theme: &Theme, status: button::Status) -> button::Style {
let palette = theme.extended_palette();
let base = button::Style {
text_color: palette.danger.base.color,
..button::Style::default()
};
match status {
button::Status::Active | button::Status::Pressed => base,
button::Status::Hovered => button::Style {
text_color: palette.danger.base.color.scale_alpha(0.8),
..base
},
button::Status::Disabled => button::Style {
text_color: palette.danger.base.color.scale_alpha(0.5),
..base
}
}
}
You can also easily create static (or even dynamic) inline styles:
use iced::{
Element, Theme, border, color,
widget::{button, row},
};
fn main() -> iced::Result {
iced::run(App::update, App::view)
}
#[derive(Default)]
struct App;
#[derive(Debug, Clone)]
enum Message {
Noop,
}
impl App {
fn update(&mut self, message: Message) {
match message {
Message::Noop => {}
}
}
fn view(&self) -> Element<'_, Message> {
row![
button("Built-in 'danger' style")
.on_press(Message::Noop)
.style(button::danger),
button("User defined inline style")
.on_press(Message::Noop)
.style(|_, _| button::Style {
background: Some(color!(0x1e1e2e).into()),
text_color: color!(0xc0ffee),
border: border::rounded(10),
..Default::default()
}),
button("User defined style method")
.on_press(Message::Noop)
.style(button_danger_text)
]
.spacing(10)
.into()
}
}
// This is a *slightly* modified version of button::text
fn button_danger_text(theme: &Theme, status: button::Status) -> button::Style {
let palette = theme.extended_palette();
let base = button::Style {
text_color: palette.danger.base.color,
..button::Style::default()
};
match status {
button::Status::Active | button::Status::Pressed => base,
button::Status::Hovered => button::Style {
text_color: palette.danger.base.color.scale_alpha(0.8),
..base
},
button::Status::Disabled => button::Style {
text_color: palette.danger.base.color.scale_alpha(0.5),
..base
}
}
}
Notice the two underscores? They're for the &Theme and Status that get passed to our closure. What's this Status, you ask? Well, a button may be hovered at a given moment, or it could be disabled, be pressed down, or neither. Taking this into account, lets see how we can create a dynamic styling method:
use iced::{
Element, Theme, border, color,
widget::{button, row},
};
fn main() -> iced::Result {
iced::run(App::update, App::view)
}
#[derive(Default)]
struct App;
#[derive(Debug, Clone)]
enum Message {
Noop,
}
impl App {
fn update(&mut self, message: Message) {
match message {
Message::Noop => {}
}
}
fn view(&self) -> Element<'_, Message> {
row![
button("Built-in 'danger' style")
.on_press(Message::Noop)
.style(button::danger),
button("User defined inline style")
.on_press(Message::Noop)
.style(|_, _| button::Style {
background: Some(color!(0x1e1e2e).into()),
text_color: color!(0xc0ffee),
border: border::rounded(10),
..Default::default()
}),
button("User defined style method")
.on_press(Message::Noop)
.style(button_danger_text)
]
.spacing(10)
.into()
}
}
// This is a *slightly* modified version of button::text
fn button_danger_text(theme: &Theme, status: button::Status) -> button::Style {
let palette = theme.extended_palette();
let base = button::Style {
text_color: palette.danger.base.color,
..button::Style::default()
};
match status {
button::Status::Active | button::Status::Pressed => base,
button::Status::Hovered => button::Style {
text_color: palette.danger.base.color.scale_alpha(0.8),
..base
},
button::Status::Disabled => button::Style {
text_color: palette.danger.base.color.scale_alpha(0.5),
..base
}
}
}
use iced::{
Element, Theme, border, color,
widget::{button, row},
};
fn main() -> iced::Result {
iced::run(App::update, App::view)
}
#[derive(Default)]
struct App;
#[derive(Debug, Clone)]
enum Message {
Noop,
}
impl App {
fn update(&mut self, message: Message) {
match message {
Message::Noop => {}
}
}
fn view(&self) -> Element<'_, Message> {
row![
button("Built-in 'danger' style")
.on_press(Message::Noop)
.style(button::danger),
button("User defined inline style")
.on_press(Message::Noop)
.style(|_, _| button::Style {
background: Some(color!(0x1e1e2e).into()),
text_color: color!(0xc0ffee),
border: border::rounded(10),
..Default::default()
}),
button("User defined style method")
.on_press(Message::Noop)
.style(button_danger_text)
]
.spacing(10)
.into()
}
}
// This is a *slightly* modified version of button::text
fn button_danger_text(theme: &Theme, status: button::Status) -> button::Style {
let palette = theme.extended_palette();
let base = button::Style {
text_color: palette.danger.base.color,
..button::Style::default()
};
match status {
button::Status::Active | button::Status::Pressed => base,
button::Status::Hovered => button::Style {
text_color: palette.danger.base.color.scale_alpha(0.8),
..base
},
button::Status::Disabled => button::Style {
text_color: palette.danger.base.color.scale_alpha(0.5),
..base
}
}
}
Complete example
main.rs
use iced::{
Element, Theme, border, color,
widget::{button, row},
};
fn main() -> iced::Result {
iced::run(App::update, App::view)
}
#[derive(Default)]
struct App;
#[derive(Debug, Clone)]
enum Message {
Noop,
}
impl App {
fn update(&mut self, message: Message) {
match message {
Message::Noop => {}
}
}
fn view(&self) -> Element<'_, Message> {
row![
button("Built-in 'danger' style")
.on_press(Message::Noop)
.style(button::danger),
button("User defined inline style")
.on_press(Message::Noop)
.style(|_, _| button::Style {
background: Some(color!(0x1e1e2e).into()),
text_color: color!(0xc0ffee),
border: border::rounded(10),
..Default::default()
}),
button("User defined style method")
.on_press(Message::Noop)
.style(button_danger_text)
]
.spacing(10)
.into()
}
}
// This is a *slightly* modified version of button::text
fn button_danger_text(theme: &Theme, status: button::Status) -> button::Style {
let palette = theme.extended_palette();
let base = button::Style {
text_color: palette.danger.base.color,
..button::Style::default()
};
match status {
button::Status::Active | button::Status::Pressed => base,
button::Status::Hovered => button::Style {
text_color: palette.danger.base.color.scale_alpha(0.8),
..base
},
button::Status::Disabled => button::Style {
text_color: palette.danger.base.color.scale_alpha(0.5),
..base
}
}
}