Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 and DefaultStyle (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
        },
    }
}