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::{widget::pick_list, Element, Theme};
fn main() -> iced::Result {
iced::application("Themes", App::update, App::view)
.theme(App::theme)
.run()
}
#[derive(Default)]
struct App {
theme: Theme,
}
#[derive(Debug, Clone)]
enum Message {
ThemeChanged(Theme),
}
impl App {
fn theme(&self) -> Theme {
self.theme.clone()
}
fn update(&mut self, message: Message) {
match message {
Message::ThemeChanged(theme) => self.theme = theme,
};
}
fn view(&self) -> Element<'_, Message> {
pick_list(Theme::ALL, Some(self.theme.clone()), Message::ThemeChanged).into()
}
}
Then you can use it like so:
use iced::{widget::pick_list, Element, Theme};
fn main() -> iced::Result {
iced::application("Themes", App::update, App::view)
.theme(App::theme)
.run()
}
#[derive(Default)]
struct App {
theme: Theme,
}
#[derive(Debug, Clone)]
enum Message {
ThemeChanged(Theme),
}
impl App {
fn theme(&self) -> Theme {
self.theme.clone()
}
fn update(&mut self, message: Message) {
match message {
Message::ThemeChanged(theme) => self.theme = theme,
};
}
fn view(&self) -> Element<'_, Message> {
pick_list(Theme::ALL, Some(self.theme.clone()), Message::ThemeChanged).into()
}
}
Complete example
main.rs
use iced::{widget::pick_list, Element, Theme};
fn main() -> iced::Result {
iced::application("Themes", App::update, App::view)
.theme(App::theme)
.run()
}
#[derive(Default)]
struct App {
theme: Theme,
}
#[derive(Debug, Clone)]
enum Message {
ThemeChanged(Theme),
}
impl App {
fn theme(&self) -> Theme {
self.theme.clone()
}
fn update(&mut self, message: Message) {
match message {
Message::ThemeChanged(theme) => self.theme = theme,
};
}
fn view(&self) -> Element<'_, Message> {
pick_list(Theme::ALL, Some(self.theme.clone()), Message::ThemeChanged).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
Default
andDefaultStyle
(iced::theme::Base
in later iced versions) for your custom type. - For each widget you plan to support, implement its
Catalog
trait (if it has one) and the dependencies of that trait.
For a reference custom theme, see iced_material
. (This is written for iced 0.14, but the Catalog
traits haven't changed much compared to 0.13)
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("Styling", 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_background)
]
.spacing(10)
.into()
}
}
// This style was backported from 0.14, where it's available built in.
fn button_background(theme: &Theme, status: button::Status) -> button::Style {
let palette = theme.extended_palette();
let base = button::Style {
background: Some(palette.background.base.color.into()),
text_color: palette.background.base.text,
border: border::rounded(2),
..Default::default()
};
match status {
button::Status::Active => base,
button::Status::Pressed => button::Style {
background: Some(palette.background.strong.color.into()),
..base
},
button::Status::Hovered => button::Style {
background: Some(palette.background.weak.color.into()),
..base
},
button::Status::Disabled => button::Style {
background: base
.background
.map(|background| background.scale_alpha(0.5)),
text_color: base.text_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("Styling", 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_background)
]
.spacing(10)
.into()
}
}
// This style was backported from 0.14, where it's available built in.
fn button_background(theme: &Theme, status: button::Status) -> button::Style {
let palette = theme.extended_palette();
let base = button::Style {
background: Some(palette.background.base.color.into()),
text_color: palette.background.base.text,
border: border::rounded(2),
..Default::default()
};
match status {
button::Status::Active => base,
button::Status::Pressed => button::Style {
background: Some(palette.background.strong.color.into()),
..base
},
button::Status::Hovered => button::Style {
background: Some(palette.background.weak.color.into()),
..base
},
button::Status::Disabled => button::Style {
background: base
.background
.map(|background| background.scale_alpha(0.5)),
text_color: base.text_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("Styling", 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_background)
]
.spacing(10)
.into()
}
}
// This style was backported from 0.14, where it's available built in.
fn button_background(theme: &Theme, status: button::Status) -> button::Style {
let palette = theme.extended_palette();
let base = button::Style {
background: Some(palette.background.base.color.into()),
text_color: palette.background.base.text,
border: border::rounded(2),
..Default::default()
};
match status {
button::Status::Active => base,
button::Status::Pressed => button::Style {
background: Some(palette.background.strong.color.into()),
..base
},
button::Status::Hovered => button::Style {
background: Some(palette.background.weak.color.into()),
..base
},
button::Status::Disabled => button::Style {
background: base
.background
.map(|background| background.scale_alpha(0.5)),
text_color: base.text_color.scale_alpha(0.5),
..base
},
}
}
use iced::{
Element, Theme, border, color,
widget::{button, row},
};
fn main() -> iced::Result {
iced::run("Styling", 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_background)
]
.spacing(10)
.into()
}
}
// This style was backported from 0.14, where it's available built in.
fn button_background(theme: &Theme, status: button::Status) -> button::Style {
let palette = theme.extended_palette();
let base = button::Style {
background: Some(palette.background.base.color.into()),
text_color: palette.background.base.text,
border: border::rounded(2),
..Default::default()
};
match status {
button::Status::Active => base,
button::Status::Pressed => button::Style {
background: Some(palette.background.strong.color.into()),
..base
},
button::Status::Hovered => button::Style {
background: Some(palette.background.weak.color.into()),
..base
},
button::Status::Disabled => button::Style {
background: base
.background
.map(|background| background.scale_alpha(0.5)),
text_color: base.text_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("Styling", 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_background)
]
.spacing(10)
.into()
}
}
// This style was backported from 0.14, where it's available built in.
fn button_background(theme: &Theme, status: button::Status) -> button::Style {
let palette = theme.extended_palette();
let base = button::Style {
background: Some(palette.background.base.color.into()),
text_color: palette.background.base.text,
border: border::rounded(2),
..Default::default()
};
match status {
button::Status::Active => base,
button::Status::Pressed => button::Style {
background: Some(palette.background.strong.color.into()),
..base
},
button::Status::Hovered => button::Style {
background: Some(palette.background.weak.color.into()),
..base
},
button::Status::Disabled => button::Style {
background: base
.background
.map(|background| background.scale_alpha(0.5)),
text_color: base.text_color.scale_alpha(0.5),
..base
},
}
}