diff --git a/CHANGELOG.md b/CHANGELOG.md
index 03ffb0cea9..50f479929f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - Gradients in Oklab color space. [#2055](https://github.com/iced-rs/iced/pull/2055)
 - `Themer` widget. [#2209](https://github.com/iced-rs/iced/pull/2209)
 - Cut functionality for `TextEditor`. [#2215](https://github.com/iced-rs/iced/pull/2215)
+- Disabled support for `Checkbox`. [#2109](https://github.com/iced-rs/iced/pull/2109)
 - `skip_taskbar` window setting for Windows. [#2211](https://github.com/iced-rs/iced/pull/2211)
 - `fetch_maximized` and `fetch_minimized` window commands. [#2189](https://github.com/iced-rs/iced/pull/2189)
 - `text_shaping` method for `Tooltip`. [#2172](https://github.com/iced-rs/iced/pull/2172)
@@ -87,6 +88,7 @@ Many thanks to...
 - @alec-deason
 - @arslee07
 - @AustinMReppert
+- @avsaase
 - @bungoboingo
 - @Calastrophe
 - @casperstorm
diff --git a/examples/checkbox/src/main.rs b/examples/checkbox/src/main.rs
index ef1a054d9e..834a8f5c37 100644
--- a/examples/checkbox/src/main.rs
+++ b/examples/checkbox/src/main.rs
@@ -1,6 +1,7 @@
 use iced::executor;
 use iced::font::{self, Font};
-use iced::widget::{checkbox, column, container, text};
+use iced::theme;
+use iced::widget::{checkbox, column, container, row, text};
 use iced::{Application, Command, Element, Length, Settings, Theme};
 
 const ICON_FONT: Font = Font::with_name("icons");
@@ -11,14 +12,16 @@ pub fn main() -> iced::Result {
 
 #[derive(Default)]
 struct Example {
-    default_checkbox: bool,
-    custom_checkbox: bool,
+    default: bool,
+    styled: bool,
+    custom: bool,
 }
 
 #[derive(Debug, Clone, Copy)]
 enum Message {
-    DefaultChecked(bool),
-    CustomChecked(bool),
+    DefaultToggled(bool),
+    CustomToggled(bool),
+    StyledToggled(bool),
     FontLoaded(Result<(), font::Error>),
 }
 
@@ -42,8 +45,15 @@ impl Application for Example {
 
     fn update(&mut self, message: Message) -> Command<Message> {
         match message {
-            Message::DefaultChecked(value) => self.default_checkbox = value,
-            Message::CustomChecked(value) => self.custom_checkbox = value,
+            Message::DefaultToggled(default) => {
+                self.default = default;
+            }
+            Message::StyledToggled(styled) => {
+                self.styled = styled;
+            }
+            Message::CustomToggled(custom) => {
+                self.custom = custom;
+            }
             Message::FontLoaded(_) => (),
         }
 
@@ -51,19 +61,35 @@ impl Application for Example {
     }
 
     fn view(&self) -> Element<Message> {
-        let default_checkbox =
-            checkbox("Default", self.default_checkbox, Message::DefaultChecked);
-        let custom_checkbox =
-            checkbox("Custom", self.custom_checkbox, Message::CustomChecked)
-                .icon(checkbox::Icon {
-                    font: ICON_FONT,
-                    code_point: '\u{e901}',
-                    size: None,
-                    line_height: text::LineHeight::Relative(1.0),
-                    shaping: text::Shaping::Basic,
-                });
+        let default_checkbox = checkbox("Default", self.default)
+            .on_toggle(Message::DefaultToggled);
 
-        let content = column![default_checkbox, custom_checkbox].spacing(22);
+        let styled_checkbox = |label, style| {
+            checkbox(label, self.styled)
+                .on_toggle_maybe(self.default.then_some(Message::StyledToggled))
+                .style(style)
+        };
+
+        let checkboxes = row![
+            styled_checkbox("Primary", theme::Checkbox::Primary),
+            styled_checkbox("Secondary", theme::Checkbox::Secondary),
+            styled_checkbox("Success", theme::Checkbox::Success),
+            styled_checkbox("Danger", theme::Checkbox::Danger),
+        ]
+        .spacing(20);
+
+        let custom_checkbox = checkbox("Custom", self.custom)
+            .on_toggle(Message::CustomToggled)
+            .icon(checkbox::Icon {
+                font: ICON_FONT,
+                code_point: '\u{e901}',
+                size: None,
+                line_height: text::LineHeight::Relative(1.0),
+                shaping: text::Shaping::Basic,
+            });
+
+        let content =
+            column![default_checkbox, checkboxes, custom_checkbox].spacing(20);
 
         container(content)
             .width(Length::Fill)
diff --git a/examples/custom_shader/src/main.rs b/examples/custom_shader/src/main.rs
index 78b2fbbb31..9e8da3baf5 100644
--- a/examples/custom_shader/src/main.rs
+++ b/examples/custom_shader/src/main.rs
@@ -89,11 +89,8 @@ impl Application for IcedCubes {
                     .step(0.01)
                     .width(100),
             ),
-            checkbox(
-                "Show Depth Buffer",
-                self.scene.show_depth_buffer,
-                Message::ShowDepthBuffer
-            ),
+            checkbox("Show Depth Buffer", self.scene.show_depth_buffer)
+                .on_toggle(Message::ShowDepthBuffer),
         ]
         .spacing(40);
 
diff --git a/examples/events/src/main.rs b/examples/events/src/main.rs
index fc51ac4aac..d5d496c7c7 100644
--- a/examples/events/src/main.rs
+++ b/examples/events/src/main.rs
@@ -85,11 +85,8 @@ impl Application for Events {
                 .map(Element::from),
         );
 
-        let toggle = checkbox(
-            "Listen to runtime events",
-            self.enabled,
-            Message::Toggled,
-        );
+        let toggle = checkbox("Listen to runtime events", self.enabled)
+            .on_toggle(Message::Toggled);
 
         let exit = button(
             text("Exit")
diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs
index 56f7afd512..b386b0cd1c 100644
--- a/examples/game_of_life/src/main.rs
+++ b/examples/game_of_life/src/main.rs
@@ -185,7 +185,8 @@ fn view_controls<'a>(
     row![
         playback_controls,
         speed_controls,
-        checkbox("Grid", is_grid_enabled, Message::ToggleGrid)
+        checkbox("Grid", is_grid_enabled)
+            .on_toggle(Message::ToggleGrid)
             .size(16)
             .spacing(5)
             .text_size(16),
diff --git a/examples/layout/src/main.rs b/examples/layout/src/main.rs
index 6cf0e57032..b626c70dd6 100644
--- a/examples/layout/src/main.rs
+++ b/examples/layout/src/main.rs
@@ -86,7 +86,8 @@ impl Application for Layout {
         let header = row![
             text(self.example.title).size(20).font(Font::MONOSPACE),
             horizontal_space(Length::Fill),
-            checkbox("Explain", self.explain, Message::ExplainToggled),
+            checkbox("Explain", self.explain)
+                .on_toggle(Message::ExplainToggled),
             pick_list(
                 Theme::ALL,
                 Some(self.theme.clone()),
diff --git a/examples/styling/src/main.rs b/examples/styling/src/main.rs
index 10f3c79dc5..4718a12333 100644
--- a/examples/styling/src/main.rs
+++ b/examples/styling/src/main.rs
@@ -115,11 +115,8 @@ impl Sandbox for Styling {
         .width(Length::Fill)
         .height(100);
 
-        let checkbox = checkbox(
-            "Check me!",
-            self.checkbox_value,
-            Message::CheckboxToggled,
-        );
+        let checkbox = checkbox("Check me!", self.checkbox_value)
+            .on_toggle(Message::CheckboxToggled);
 
         let toggler = toggler(
             String::from("Toggle me!"),
diff --git a/examples/svg/src/main.rs b/examples/svg/src/main.rs
index 3bf4960f8f..ba93007ca8 100644
--- a/examples/svg/src/main.rs
+++ b/examples/svg/src/main.rs
@@ -51,11 +51,9 @@ impl Sandbox for Tiger {
             },
         );
 
-        let apply_color_filter = checkbox(
-            "Apply a color filter",
-            self.apply_color_filter,
-            Message::ToggleColorFilter,
-        );
+        let apply_color_filter =
+            checkbox("Apply a color filter", self.apply_color_filter)
+                .on_toggle(Message::ToggleColorFilter);
 
         container(
             column![
diff --git a/examples/todos/src/main.rs b/examples/todos/src/main.rs
index 3d79f08789..88c4c2b481 100644
--- a/examples/todos/src/main.rs
+++ b/examples/todos/src/main.rs
@@ -352,13 +352,10 @@ impl Task {
     fn view(&self, i: usize) -> Element<TaskMessage> {
         match &self.state {
             TaskState::Idle => {
-                let checkbox = checkbox(
-                    &self.description,
-                    self.completed,
-                    TaskMessage::Completed,
-                )
-                .width(Length::Fill)
-                .text_shaping(text::Shaping::Advanced);
+                let checkbox = checkbox(&self.description, self.completed)
+                    .on_toggle(TaskMessage::Completed)
+                    .width(Length::Fill)
+                    .text_shaping(text::Shaping::Advanced);
 
                 row![
                     checkbox,
diff --git a/examples/tour/src/main.rs b/examples/tour/src/main.rs
index b656289a9b..6d24b5ec9c 100644
--- a/examples/tour/src/main.rs
+++ b/examples/tour/src/main.rs
@@ -554,11 +554,13 @@ impl<'a> Step {
                     .width(Length::Fill)
                     .horizontal_alignment(alignment::Horizontal::Center),
             )
-            .push(checkbox(
-                "Use nearest interpolation",
-                filter_method == image::FilterMethod::Nearest,
-                StepMessage::ImageUseNearestToggled,
-            ))
+            .push(
+                checkbox(
+                    "Use nearest interpolation",
+                    filter_method == image::FilterMethod::Nearest,
+                )
+                .on_toggle(StepMessage::ImageUseNearestToggled),
+            )
             .align_items(Alignment::Center)
     }
 
@@ -616,16 +618,14 @@ impl<'a> Step {
             } else {
                 text_input
             })
-            .push(checkbox(
-                "Enable password mode",
-                is_secure,
-                StepMessage::ToggleSecureInput,
-            ))
-            .push(checkbox(
-                "Show icon",
-                is_showing_icon,
-                StepMessage::ToggleTextInputIcon,
-            ))
+            .push(
+                checkbox("Enable password mode", is_secure)
+                    .on_toggle(StepMessage::ToggleSecureInput),
+            )
+            .push(
+                checkbox("Show icon", is_showing_icon)
+                    .on_toggle(StepMessage::ToggleTextInputIcon),
+            )
             .push(
                 "A text input produces a message every time it changes. It is \
                  very easy to keep track of its contents:",
@@ -658,7 +658,8 @@ impl<'a> Step {
                         .horizontal_alignment(alignment::Horizontal::Center),
                 )
             } else {
-                checkbox("Explain layout", debug, StepMessage::DebugToggled)
+                checkbox("Explain layout", debug)
+                    .on_toggle(StepMessage::DebugToggled)
                     .into()
             })
             .push("Feel free to go back and take a look.")
diff --git a/examples/vectorial_text/src/main.rs b/examples/vectorial_text/src/main.rs
index d366b9072c..c2212b22ca 100644
--- a/examples/vectorial_text/src/main.rs
+++ b/examples/vectorial_text/src/main.rs
@@ -75,11 +75,8 @@ impl Sandbox for VectorialText {
         column![
             canvas(&self.state).width(Length::Fill).height(Length::Fill),
             column![
-                checkbox(
-                    "Use Japanese",
-                    self.state.use_japanese,
-                    Message::ToggleJapanese
-                ),
+                checkbox("Use Japanese", self.state.use_japanese,)
+                    .on_toggle(Message::ToggleJapanese),
                 row![
                     slider_with_label(
                         "Size",
diff --git a/style/src/checkbox.rs b/style/src/checkbox.rs
index d96ea4adb9..82c1766fa9 100644
--- a/style/src/checkbox.rs
+++ b/style/src/checkbox.rs
@@ -24,4 +24,7 @@ pub trait StyleSheet {
 
     /// Produces the hovered [`Appearance`] of a checkbox.
     fn hovered(&self, style: &Self::Style, is_checked: bool) -> Appearance;
+
+    /// Produces the disabled [`Appearance`] of a checkbox.
+    fn disabled(&self, style: &Self::Style, is_checked: bool) -> Appearance;
 }
diff --git a/style/src/theme.rs b/style/src/theme.rs
index 8d1ff23779..166410ae71 100644
--- a/style/src/theme.rs
+++ b/style/src/theme.rs
@@ -319,7 +319,7 @@ impl checkbox::StyleSheet for Theme {
             Checkbox::Secondary => checkbox_appearance(
                 palette.background.base.text,
                 palette.background.base,
-                palette.background.base,
+                palette.background.strong,
                 is_checked,
             ),
             Checkbox::Success => checkbox_appearance(
@@ -355,7 +355,7 @@ impl checkbox::StyleSheet for Theme {
             Checkbox::Secondary => checkbox_appearance(
                 palette.background.base.text,
                 palette.background.weak,
-                palette.background.base,
+                palette.background.strong,
                 is_checked,
             ),
             Checkbox::Success => checkbox_appearance(
@@ -373,6 +373,42 @@ impl checkbox::StyleSheet for Theme {
             Checkbox::Custom(custom) => custom.hovered(self, is_checked),
         }
     }
+
+    fn disabled(
+        &self,
+        style: &Self::Style,
+        is_checked: bool,
+    ) -> checkbox::Appearance {
+        let palette = self.extended_palette();
+
+        match style {
+            Checkbox::Primary => checkbox_appearance(
+                palette.primary.strong.text,
+                palette.background.weak,
+                palette.background.strong,
+                is_checked,
+            ),
+            Checkbox::Secondary => checkbox_appearance(
+                palette.background.strong.color,
+                palette.background.weak,
+                palette.background.weak,
+                is_checked,
+            ),
+            Checkbox::Success => checkbox_appearance(
+                palette.success.base.text,
+                palette.background.weak,
+                palette.success.weak,
+                is_checked,
+            ),
+            Checkbox::Danger => checkbox_appearance(
+                palette.danger.base.text,
+                palette.background.weak,
+                palette.danger.weak,
+                is_checked,
+            ),
+            Checkbox::Custom(custom) => custom.active(self, is_checked),
+        }
+    }
 }
 
 fn checkbox_appearance(
diff --git a/widget/src/checkbox.rs b/widget/src/checkbox.rs
index 6f559ccc72..0ff4d58b51 100644
--- a/widget/src/checkbox.rs
+++ b/widget/src/checkbox.rs
@@ -28,7 +28,7 @@ pub use crate::style::checkbox::{Appearance, StyleSheet};
 ///
 /// let is_checked = true;
 ///
-/// Checkbox::new("Toggle me!", is_checked, Message::CheckboxToggled);
+/// Checkbox::new("Toggle me!", is_checked).on_toggle(Message::CheckboxToggled);
 /// ```
 ///
 /// ![Checkbox drawn by `iced_wgpu`](https://github.com/iced-rs/iced/blob/7760618fb112074bc40b148944521f312152012a/docs/images/checkbox.png?raw=true)
@@ -43,7 +43,7 @@ pub struct Checkbox<
     Renderer: text::Renderer,
 {
     is_checked: bool,
-    on_toggle: Box<dyn Fn(bool) -> Message + 'a>,
+    on_toggle: Option<Box<dyn Fn(bool) -> Message + 'a>>,
     label: String,
     width: Length,
     size: f32,
@@ -70,18 +70,12 @@ where
     /// Creates a new [`Checkbox`].
     ///
     /// It expects:
-    ///   * a boolean describing whether the [`Checkbox`] is checked or not
     ///   * the label of the [`Checkbox`]
-    ///   * a function that will be called when the [`Checkbox`] is toggled. It
-    ///     will receive the new state of the [`Checkbox`] and must produce a
-    ///     `Message`.
-    pub fn new<F>(label: impl Into<String>, is_checked: bool, f: F) -> Self
-    where
-        F: 'a + Fn(bool) -> Message,
-    {
+    ///   * a boolean describing whether the [`Checkbox`] is checked or not
+    pub fn new(label: impl Into<String>, is_checked: bool) -> Self {
         Checkbox {
             is_checked,
-            on_toggle: Box::new(f),
+            on_toggle: None,
             label: label.into(),
             width: Length::Shrink,
             size: Self::DEFAULT_SIZE,
@@ -101,6 +95,31 @@ where
         }
     }
 
+    /// Sets the function that will be called when the [`Checkbox`] is toggled.
+    /// It will receive the new state of the [`Checkbox`] and must produce a
+    /// `Message`.
+    ///
+    /// Unless `on_toggle` is called, the [`Checkbox`] will be disabled.
+    pub fn on_toggle<F>(mut self, f: F) -> Self
+    where
+        F: 'a + Fn(bool) -> Message,
+    {
+        self.on_toggle = Some(Box::new(f));
+        self
+    }
+
+    /// Sets the function that will be called when the [`Checkbox`] is toggled,
+    /// if `Some`.
+    ///
+    /// If `None`, the checkbox will be disabled.
+    pub fn on_toggle_maybe<F>(mut self, f: Option<F>) -> Self
+    where
+        F: Fn(bool) -> Message + 'a,
+    {
+        self.on_toggle = f.map(|f| Box::new(f) as _);
+        self
+    }
+
     /// Sets the size of the [`Checkbox`].
     pub fn size(mut self, size: impl Into<Pixels>) -> Self {
         self.size = size.into().0;
@@ -235,9 +254,10 @@ where
                 let mouse_over = cursor.is_over(layout.bounds());
 
                 if mouse_over {
-                    shell.publish((self.on_toggle)(!self.is_checked));
-
-                    return event::Status::Captured;
+                    if let Some(on_toggle) = &self.on_toggle {
+                        shell.publish((on_toggle)(!self.is_checked));
+                        return event::Status::Captured;
+                    }
                 }
             }
             _ => {}
@@ -254,7 +274,7 @@ where
         _viewport: &Rectangle,
         _renderer: &Renderer,
     ) -> mouse::Interaction {
-        if cursor.is_over(layout.bounds()) {
+        if cursor.is_over(layout.bounds()) && self.on_toggle.is_some() {
             mouse::Interaction::Pointer
         } else {
             mouse::Interaction::default()
@@ -272,10 +292,13 @@ where
         viewport: &Rectangle,
     ) {
         let is_mouse_over = cursor.is_over(layout.bounds());
+        let is_disabled = self.on_toggle.is_none();
 
         let mut children = layout.children();
 
-        let custom_style = if is_mouse_over {
+        let custom_style = if is_disabled {
+            theme.disabled(&self.style, self.is_checked)
+        } else if is_mouse_over {
             theme.hovered(&self.style, self.is_checked)
         } else {
             theme.active(&self.style, self.is_checked)
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index 444eb4c2ea..e13e900a33 100644
--- a/widget/src/helpers.rs
+++ b/widget/src/helpers.rs
@@ -158,13 +158,12 @@ where
 pub fn checkbox<'a, Message, Theme, Renderer>(
     label: impl Into<String>,
     is_checked: bool,
-    f: impl Fn(bool) -> Message + 'a,
 ) -> Checkbox<'a, Message, Theme, Renderer>
 where
     Theme: checkbox::StyleSheet + text::StyleSheet,
     Renderer: core::text::Renderer,
 {
-    Checkbox::new(label, is_checked, f)
+    Checkbox::new(label, is_checked)
 }
 
 /// Creates a new [`Radio`].