Repository: zng-ui/zng (language: rust)

Issue:
Add some color to the default styles
We have a very simple default style, maybe add accent color or some sort of color palette context that all styles derives from.


Discussion / hints:
Maybe add color palettes to enable "success", "warning", "error" buttons.

Fix patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 00a2383e6..bfcf6dfb0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,6 +17,7 @@ env:
   RUSTDOCFLAGS: '--deny=warnings'
   CARGO_TERM_COLOR: always
   ZNG_TP_LICENSES: false
+  NEXTEST_RETRIES: 3
 
 jobs:
   check:
@@ -99,7 +100,6 @@ jobs:
     needs: [check-windows]
     env:
       ZNG_TP_LICENSES: true
-      NEXTEST_RETRIES: 3
       CC: 'clang-cl'
       CXX: 'clang-cl'
     steps:
@@ -124,7 +124,6 @@ jobs:
     needs: [check-macos]
     env:
       ZNG_TP_LICENSES: true
-      NEXTEST_RETRIES: 3
     steps:
     - uses: dtolnay/rust-toolchain@stable
     - name: install cargo-about
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index be2098674..4e74b00cd 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -34,6 +34,7 @@ env:
   RUSTDOCFLAGS: '--deny=warnings'
   CARGO_TERM_COLOR: always
   ZNG_TP_LICENSES: false
+  NEXTEST_RETRIES: 3
 
 jobs:
   check-ubuntu:
@@ -391,7 +392,6 @@ jobs:
     env:
       CC: 'clang-cl'
       CXX: 'clang-cl'
-      NEXTEST_RETRIES: 3
     steps:
       - name: fix clang version issue
         run: choco upgrade llvm
@@ -440,8 +440,6 @@ jobs:
             target: aarch64-apple-darwin
     runs-on: ${{ matrix.os }}
     needs: [prebuild-macos]
-    env:
-      NEXTEST_RETRIES: 3
     steps:
       - name: install cargo-nextest
         if: ${{ github.event.inputs.skip_tests != 'true' }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6ec71bf78..e1d4aa70c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,7 @@
 # Unreleased
 
+* Add `zng::button::PrimaryStyle`.
+* **Breaking** View API now groups color scheme with a new accent color config.
 * **Breaking** Refactored "color pair".
     - Now named `LightDark`.
     - Removed helper methods for declaring vars, now `IntoVar<Rgba> for LightDark` is contextual.
diff --git a/crates/zng-app/src/running.rs b/crates/zng-app/src/running.rs
index 9f211535c..7df920912 100644
--- a/crates/zng-app/src/running.rs
+++ b/crates/zng-app/src/running.rs
@@ -308,10 +308,6 @@ impl<E: AppExtension> RunningApp<E> {
                 let args = RawMonitorsChangedArgs::now(monitors);
                 self.notify_event(RAW_MONITORS_CHANGED_EVENT.new_update(args), observer);
             }
-            Event::ColorSchemeChanged(w_id, scheme) => {
-                let args = RawColorSchemeChangedArgs::now(window_id(w_id), scheme);
-                self.notify_event(RAW_COLOR_SCHEME_CHANGED_EVENT.new_update(args), observer);
-            }
             Event::WindowCloseRequested(w_id) => {
                 let args = RawWindowCloseRequestedArgs::now(window_id(w_id));
                 self.notify_event(RAW_WINDOW_CLOSE_REQUESTED_EVENT.new_update(args), observer);
@@ -449,6 +445,10 @@ impl<E: AppExtension> RunningApp<E> {
                 let args = RawLocaleChangedArgs::now(cfg);
                 self.notify_event(RAW_LOCALE_CONFIG_CHANGED_EVENT.new_update(args), observer);
             }
+            Event::ColorsConfigChanged(cfg) => {
+                let args = RawColorsConfigChangedArgs::now(cfg);
+                self.notify_event(RAW_COLORS_CONFIG_CHANGED_EVENT.new_update(args), observer);
+            }
 
             // `device_events`
             Event::DeviceAdded(d_id) => {
@@ -562,7 +562,7 @@ impl<E: AppExtension> RunningApp<E> {
                     font_aa,
                     animations_config,
                     locale_config,
-                    color_scheme,
+                    colors_config,
                     extensions,
                 }) => {
                     // notify immediately.
@@ -589,7 +589,7 @@ impl<E: AppExtension> RunningApp<E> {
                         font_aa,
                         animations_config,
                         locale_config,
-                        color_scheme,
+                        colors_config,
                         extensions,
                     );
                     self.notify_event(VIEW_PROCESS_INITED_EVENT.new_update(args), observer);
diff --git a/crates/zng-app/src/view_process.rs b/crates/zng-app/src/view_process.rs
index 4651db1c3..7a9a57791 100644
--- a/crates/zng-app/src/view_process.rs
+++ b/crates/zng-app/src/view_process.rs
@@ -24,7 +24,7 @@ use zng_var::ResponderVar;
 use zng_view_api::{
     self,
     api_extension::{ApiExtensionId, ApiExtensionName, ApiExtensionPayload, ApiExtensionRecvError, ApiExtensions},
-    config::{AnimationsConfig, ColorScheme, FontAntiAliasing, LocaleConfig, MultiClickConfig, TouchConfig},
+    config::{AnimationsConfig, ColorsConfig, FontAntiAliasing, LocaleConfig, MultiClickConfig, TouchConfig},
     dialog::{FileDialog, FileDialogResponse, MsgDialog, MsgDialogResponse},
     font::FontOptions,
     image::{ImageMaskMode, ImagePpi, ImageRequest, ImageTextureId},
@@ -585,13 +585,8 @@ event_args! {
         /// System locale config.
         pub locale_config: LocaleConfig,
 
-        /// System preferred color scheme.
-        ///
-        /// Updates of this preference can be received in [`RAW_WINDOW_OPEN_EVENT`] and [`RAW_COLOR_SCHEME_CHANGED_EVENT`].
-        ///
-        /// [`RAW_WINDOW_OPEN_EVENT`]: crate::view_process::raw_events::RAW_WINDOW_OPEN_EVENT
-        /// [`RAW_COLOR_SCHEME_CHANGED_EVENT`]: crate::view_process::raw_events::RAW_COLOR_SCHEME_CHANGED_EVENT
-        pub color_scheme: ColorScheme,
+        /// System preferred color scheme and accent color.
+        pub colors_config: ColorsConfig,
 
         /// API extensions implemented by the view-process.
         ///
@@ -633,9 +628,6 @@ pub struct WindowOpenData {
 
     /// Actual render mode, can be different from the requested mode if it is not available.
     pub render_mode: RenderMode,
-
-    /// Preferred color scheme.
-    pub color_scheme: ColorScheme,
 }
 impl WindowOpenData {
     pub(crate) fn new(data: zng_view_api::window::WindowOpenData, map_monitor: impl FnOnce(ApiMonitorId) -> MonitorId) -> Self {
@@ -646,7 +638,6 @@ impl WindowOpenData {
             size: data.size,
             scale_factor: data.scale_factor,
             render_mode: data.render_mode,
-            color_scheme: data.color_scheme,
         }
     }
 }
diff --git a/crates/zng-app/src/view_process/raw_events.rs b/crates/zng-app/src/view_process/raw_events.rs
index f7c9a32e3..9a63371f8 100644
--- a/crates/zng-app/src/view_process/raw_events.rs
+++ b/crates/zng-app/src/view_process/raw_events.rs
@@ -20,7 +20,7 @@ use zng_layout::unit::{DipPoint, DipSize, Factor, PxPoint, PxRect};
 use zng_txt::Txt;
 use zng_view_api::{
     api_extension::{ApiExtensionId, ApiExtensionPayload},
-    config::{AnimationsConfig, ColorScheme, FontAntiAliasing, KeyRepeatConfig, LocaleConfig, MultiClickConfig, TouchConfig},
+    config::{AnimationsConfig, ColorsConfig, FontAntiAliasing, KeyRepeatConfig, LocaleConfig, MultiClickConfig, TouchConfig},
     keyboard::{Key, KeyCode, KeyLocation, KeyState},
     mouse::{ButtonState, MouseButton, MouseScrollDelta},
     touch::{TouchPhase, TouchUpdate},
@@ -481,22 +481,6 @@ event_args! {
         }
     }
 
-    /// Arguments for the [`RAW_COLOR_SCHEME_CHANGED_EVENT`].
-    pub struct RawColorSchemeChangedArgs {
-        /// Window for which the preference was changed.
-        pub window_id: WindowId,
-
-        /// New preference.
-        pub color_scheme: ColorScheme,
-
-        ..
-
-        /// Broadcast to all widgets.
-        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
-           list.search_all();
-        }
-    }
-
     /// Arguments for the image events.
     pub struct RawImageArgs {
         /// Image that changed.
@@ -620,6 +604,19 @@ event_args! {
         }
     }
 
+    /// Arguments for the [`RAW_COLORS_CONFIG_CHANGED_EVENT`].
+    pub struct RawColorsConfigChangedArgs {
+        /// New config.
+        pub config: ColorsConfig,
+
+        ..
+
+        /// Broadcast to all widgets.
+        fn delivery_list(&self, list: &mut UpdateDeliveryList) {
+           list.search_all();
+        }
+    }
+
     /// Arguments for the [`RAW_EXTENSION_EVENT`].
     pub struct RawExtensionEventArgs {
         /// Id of the sender extension.
@@ -733,8 +730,8 @@ event! {
     /// Monitors added, removed or modified.
     pub static RAW_MONITORS_CHANGED_EVENT: RawMonitorsChangedArgs;
 
-    /// Color scheme preference changed for a window.
-    pub static RAW_COLOR_SCHEME_CHANGED_EVENT: RawColorSchemeChangedArgs;
+    /// Color scheme or accent color preference changed for a window.
+    pub static RAW_COLORS_CONFIG_CHANGED_EVENT: RawColorsConfigChangedArgs;
 
     /// Change in system font anti-aliasing config.
     pub static RAW_FONT_AA_CHANGED_EVENT: RawFontAaChangedArgs;
diff --git a/crates/zng-color/src/lib.rs b/crates/zng-color/src/lib.rs
index 89dee28cd..e108761b0 100644
--- a/crates/zng-color/src/lib.rs
+++ b/crates/zng-color/src/lib.rs
@@ -857,13 +857,13 @@ pub struct LightDark {
     pub light: Rgba,
 }
 impl_from_and_into_var! {
-    // /// From `(light, dark)` tuple. !!: TODO
-    // fn from<L: Into<Rgba>, D: Into<Rgba>>((light, dark): (L, D)) -> LightDark {
-    //     LightDark {
-    //         light: light.into(),
-    //         dark: dark.into(),
-    //     }
-    // }
+    /// From `(light, dark)` tuple.
+    fn from<L: Into<Rgba>, D: Into<Rgba>>((light, dark): (L, D)) -> LightDark {
+        LightDark {
+            light: light.into(),
+            dark: dark.into(),
+        }
+    }
 
     /// From same color to both.
     fn from(color: Rgba) -> LightDark {
@@ -879,6 +879,8 @@ impl_from_and_into_var! {
     fn from(color: Hsla) -> LightDark {
         Rgba::from(color).into()
     }
+
+    fn from(color: LightDark) -> Option<LightDark>;
 }
 impl IntoVar<Rgba> for LightDark {
     type Var = ContextualizedVar<Rgba>;
diff --git a/crates/zng-ext-window/src/control.rs b/crates/zng-ext-window/src/control.rs
index f9959f356..49cecdc31 100644
--- a/crates/zng-ext-window/src/control.rs
+++ b/crates/zng-ext-window/src/control.rs
@@ -11,7 +11,7 @@ use zng_app::{
     update::{EventUpdate, InfoUpdates, LayoutUpdates, RenderUpdates, WidgetUpdates, UPDATES},
     view_process::{
         raw_events::{
-            RawWindowFocusArgs, RAW_COLOR_SCHEME_CHANGED_EVENT, RAW_FRAME_RENDERED_EVENT, RAW_HEADLESS_OPEN_EVENT, RAW_IME_EVENT,
+            RawWindowFocusArgs, RAW_COLORS_CONFIG_CHANGED_EVENT, RAW_FRAME_RENDERED_EVENT, RAW_HEADLESS_OPEN_EVENT, RAW_IME_EVENT,
             RAW_WINDOW_CHANGED_EVENT, RAW_WINDOW_FOCUS_EVENT, RAW_WINDOW_OPEN_EVENT, RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT,
         },
         ViewHeadless, ViewRenderer, ViewWindow, VIEW_PROCESS, VIEW_PROCESS_INITED_EVENT,
@@ -25,7 +25,7 @@ use zng_app::{
     Deadline,
 };
 use zng_clone_move::clmv;
-use zng_color::{colors, Rgba};
+use zng_color::{colors, LightDark, Rgba};
 use zng_ext_image::{ImageRenderArgs, ImageSource, ImageVar, Img, IMAGES};
 use zng_layout::{
     context::{LayoutMetrics, LayoutPassId, DIRECTION_VAR, LAYOUT},
@@ -99,8 +99,8 @@ struct HeadedCtrl {
     resize_wait_id: Option<FrameWaitId>,
     img_res: ImageResources,
     actual_state: Option<WindowState>, // for WindowChangedEvent
-    system_color_scheme: Option<ColorScheme>,
     parent_color_scheme: Option<ReadOnlyArcVar<ColorScheme>>,
+    parent_accent_color: Option<ReadOnlyArcVar<LightDark>>,
     actual_parent: Option<WindowId>,
     root_font_size: Dip,
     render_access_update: Option<WidgetInfoTree>, // previous info tree
@@ -130,8 +130,8 @@ impl HeadedCtrl {
             monitor: None,
             resize_wait_id: None,
             img_res: ImageResources::default(),
-            system_color_scheme: None,
             parent_color_scheme: None,
+            parent_accent_color: None,
             actual_parent: None,
             actual_state: None,
             root_font_size: Dip::from_px(Length::pt_to_px(11.0, 1.fct()), 1.fct()),
@@ -518,28 +518,36 @@ impl HeadedCtrl {
             // else indicator is send with init.
         }
 
-        let mut update_color_scheme = false;
+        let mut update_colors = false;
 
         if update_parent(&mut self.actual_parent, &self.vars) {
             self.parent_color_scheme = self
                 .actual_parent
                 .and_then(|id| WINDOWS.vars(id).ok().map(|v| v.actual_color_scheme()));
-            update_color_scheme = true;
+            self.parent_accent_color = self
+                .actual_parent
+                .and_then(|id| WINDOWS.vars(id).ok().map(|v| v.actual_accent_color()));
+            update_colors = true;
         }
 
-        if update_color_scheme
-            || self.vars.color_scheme().is_new()
-            || self.parent_color_scheme.as_ref().map(|t| t.is_new()).unwrap_or(false)
-        {
+        if update_colors || self.vars.color_scheme().is_new() || self.parent_color_scheme.as_ref().map(|t| t.is_new()).unwrap_or(false) {
             let scheme = self
                 .vars
                 .color_scheme()
                 .get()
                 .or_else(|| self.parent_color_scheme.as_ref().map(|t| t.get()))
-                .or(self.system_color_scheme)
-                .unwrap_or_default();
+                .unwrap_or_else(|| WINDOWS.system_colors_config().scheme);
             self.vars.0.actual_color_scheme.set(scheme);
         }
+        if update_colors || self.vars.accent_color().is_new() || self.parent_accent_color.as_ref().map(|t| t.is_new()).unwrap_or(false) {
+            let accent = self
+                .vars
+                .accent_color()
+                .get()
+                .or_else(|| self.parent_accent_color.as_ref().map(|t| t.get()))
+                .unwrap_or_else(|| WINDOWS.system_colors_config().accent.into());
+            self.vars.0.actual_accent_color.set(accent);
+        }
 
         if self.vars.0.access_enabled.is_new() {
             UPDATES.update_info_window(WINDOW.id());
@@ -712,16 +720,21 @@ impl HeadedCtrl {
                 self.vars.0.scale_factor.set(args.data.scale_factor);
 
                 self.state = Some(args.data.state.clone());
-                self.system_color_scheme = Some(args.data.color_scheme);
 
                 let scheme = self
                     .vars
                     .color_scheme()
                     .get()
                     .or_else(|| self.parent_color_scheme.as_ref().map(|t| t.get()))
-                    .or(self.system_color_scheme)
-                    .unwrap_or_default();
+                    .unwrap_or_else(|| WINDOWS.system_colors_config().scheme);
                 self.vars.0.actual_color_scheme.set(scheme);
+                let accent = self
+                    .vars
+                    .accent_color()
+                    .get()
+                    .or_else(|| self.parent_accent_color.as_ref().map(|t| t.get()))
+                    .unwrap_or_else(|| WINDOWS.system_colors_config().accent.into());
+                self.vars.0.actual_accent_color.set(accent);
 
                 UPDATES.layout_window(args.window_id).render_window(args.window_id);
 
@@ -729,19 +742,21 @@ impl HeadedCtrl {
                     update(&args.window);
                 }
             }
-        } else if let Some(args) = RAW_COLOR_SCHEME_CHANGED_EVENT.on(update) {
-            if args.window_id == WINDOW.id() {
-                self.system_color_scheme = Some(args.color_scheme);
-
-                let scheme = self
-                    .vars
-                    .color_scheme()
-                    .get()
-                    .or_else(|| self.parent_color_scheme.as_ref().map(|t| t.get()))
-                    .or(self.system_color_scheme)
-                    .unwrap_or_default();
-                self.vars.0.actual_color_scheme.set(scheme);
-            }
+        } else if let Some(args) = RAW_COLORS_CONFIG_CHANGED_EVENT.on(update) {
+            let scheme = self
+                .vars
+                .color_scheme()
+                .get()
+                .or_else(|| self.parent_color_scheme.as_ref().map(|t| t.get()))
+                .unwrap_or(args.config.scheme);
+            self.vars.0.actual_color_scheme.set(scheme);
+            let color = self
+                .vars
+                .accent_color()
+                .get()
+                .or_else(|| self.parent_accent_color.as_ref().map(|t| t.get()))
+                .unwrap_or_else(|| args.config.accent.into());
+            self.vars.0.actual_accent_color.set(color);
         } else if let Some(args) = RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT.on(update) {
             let w_id = WINDOW.id();
             if args.window_id == w_id && self.window.is_none() && self.waiting_view {
diff --git a/crates/zng-ext-window/src/service.rs b/crates/zng-ext-window/src/service.rs
index f16b6c90b..0c32bb288 100644
--- a/crates/zng-ext-window/src/service.rs
+++ b/crates/zng-ext-window/src/service.rs
@@ -9,8 +9,8 @@ use zng_app::{
     view_process::{
         self,
         raw_events::{
-            RAW_COLOR_SCHEME_CHANGED_EVENT, RAW_IMAGE_LOADED_EVENT, RAW_IMAGE_LOAD_ERROR_EVENT, RAW_WINDOW_CLOSE_EVENT,
-            RAW_WINDOW_CLOSE_REQUESTED_EVENT, RAW_WINDOW_FOCUS_EVENT, RAW_WINDOW_OPEN_EVENT,
+            RAW_COLORS_CONFIG_CHANGED_EVENT, RAW_IMAGE_LOADED_EVENT, RAW_IMAGE_LOAD_ERROR_EVENT, RAW_WINDOW_CLOSE_EVENT,
+            RAW_WINDOW_CLOSE_REQUESTED_EVENT, RAW_WINDOW_FOCUS_EVENT,
         },
         ViewImage, ViewRenderer, ViewWindowOrHeadless, VIEW_PROCESS, VIEW_PROCESS_INITED_EVENT,
     },
@@ -24,6 +24,7 @@ use zng_app::{
 };
 use zng_app_context::app_local;
 
+use zng_color::{colors::ACCENT_COLOR_VAR, COLOR_SCHEME_VAR};
 use zng_ext_image::{ImageRenderWindowRoot, ImageRenderWindowsService, ImageVar, Img};
 use zng_layout::unit::TimeUnits as _;
 use zng_layout::unit::{Factor, FactorUnits, LengthUnits, PxRect};
@@ -39,11 +40,12 @@ use zng_var::{
 };
 use zng_view_api::{
     api_extension::{ApiExtensionId, ApiExtensionPayload},
-    config::ColorScheme,
+    config::ColorsConfig,
     image::ImageMaskMode,
     window::{RenderMode, WindowState},
     ViewProcessOffline,
 };
+use zng_wgt::node::with_context_var;
 
 use crate::{
     cmd::WindowCommands, control::WindowCtrl, CloseWindowResult, FrameCaptureMode, HeadlessMonitor, StartPosition, ViewExtensionError,
@@ -79,7 +81,7 @@ pub(super) struct WindowsService {
     frame_images: Vec<WeakArcVar<Img>>,
 
     loading_deadline: Option<DeadlineHandle>,
-    latest_color_scheme: ColorScheme,
+    latest_colors_cfg: ColorsConfig,
 
     view_window_tasks: Vec<ViewWindowTask>,
 }
@@ -102,7 +104,7 @@ impl WindowsService {
             bring_to_top_requests: vec![],
             frame_images: vec![],
             loading_deadline: None,
-            latest_color_scheme: ColorScheme::Dark,
+            latest_colors_cfg: ColorsConfig::default(),
             view_window_tasks: vec![],
         }
     }
@@ -714,6 +716,10 @@ impl WINDOWS {
         }
     }
 
+    pub(super) fn system_colors_config(&self) -> ColorsConfig {
+        WINDOWS_SV.read().latest_colors_cfg
+    }
+
     pub(super) fn take_view_extensions_init(&self, id: WindowId) -> Vec<(ApiExtensionId, ApiExtensionPayload)> {
         std::mem::take(&mut WINDOWS_SV.write().windows_info.get_mut(&id).unwrap().extensions)
     }
@@ -916,12 +922,10 @@ impl WINDOWS {
                 let args = WindowCloseArgs::new(args.timestamp, args.propagation().clone(), windows);
                 WINDOW_CLOSE_EVENT.notify(args);
             }
-        } else if let Some(args) = RAW_WINDOW_OPEN_EVENT.on(update) {
-            WINDOWS_SV.write().latest_color_scheme = args.data.color_scheme;
-        } else if let Some(args) = RAW_COLOR_SCHEME_CHANGED_EVENT.on(update) {
-            WINDOWS_SV.write().latest_color_scheme = args.color_scheme;
+        } else if let Some(args) = RAW_COLORS_CONFIG_CHANGED_EVENT.on(update) {
+            WINDOWS_SV.write().latest_colors_cfg = args.config;
         } else if let Some(args) = VIEW_PROCESS_INITED_EVENT.on(update) {
-            WINDOWS_SV.write().latest_color_scheme = args.color_scheme;
+            WINDOWS_SV.write().latest_colors_cfg = args.colors_config;
 
             // we skipped request fulfillment until this event.
             UPDATES.update(None);
@@ -1077,9 +1081,9 @@ impl WINDOWS {
             return;
         }
 
-        let ((open, mut open_tasks, close, focus, bring_to_top, view_tasks), color_scheme) = {
+        let ((open, mut open_tasks, close, focus, bring_to_top, view_tasks), colors_cfg) = {
             let mut wns = WINDOWS_SV.write();
-            (wns.take_requests(), wns.latest_color_scheme)
+            (wns.take_requests(), wns.latest_colors_cfg)
         };
 
         let window_mode = zng_app::APP.window_mode();
@@ -1094,12 +1098,12 @@ impl WINDOWS {
                 (mode, _) => mode,
             };
 
-            let color_scheme = match window_mode {
-                WindowMode::Headed => color_scheme,
-                WindowMode::Headless | WindowMode::HeadlessWithRenderer => ColorScheme::default(),
+            let colors_cfg = match window_mode {
+                WindowMode::Headed => colors_cfg,
+                WindowMode::Headless | WindowMode::HeadlessWithRenderer => ColorsConfig::default(),
             };
 
-            let task = AppWindowTask::new(r.id, window_mode, color_scheme, r.new.into_inner(), r.responder);
+            let task = AppWindowTask::new(r.id, window_mode, colors_cfg, r.new.into_inner(), r.responder);
             open_tasks.push(task);
         }
 
@@ -1427,7 +1431,7 @@ struct AppWindowTask {
     responder: ResponderVar<WindowId>,
 }
 impl AppWindowTask {
-    fn new(id: WindowId, mode: WindowMode, color_scheme: ColorScheme, new: UiTask<WindowRoot>, responder: ResponderVar<WindowId>) -> Self {
+    fn new(id: WindowId, mode: WindowMode, colors_cfg: ColorsConfig, new: UiTask<WindowRoot>, responder: ResponderVar<WindowId>) -> Self {
         let primary_scale_factor = match mode {
             WindowMode::Headed => MONITORS
                 .primary_monitor()
@@ -1438,7 +1442,7 @@ impl AppWindowTask {
 
         let mut ctx = WindowCtx::new(id, mode);
 
-        let vars = WindowVars::new(WINDOWS_SV.read().default_render_mode.get(), primary_scale_factor, color_scheme);
+        let vars = WindowVars::new(WINDOWS_SV.read().default_render_mode.get(), primary_scale_factor, colors_cfg);
         ctx.with_state(|s| s.borrow_mut().set(*WINDOW_VARS_ID, vars.clone()));
 
         Self {
@@ -1473,6 +1477,11 @@ impl AppWindowTask {
                 let root = mem::replace(&mut window.child, NilUiNode.boxed());
                 window.child = ext(WindowRootExtenderArgs { root });
             }
+            let child = mem::replace(&mut window.child, NilUiNode.boxed());
+            let vars = WINDOW.vars();
+            let child = with_context_var(child, ACCENT_COLOR_VAR, vars.actual_accent_color());
+            let child = with_context_var(child, COLOR_SCHEME_VAR, vars.actual_color_scheme());
+            window.child = child.boxed();
         });
 
         let mode = self.mode;
diff --git a/crates/zng-ext-window/src/vars.rs b/crates/zng-ext-window/src/vars.rs
index 8dbad76a1..7ba7e36ba 100644
--- a/crates/zng-ext-window/src/vars.rs
+++ b/crates/zng-ext-window/src/vars.rs
@@ -4,6 +4,7 @@ use zng_app::{
     widget::info::access::AccessEnabled,
     window::{MonitorId, WindowId, WINDOW},
 };
+use zng_color::LightDark;
 use zng_ext_image::Img;
 use zng_layout::unit::{Dip, DipPoint, DipRect, DipSize, DipToPx, Factor, FactorUnits, Length, LengthUnits, Point, PxPoint, PxSize, Size};
 use zng_state_map::{static_id, StateId};
@@ -11,7 +12,7 @@ use zng_txt::Txt;
 use zng_unique_id::IdSet;
 use zng_var::{merge_var, var, var_from, ArcVar, BoxedVar, ReadOnlyArcVar, Var};
 use zng_view_api::{
-    config::ColorScheme,
+    config::{ColorScheme, ColorsConfig},
     window::{CursorIcon, FocusIndicator, RenderMode, VideoMode, WindowButton, WindowState},
 };
 
@@ -66,6 +67,8 @@ pub(super) struct WindowVarsData {
 
     color_scheme: ArcVar<Option<ColorScheme>>,
     pub(super) actual_color_scheme: ArcVar<ColorScheme>,
+    accent_color: ArcVar<Option<LightDark>>,
+    pub(super) actual_accent_color: ArcVar<LightDark>,
 
     pub(super) is_open: ArcVar<bool>,
     pub(super) is_loaded: ArcVar<bool>,
@@ -88,7 +91,7 @@ pub(super) struct WindowVarsData {
 #[derive(Clone)]
 pub struct WindowVars(pub(super) Arc<WindowVarsData>);
 impl WindowVars {
-    pub(super) fn new(default_render_mode: RenderMode, primary_scale_factor: Factor, system_color_scheme: ColorScheme) -> Self {
+    pub(super) fn new(default_render_mode: RenderMode, primary_scale_factor: Factor, system_colors: ColorsConfig) -> Self {
         let vars = Arc::new(WindowVarsData {
             chrome: var(true),
             icon: var(WindowIcon::Default),
@@ -141,7 +144,9 @@ impl WindowVars {
             children: var(IdSet::default()),
 
             color_scheme: var(None),
-            actual_color_scheme: var(system_color_scheme),
+            actual_color_scheme: var(system_colors.scheme),
+            accent_color: var(None),
+            actual_accent_color: var(system_colors.accent.into()),
 
             is_open: var(true),
             is_loaded: var(false),
@@ -547,7 +552,8 @@ impl WindowVars {
     /// * This window is always on-top of the parent window.
     /// * If the parent window is closed, this window is also closed.
     /// * If [`modal`] is set, the parent window cannot be focused while this window is open.
-    /// * If a [`color_scheme`] is not set, the [`color_scheme`] fallback is the parent's actual scheme.
+    /// * If a [`color_scheme`] is not set, the fallback is the parent's actual scheme.
+    /// * If an [`accent_color`] is not set, the fallback is the parent's actual accent.
     ///
     /// The default value is `None`.
     ///
@@ -560,7 +566,7 @@ impl WindowVars {
     ///
     /// [`modal`]: Self::modal
     /// [`color_scheme`]: Self::color_scheme
-    /// [`actual_color_scheme`]: Self::color_scheme
+    /// [`accent_color`]: Self::accent_color
     pub fn parent(&self) -> ArcVar<Option<WindowId>> {
         self.0.parent.clone()
     }
@@ -601,6 +607,27 @@ impl WindowVars {
         self.0.actual_color_scheme.read_only()
     }
 
+    /// Override the preferred accent color.
+    ///
+    /// If set to `None` the system preference is used, see [`actual_accent_color`].
+    ///
+    /// [`actual_accent_color`]: Self::actual_accent_color
+    pub fn accent_color(&self) -> ArcVar<Option<LightDark>> {
+        self.0.accent_color.clone()
+    }
+
+    /// Actual accent color to use.
+    ///
+    /// This is the system preference, or [`color_scheme`] if it is set.
+    ///
+    /// The window widget also sets [`ACCENT_COLOR_VAR`] to this variable.
+    ///
+    /// [`color_scheme`]: Self::color_scheme
+    /// [`ACCENT_COLOR_VAR`]: zng_color::colors::ACCENT_COLOR_VAR
+    pub fn actual_accent_color(&self) -> ReadOnlyArcVar<LightDark> {
+        self.0.actual_accent_color.read_only()
+    }
+
     /// If the window is open.
     ///
     /// This is a read-only variable, it starts set to `true` and will update only once,
diff --git a/crates/zng-view-api/src/config.rs b/crates/zng-view-api/src/config.rs
index 242f987dd..81b8a6e50 100644
--- a/crates/zng-view-api/src/config.rs
+++ b/crates/zng-view-api/src/config.rs
@@ -5,7 +5,7 @@ use std::{fmt, time::Duration};
 use serde::{Deserialize, Serialize};
 
 use zng_txt::Txt;
-use zng_unit::{Dip, DipSize};
+use zng_unit::{Dip, DipSize, Rgba};
 
 /// System settings needed for implementing double/triple clicks.
 #[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Deserialize)]
@@ -165,6 +165,27 @@ impl Default for ColorScheme {
     }
 }
 
+/// System colors and color scheme.
+#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
+pub struct ColorsConfig {
+    /// Color scheme (light/dark) preference.
+    pub scheme: ColorScheme,
+    /// Accent color.
+    ///
+    /// Accent color preference.
+    ///
+    /// Expect a saturated color that contrasts with the text color.
+    pub accent: Rgba,
+}
+impl Default for ColorsConfig {
+    fn default() -> Self {
+        Self {
+            scheme: Default::default(),
+            accent: Rgba::new(10, 10, 200, 255),
+        }
+    }
+}
+
 #[cfg(feature = "var")]
 zng_var::impl_from_and_into_var! {
     fn from(some: ColorScheme) -> Option<ColorScheme>;
diff --git a/crates/zng-view-api/src/types.rs b/crates/zng-view-api/src/types.rs
index bef33ea7b..9f6345dcb 100644
--- a/crates/zng-view-api/src/types.rs
+++ b/crates/zng-view-api/src/types.rs
@@ -3,7 +3,7 @@
 use crate::{
     access::{AccessCmd, AccessNodeId},
     api_extension::{ApiExtensionId, ApiExtensionPayload, ApiExtensions},
-    config::{AnimationsConfig, ColorScheme, FontAntiAliasing, KeyRepeatConfig, LocaleConfig, MultiClickConfig, TouchConfig},
+    config::{AnimationsConfig, ColorsConfig, FontAntiAliasing, KeyRepeatConfig, LocaleConfig, MultiClickConfig, TouchConfig},
     dialog::{DialogId, FileDialogResponse, MsgDialogResponse},
     image::{ImageId, ImageLoadedData, ImagePpi},
     ipc::IpcBytes,
@@ -131,8 +131,8 @@ pub struct Inited {
     pub animations_config: AnimationsConfig,
     /// System locale config.
     pub locale_config: LocaleConfig,
-    /// System preferred color scheme.
-    pub color_scheme: ColorScheme,
+    /// System preferred color scheme and colors.
+    pub colors_config: ColorsConfig,
     /// API extensions implemented by the view-process.
     ///
     /// The extension IDs will stay valid for the duration of the view-process.
@@ -375,8 +375,6 @@ pub enum Event {
     /// The available monitors have changed.
     MonitorsChanged(Vec<(MonitorId, MonitorInfo)>),
 
-    /// The preferred color scheme for a window has changed.
-    ColorSchemeChanged(WindowId, ColorScheme),
     /// The window has been requested to close.
     WindowCloseRequested(WindowId),
     /// The window has closed.
@@ -465,6 +463,8 @@ pub enum Event {
     TouchConfigChanged(TouchConfig),
     /// System locale changed.
     LocaleChanged(LocaleConfig),
+    /// System color scheme or colors changed.
+    ColorsConfigChanged(ColorsConfig),
 
     /* Raw device events */
     /// Device added or installed.
diff --git a/crates/zng-view-api/src/window.rs b/crates/zng-view-api/src/window.rs
index 65d0f1e82..5576f89d6 100644
--- a/crates/zng-view-api/src/window.rs
+++ b/crates/zng-view-api/src/window.rs
@@ -7,7 +7,6 @@ use zng_txt::Txt;
 
 use crate::{
     api_extension::{ApiExtensionId, ApiExtensionPayload},
-    config::ColorScheme,
     display_list::{DisplayList, FrameValueUpdate},
     image::{ImageId, ImageLoadedData, ImageMaskMode},
 };
@@ -216,9 +215,6 @@ pub struct WindowOpenData {
 
     /// Actual render mode, can be different from the requested mode if it is not available.
     pub render_mode: RenderMode,
-
-    /// Preferred color scheme.
-    pub color_scheme: ColorScheme,
 }
 
 /// Information about a successfully opened headless surface.
diff --git a/crates/zng-view/Cargo.toml b/crates/zng-view/Cargo.toml
index 33260e120..81665dec8 100644
--- a/crates/zng-view/Cargo.toml
+++ b/crates/zng-view/Cargo.toml
@@ -118,7 +118,7 @@ features = [
 ]
 [target.'cfg(windows)'.dependencies.windows]
 version = "0.57.0"
-features = ["Foundation_Collections", "System_UserProfile"]
+features = ["Foundation_Collections", "System_UserProfile", "UI_ViewManagement"]
 
 [target.'cfg(windows)'.dependencies]
 clipboard-win = { version = "5.0", features = ["std"] }
@@ -137,7 +137,13 @@ tempfile = "3.10"
 version = "0.2.2"
 features = [
     "NSEvent",
+    "NSAppearance",
+    "NSColor",
 ]
+[target.'cfg(target_os = "macos")'.dependencies.objc2-foundation]
+version = "0.2.2"
+features = ["NSString"]
+
 
 
 [build-dependencies]
diff --git a/crates/zng-view/src/config.rs b/crates/zng-view/src/config.rs
index 672d99264..6c52d6e95 100644
--- a/crates/zng-view/src/config.rs
+++ b/crates/zng-view/src/config.rs
@@ -1,4 +1,6 @@
-use zng_view_api::config::{AnimationsConfig, ColorScheme, FontAntiAliasing, KeyRepeatConfig, LocaleConfig, MultiClickConfig, TouchConfig};
+use zng_view_api::config::{
+    AnimationsConfig, ColorsConfig, FontAntiAliasing, KeyRepeatConfig, LocaleConfig, MultiClickConfig, TouchConfig,
+};
 
 #[cfg(windows)]
 mod windows;
@@ -59,8 +61,8 @@ pub fn touch_config() -> TouchConfig {
     platform::touch_config()
 }
 
-pub fn color_scheme_config() -> ColorScheme {
-    platform::color_scheme_config()
+pub fn colors_config() -> ColorsConfig {
+    platform::colors_config()
 }
 
 pub fn locale_config() -> LocaleConfig {
diff --git a/crates/zng-view/src/config/dconf.rs b/crates/zng-view/src/config/dconf.rs
index d9538b56f..adf5d2a0f 100644
--- a/crates/zng-view/src/config/dconf.rs
+++ b/crates/zng-view/src/config/dconf.rs
@@ -1,8 +1,8 @@
 use std::{io::BufRead as _, time::Duration};
 
-use zng_unit::TimeUnits as _;
+use zng_unit::{Rgba, TimeUnits as _};
 use zng_view_api::{
-    config::{AnimationsConfig, ColorScheme, FontAntiAliasing, KeyRepeatConfig, LocaleConfig, MultiClickConfig, TouchConfig},
+    config::{AnimationsConfig, ColorScheme, ColorsConfig, FontAntiAliasing, KeyRepeatConfig, LocaleConfig, MultiClickConfig, TouchConfig},
     Event,
 };
 
@@ -51,8 +51,8 @@ pub fn touch_config() -> TouchConfig {
     super::other::touch_config()
 }
 
-pub fn color_scheme_config() -> ColorScheme {
-    match dconf("/org/gnome/desktop/interface/color-scheme") {
+pub fn colors_config() -> ColorsConfig {
+    let scheme = match dconf("/org/gnome/desktop/interface/color-scheme") {
         Some(cs) => {
             if cs.contains("dark") {
                 ColorScheme::Dark
@@ -61,7 +61,30 @@ pub fn color_scheme_config() -> ColorScheme {
             }
         }
         None => ColorScheme::Light,
-    }
+    };
+
+    // the color value is not in any config, need to parse theme name
+    let theme = dconf("/org/gnome/desktop/interface/gtk-theme");
+    let theme = match theme.as_ref() {
+        Some(n) => n.strip_prefix("Yaru-").unwrap_or("").split('-').next().unwrap_or(""),
+        None => "?",
+    };
+    // see https://github.com/ubuntu/yaru/blob/6e28865e0ce55c0f95d17a25871618b1660e97b5/common/accent-colors.scss.in
+    let accent = match theme {
+        "" => Rgba::new(233, 84, 32, 255),               // #E95420
+        "bark" => Rgba::new(120, 120, 89, 255),          // #787859
+        "sage" => Rgba::new(101, 123, 105, 255),         // #657b69
+        "olive" => Rgba::new(75, 133, 1, 255),           // #4B8501
+        "viridian" => Rgba::new(3, 135, 91, 255),        // #03875b
+        "prussiangreen" => Rgba::new(48, 130, 128, 255), // #308280
+        "blue" => Rgba::new(0, 115, 229, 255),           // #0073E5
+        "purple" => Rgba::new(119, 100, 216, 255),       // #7764d8
+        "magenta" => Rgba::new(179, 76, 179, 255),       // #b34cb3
+        "red" => Rgba::new(218, 52, 80, 255),            // #DA3450
+        _ => ColorsConfig::default().accent,
+    };
+
+    ColorsConfig { scheme, accent }
 }
 
 pub fn locale_config() -> LocaleConfig {
@@ -73,8 +96,8 @@ fn on_change(key: &str, s: &crate::AppEventSender) {
     // println!("{key}"); // to discover keys, uncomment and change the config in system config app.
 
     match key {
-        "/org/gnome/desktop/interface/color-scheme" => {
-            let _ = s.send(AppEvent::ColorSchemeConfigChanged);
+        "/org/gnome/desktop/interface/color-scheme" | "/org/gnome/desktop/interface/gtk-theme" => {
+            let _ = s.send(AppEvent::Notify(Event::ColorsConfigChanged(colors_config())));
         }
         "/org/gnome/desktop/peripherals/keyboard/delay" | "/org/gnome/desktop/peripherals/keyboard/repeat-interval" => {
             let _ = s.send(AppEvent::Notify(Event::KeyRepeatConfigChanged(key_repeat_config())));
diff --git a/crates/zng-view/src/config/macos.rs b/crates/zng-view/src/config/macos.rs
index 75331ff8a..462316a6d 100644
--- a/crates/zng-view/src/config/macos.rs
+++ b/crates/zng-view/src/config/macos.rs
@@ -1,6 +1,7 @@
 use objc2_app_kit::*;
-use zng_unit::TimeUnits as _;
-use zng_view_api::config::{AnimationsConfig, ColorScheme, FontAntiAliasing, KeyRepeatConfig, MultiClickConfig, TouchConfig};
+use objc2_foundation::*;
+use zng_unit::{Rgba, TimeUnits as _};
+use zng_view_api::config::{AnimationsConfig, ColorScheme, ColorsConfig, FontAntiAliasing, KeyRepeatConfig, MultiClickConfig, TouchConfig};
 
 pub fn font_aa() -> FontAntiAliasing {
     super::other::font_aa()
@@ -28,8 +29,35 @@ pub fn touch_config() -> TouchConfig {
     super::other::touch_config()
 }
 
-pub fn color_scheme_config() -> ColorScheme {
-    super::other::color_scheme_config()
+pub fn colors_config() -> ColorsConfig {
+    let appearance = unsafe { NSAppearance::currentDrawingAppearance() };
+
+    // source: winit
+    fn dark_appearance_name() -> &'static NSString {
+        // Don't use the static `NSAppearanceNameDarkAqua` to allow linking on macOS < 10.14
+        ns_string!("NSAppearanceNameDarkAqua")
+    }
+    let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[
+        unsafe { NSAppearanceNameAqua.copy() },
+        dark_appearance_name().copy(),
+    ]));
+    let scheme = if let Some(best_match) = best_match {
+        if *best_match == *dark_appearance_name() {
+            ColorScheme::Dark
+        } else {
+            ColorScheme::Light
+        }
+    } else {
+        tracing::warn!("failed to determine macOS color scheme");
+        ColorScheme::Light
+    };
+
+    let accent = unsafe {
+        let a = NSColor::controlAccentColor();
+        Rgba::new(a.redComponent(), a.greenComponent(), a.blueComponent(), a.alphaComponent())
+    };
+
+    ColorsConfig { scheme, accent }
 }
 
 #[cfg(not(windows))]
diff --git a/crates/zng-view/src/config/other.rs b/crates/zng-view/src/config/other.rs
index 9dbb5efbb..9f6de50a4 100644
--- a/crates/zng-view/src/config/other.rs
+++ b/crates/zng-view/src/config/other.rs
@@ -1,6 +1,6 @@
 #![allow(unused)]
 
-use zng_view_api::config::{AnimationsConfig, ColorScheme, FontAntiAliasing, KeyRepeatConfig, MultiClickConfig, TouchConfig};
+use zng_view_api::config::{AnimationsConfig, ColorScheme, ColorsConfig, FontAntiAliasing, KeyRepeatConfig, MultiClickConfig, TouchConfig};
 
 pub fn font_aa() -> FontAntiAliasing {
     warn("font_aa");
@@ -27,9 +27,9 @@ pub fn touch_config() -> TouchConfig {
     TouchConfig::default()
 }
 
-pub fn color_scheme_config() -> ColorScheme {
-    warn("color_scheme_config");
-    ColorScheme::default()
+pub fn colors_config() -> ColorsConfig {
+    warn("colors_config");
+    ColorsConfig::default()
 }
 
 #[cfg(not(windows))]
diff --git a/crates/zng-view/src/config/windows.rs b/crates/zng-view/src/config/windows.rs
index f27318822..9934313c1 100644
--- a/crates/zng-view/src/config/windows.rs
+++ b/crates/zng-view/src/config/windows.rs
@@ -1,4 +1,7 @@
-use zng_view_api::config::{AnimationsConfig, ColorScheme, FontAntiAliasing, KeyRepeatConfig, LocaleConfig, MultiClickConfig, TouchConfig};
+use zng_unit::Rgba;
+use zng_view_api::config::{
+    AnimationsConfig, ColorScheme, ColorsConfig, FontAntiAliasing, KeyRepeatConfig, LocaleConfig, MultiClickConfig, TouchConfig,
+};
 
 /// Create a hidden window that listens to Windows config change events.
 pub(crate) fn spawn_listener(event_loop: crate::AppEventSender) -> Option<Box<dyn FnOnce()>> {
@@ -80,6 +83,8 @@ fn config_listener(event_loop: crate::AppEventSender) {
         RegisterPowerSettingNotification(window, &GUID_SESSION_DISPLAY_STATUS, 0)
     };
 
+    let mut colors_cfg = colors_config();
+
     let r = util::set_raw_windows_event_handler(window, u32::from_ne_bytes(*b"cevl") as _, move |_, msg, wparam, lparam| {
         let notify = |ev| {
             let _ = event_loop.send(AppEvent::Notify(ev));
@@ -105,7 +110,17 @@ fn config_listener(event_loop: crate::AppEventSender) {
                         _ => None,
                     }
                 }
-                _ => None,
+                _ => {
+                    // winit does this too for color scheme
+                    let cfg = colors_config();
+                    if cfg != colors_cfg {
+                        colors_cfg = cfg;
+                        notify(Event::ColorsConfigChanged(cfg));
+                        Some(0)
+                    } else {
+                        None
+                    }
+                }
             },
             WM_DISPLAYCHANGE => {
                 let _ = event_loop.send(AppEvent::RefreshMonitors);
@@ -292,7 +307,7 @@ pub fn touch_config() -> TouchConfig {
     super::other::touch_config()
 }
 
-pub fn color_scheme_config() -> ColorScheme {
+pub fn colors_config() -> ColorsConfig {
     // source: winit
 
     use std::mem;
@@ -344,11 +359,19 @@ pub fn color_scheme_config() -> ColorScheme {
         ok != 0 && hc.dwFlags & HCF_HIGHCONTRASTON == HCF_HIGHCONTRASTON
     }
 
-    if should_apps_use_dark_mode() && !is_high_contrast() {
+    let scheme = if should_apps_use_dark_mode() && !is_high_contrast() {
         ColorScheme::Dark
     } else {
         ColorScheme::Light
-    }
+    };
+
+    let accent = windows::UI::ViewManagement::UISettings::new()
+        .ok()
+        .and_then(|ui| ui.GetColorValue(windows::UI::ViewManagement::UIColorType::Accent).ok())
+        .map(|a| Rgba::new(a.R, a.G, a.B, a.A))
+        .unwrap_or_else(|| ColorsConfig::default().accent);
+
+    ColorsConfig { scheme, accent }
 }
 
 pub(crate) fn locale_config() -> LocaleConfig {
diff --git a/crates/zng-view/src/lib.rs b/crates/zng-view/src/lib.rs
index 111236e6f..5bfc8c987 100644
--- a/crates/zng-view/src/lib.rs
+++ b/crates/zng-view/src/lib.rs
@@ -134,7 +134,6 @@ use zng_txt::Txt;
 use zng_unit::{Dip, DipPoint, DipRect, DipSize, Factor, Px, PxPoint, PxRect, PxToDip};
 use zng_view_api::{
     api_extension::{ApiExtensionId, ApiExtensionPayload},
-    config::ColorScheme,
     dialog::{DialogId, FileDialog, MsgDialog, MsgDialogResponse},
     font::{FontFaceId, FontId, FontOptions, FontVariationName},
     image::{ImageId, ImageLoadedData, ImageMaskMode, ImageRequest, ImageTextureId},
@@ -793,7 +792,6 @@ impl winit::application::ApplicationHandler<AppEvent> for App {
                     self.notify(Event::WindowChanged(WindowChanged::resized(id, size, EventCause::System, None)));
                 }
             }
-            WindowEvent::ThemeChanged(t) => self.notify(Event::ColorSchemeChanged(id, util::winit_theme_to_zng(t))),
             WindowEvent::Ime(ime) => {
                 linux_modal_dialog_bail!();
 
@@ -817,6 +815,7 @@ impl winit::application::ApplicationHandler<AppEvent> for App {
                     }
                 }
             }
+            WindowEvent::ThemeChanged(_) => {}
             WindowEvent::Occluded(_) => {}
             WindowEvent::ActivationTokenDone { .. } => {}
             WindowEvent::PinchGesture { .. } => {}
@@ -866,22 +865,6 @@ impl winit::application::ApplicationHandler<AppEvent> for App {
                     w.redraw();
                 }
             }
-            AppEvent::ColorSchemeConfigChanged => {
-                #[cfg(any(
-                    target_os = "linux",
-                    target_os = "dragonfly",
-                    target_os = "freebsd",
-                    target_os = "netbsd",
-                    target_os = "openbsd"
-                ))]
-                {
-                    let cfg = crate::config::color_scheme_config();
-                    let ids: Vec<_> = self.windows.iter().map(|w| w.id()).collect();
-                    for id in ids {
-                        self.notify(Event::ColorSchemeChanged(id, cfg));
-                    }
-                }
-            }
             AppEvent::InitDeviceEvents(enabled) => {
                 self.init_device_events(enabled, Some(winit_loop));
             }
@@ -1093,7 +1076,6 @@ impl App {
                                 self.app.image_cache.loaded(data);
                             }
                             AppEvent::MonitorPowerChanged => {} // headless
-                            AppEvent::ColorSchemeConfigChanged => {}
                             AppEvent::InitDeviceEvents(enabled) => {
                                 self.app.init_device_events(enabled, None);
                             }
@@ -1547,13 +1529,13 @@ impl Api for App {
             generation: gen,
             is_respawn,
             available_monitors,
-            color_scheme: config::color_scheme_config(),
             multi_click_config: config::multi_click_config(),
             key_repeat_config: config::key_repeat_config(),
             touch_config: config::touch_config(),
             font_aa: config::font_aa(),
             animations_config: config::animations_config(),
             locale_config: config::locale_config(),
+            colors_config: config::colors_config(),
             extensions: self.exts.api_extensions(),
         }));
     }
@@ -1590,7 +1572,6 @@ impl Api for App {
                 position: (PxPoint::zero(), DipPoint::zero()),
                 size: config.state.restore_rect.size,
                 scale_factor: Factor(1.0),
-                color_scheme: ColorScheme::Light,
                 state: WindowStateAll {
                     state: WindowState::Fullscreen,
                     global_position: PxPoint::zero(),
@@ -1628,7 +1609,6 @@ impl Api for App {
                 scale_factor: win.scale_factor(),
                 render_mode: win.render_mode(),
                 state: win.state(),
-                color_scheme: win.color_scheme(),
             };
 
             self.windows.push(win);
@@ -2082,12 +2062,6 @@ pub(crate) enum AppEvent {
     /// Send when monitor was turned on/off by the OS, need to redraw all screens to avoid blank issue.
     #[allow(unused)]
     MonitorPowerChanged,
-
-    /// System color scheme changed.
-    ///
-    /// X11/Wayland needs this
-    #[allow(unused)]
-    ColorSchemeConfigChanged,
 }
 
 /// Message inserted in the request loop from the view-process.
diff --git a/crates/zng-view/src/util.rs b/crates/zng-view/src/util.rs
index b011da4b8..691e17a55 100644
--- a/crates/zng-view/src/util.rs
+++ b/crates/zng-view/src/util.rs
@@ -16,7 +16,6 @@ use zng_view_api::clipboard as clipboard_api;
 use zng_view_api::keyboard::{KeyLocation, NativeKeyCode};
 use zng_view_api::window::{FrameCapture, FrameRequest, FrameUpdateRequest, ResizeDirection, WindowButton};
 use zng_view_api::{
-    config::ColorScheme,
     keyboard::{Key, KeyCode, KeyState},
     mouse::{ButtonState, MouseButton, MouseScrollDelta},
     touch::{TouchForce, TouchPhase},
@@ -366,13 +365,6 @@ pub(crate) fn winit_mouse_button_to_zng(b: winit::event::MouseButton) -> MouseBu
     }
 }
 
-pub(crate) fn winit_theme_to_zng(t: winit::window::Theme) -> ColorScheme {
-    match t {
-        winit::window::Theme::Light => ColorScheme::Light,
-        winit::window::Theme::Dark => ColorScheme::Dark,
-    }
-}
-
 #[cfg(windows)]
 pub(crate) fn winit_to_hwnd(window: &winit::window::Window) -> isize {
     use raw_window_handle::HasWindowHandle as _;
diff --git a/crates/zng-view/src/window.rs b/crates/zng-view/src/window.rs
index 1f910a3a2..3abd24863 100644
--- a/crates/zng-view/src/window.rs
+++ b/crates/zng-view/src/window.rs
@@ -25,7 +25,6 @@ use zng_txt::{ToTxt, Txt};
 use zng_unit::{DipPoint, DipRect, DipSize, DipToPx, Factor, Px, PxPoint, PxRect, PxToDip, PxVector, Rgba};
 use zng_view_api::{
     api_extension::{ApiExtensionId, ApiExtensionPayload},
-    config::ColorScheme,
     font::{FontFaceId, FontId, FontOptions, FontVariationName},
     image::{ImageId, ImageLoadedData, ImageMaskMode, ImageTextureId},
     window::{
@@ -1179,17 +1178,6 @@ impl Window {
         self.state.clone()
     }
 
-    /// Returns the preferred color scheme for the window.
-    pub fn color_scheme(&self) -> ColorScheme {
-        match self.window.theme() {
-            Some(t) => match t {
-                winit::window::Theme::Light => ColorScheme::Light,
-                winit::window::Theme::Dark => ColorScheme::Dark,
-            },
-            None => crate::config::color_scheme_config(),
-        }
-    }
-
     fn set_inner_position(&self, pos: DipPoint) {
         let monitor_offset = if let Some(m) = self.window.current_monitor() {
             m.position().to_px().to_vector()
diff --git a/crates/zng-wgt-button/src/lib.rs b/crates/zng-wgt-button/src/lib.rs
index f94fc959d..0dc23efc5 100644
--- a/crates/zng-wgt-button/src/lib.rs
+++ b/crates/zng-wgt-button/src/lib.rs
@@ -13,7 +13,7 @@ zng_wgt::enable_widget_macros!();
 
 use std::{any::TypeId, ops};
 
-use colors::BASE_COLOR_VAR;
+use colors::{ACCENT_COLOR_VAR, BASE_COLOR_VAR};
 use zng_app::event::CommandParam;
 use zng_var::ReadOnlyContextVar;
 use zng_wgt::{base_color, border, corner_radius, is_disabled, prelude::*};
@@ -330,6 +330,20 @@ impl DefaultStyle {
     }
 }
 
+/// Primary button style.
+#[widget($crate::PrimaryStyle)]
+pub struct PrimaryStyle(DefaultStyle);
+impl PrimaryStyle {
+    fn widget_intrinsic(&mut self) {
+        widget_set! {
+            self;
+
+            base_color = ACCENT_COLOR_VAR.map(|c| c.shade(-2));
+            zng_wgt_text::font_weight = zng_ext_font::FontWeight::BOLD;
+        }
+    }
+}
+
 /// Button light style.
 #[widget($crate::LightStyle)]
 pub struct LightStyle(DefaultStyle);
diff --git a/crates/zng-wgt-text-input/src/selectable.rs b/crates/zng-wgt-text-input/src/selectable.rs
index 254850e5a..9edbe783b 100644
--- a/crates/zng-wgt-text-input/src/selectable.rs
+++ b/crates/zng-wgt-text-input/src/selectable.rs
@@ -56,6 +56,7 @@ impl DefaultStyle {
             popup::context_capture = crate::default_popup_context_capture();
             context_menu_fn = WidgetFn::new(default_context_menu);
             selection_toolbar_fn = WidgetFn::new(default_selection_toolbar);
+            selection_color = colors::ACCENT_COLOR_VAR.rgba_map(|c| c.with_alpha(30.pct()));
         }
     }
 }
diff --git a/crates/zng-wgt-text-input/src/text_input.rs b/crates/zng-wgt-text-input/src/text_input.rs
index ab6244099..125b420cb 100644
--- a/crates/zng-wgt-text-input/src/text_input.rs
+++ b/crates/zng-wgt-text-input/src/text_input.rs
@@ -1,4 +1,4 @@
-use colors::BASE_COLOR_VAR;
+use colors::{ACCENT_COLOR_VAR, BASE_COLOR_VAR};
 use zng_ext_clipboard::{COPY_CMD, CUT_CMD, PASTE_CMD};
 use zng_wgt::base_color;
 use zng_wgt::{align, is_disabled, margin, prelude::*};
@@ -160,6 +160,7 @@ impl DefaultStyle {
             popup::context_capture = default_popup_context_capture();
             context_menu_fn = WidgetFn::new(default_context_menu);
             selection_toolbar_fn = WidgetFn::new(default_selection_toolbar);
+            selection_color = ACCENT_COLOR_VAR.rgba_map(|c| c.with_alpha(30.pct()));
 
             when *#is_cap_hovered || *#is_return_focus {
                 border = {
@@ -171,7 +172,7 @@ impl DefaultStyle {
             when *#is_focused {
                 border = {
                     widths: 1,
-                    sides: BASE_COLOR_VAR.shade_fct_into(0.40),
+                    sides: ACCENT_COLOR_VAR.rgba_into(),
                 };
             }
 
diff --git a/crates/zng-wgt-toggle/src/lib.rs b/crates/zng-wgt-toggle/src/lib.rs
index 3d9770c5a..ed2315be9 100644
--- a/crates/zng-wgt-toggle/src/lib.rs
+++ b/crates/zng-wgt-toggle/src/lib.rs
@@ -24,7 +24,7 @@ use zng_ext_input::{
 };
 use zng_ext_l10n::lang;
 use zng_var::{AnyVar, AnyVarValue, BoxedAnyVar, Var, VarIsReadOnlyError};
-use zng_wgt::{align, border, border_align, border_over, corner_radius, hit_test_mode, is_inited, prelude::*, Wgt};
+use zng_wgt::{align, border, border_align, border_over, corner_radius, hit_test_mode, is_inited, prelude::*, Wgt, ICONS};
 use zng_wgt_access::{access_role, accessible, AccessRole};
 use zng_wgt_container::{child_align, child_end, child_start, padding};
 use zng_wgt_fill::background_color;
@@ -972,7 +972,7 @@ impl CheckStyle {
 }
 context_var! {
     /// Spacing between the checkmark and the content.
-    pub static CHECK_SPACING_VAR: Length = 2;
+    pub static CHECK_SPACING_VAR: Length = 4;
 }
 
 /// Spacing between the checkmark and the content.
@@ -982,24 +982,27 @@ pub fn check_spacing(child: impl UiNode, spacing: impl IntoVar<Length>) -> impl
 }
 
 fn checkmark_visual(parent_hovered: impl Var<bool>) -> impl UiNode {
-    zng_wgt_text::Text! {
+    let checked = ICONS.get_or(["toggle.checked", "check"], || {
+        zng_wgt_text::Text! {
+            txt = "✓";
+            font_family = FontNames::system_ui(&lang!(und));
+            txt_align = Align::CENTER;
+        }
+    });
+    let indeterminate = ICONS.get_or(["toggle.indeterminate"], || {
+        zng_wgt::Wgt! {
+            align = Align::CENTER;
+            background_color = zng_wgt_text::FONT_COLOR_VAR;
+            size = (6, 2);
+            corner_radius = 0;
+        }
+    });
+    zng_wgt_container::Container! {
         hit_test_mode = false;
         accessible = false;
         size = 1.2.em();
-        font_family = FontNames::system_ui(&lang!(und));
-        txt_align = Align::CENTER;
-        align = Align::CENTER;
         corner_radius = 0.1.em();
-
-        txt = "✓";
-        when #{IS_CHECKED_VAR}.is_none() {
-            txt = "━";
-        }
-
-        font_color = zng_wgt_text::FONT_COLOR_VAR.map(|c| c.transparent());
-        when #{IS_CHECKED_VAR}.unwrap_or(true) {
-            font_color = zng_wgt_text::FONT_COLOR_VAR;
-        }
+        align = Align::CENTER;
 
         #[easing(150.ms())]
         background_color = zng_wgt_text::FONT_COLOR_VAR.map(|c| c.with_alpha(10.pct()));
@@ -1007,6 +1010,15 @@ fn checkmark_visual(parent_hovered: impl Var<bool>) -> impl UiNode {
             #[easing(0.ms())]
             background_color = zng_wgt_text::FONT_COLOR_VAR.map(|c| c.with_alpha(20.pct()));
         }
+
+        when #{IS_CHECKED_VAR}.is_none() {
+            child = indeterminate;
+        }
+        when *#{IS_CHECKED_VAR} == Some(true) {
+            child = checked;
+            #[easing(0.ms())]
+            background_color = colors::ACCENT_COLOR_VAR.shade(-1);
+        }
     }
 }
 
@@ -1026,7 +1038,7 @@ impl ComboStyle {
             child_align = Align::FILL;
             border_over = false;
             border_align = 1.fct();
-            padding = COMBO_SPACING_VAR.map(|e| SideOffsets::new(-1, e.clone(), -1, -1));
+            padding = -1;
             checked = var(false);
             child_end = {
                 node: combomark_visual(),
@@ -1086,7 +1098,7 @@ impl ComboStyle {
 }
 context_var! {
     /// Spacing between the arrow symbol and the content.
-    pub static COMBO_SPACING_VAR: Length = 2;
+    pub static COMBO_SPACING_VAR: Length = 0;
 }
 
 /// Spacing between the arrow symbol and the content.
@@ -1164,6 +1176,22 @@ pub fn checked_popup(child: impl UiNode, popup: impl IntoVar<WidgetFn<()>>) -> i
 
 #[allow(non_snake_case)]
 fn combomark_visual() -> impl UiNode {
+    let dropdown = ICONS.get_or(
+        ["toggle.dropdown", "material/rounded/keyboard-arrow-down", "keyboard-arrow-down"],
+        combomark_visual_fallback,
+    );
+    Wgt! {
+        size = 12;
+        zng_wgt_fill::background = dropdown;
+        align = Align::CENTER;
+
+        zng_wgt_transform::rotate_x = 0.deg();
+        when #is_checked {
+            zng_wgt_transform::rotate_x = 180.deg();
+        }
+    }
+}
+fn combomark_visual_fallback() -> impl UiNode {
     let color_key = FrameValueKey::new_unique();
     let mut size = PxSize::zero();
     let mut bounds = PxBox::zero();
@@ -1177,16 +1205,15 @@ fn combomark_visual() -> impl UiNode {
             .rotate(45.deg())
             .scale_x(0.7)
             .translate(center.x, center.y)
+            .translate_x(Length::from(2).layout_x())
             .layout();
-
         let bounds = transform.outer_transformed(PxBox::from_size(size)).unwrap_or_default();
         (size, transform, bounds)
     }
 
-    let visual = match_node_leaf(move |op| match op {
+    match_node_leaf(move |op| match op {
         UiNodeOp::Init => {
             WIDGET.sub_var_render_update(&zng_wgt_text::FONT_COLOR_VAR);
-            WIDGET.sub_var_render(&IS_CHECKED_VAR);
         }
         UiNodeOp::Measure { desired_size, .. } => {
             let (s, _, _) = layout();
@@ -1199,10 +1226,7 @@ fn combomark_visual() -> impl UiNode {
         UiNodeOp::Render { frame } => {
             let mut clip = bounds.to_rect();
             clip.size.height *= 0.5.fct();
-
-            if !IS_CHECKED_VAR.get().unwrap_or(false) {
-                clip.origin.y += clip.size.height;
-            }
+            clip.origin.y += clip.size.height;
 
             frame.push_clip_rect(clip, false, false, |frame| {
                 frame.push_reference_frame((WIDGET.id(), 0).into(), transform.into(), false, false, |frame| {
@@ -1214,11 +1238,7 @@ fn combomark_visual() -> impl UiNode {
             update.update_color_opt(color_key.update_var(&zng_wgt_text::FONT_COLOR_VAR, |&c| c));
         }
         _ => {}
-    });
-    zng_wgt_container::Container! {
-        child = visual;
-        child_align = Align::CENTER;
-    }
+    })
 }
 
 /// Switch toggle style.
@@ -1280,6 +1300,9 @@ fn switch_visual(parent_hovered: impl Var<bool>) -> impl UiNode {
             #[easing(0.ms())]
             background_color = zng_wgt_text::FONT_COLOR_VAR.map(|c| c.with_alpha(20.pct()));
         }
+        when #is_checked {
+            background_color = colors::ACCENT_COLOR_VAR.shade(-1);
+        }
     }
 }
 
@@ -1337,7 +1360,7 @@ fn radio_visual(parent_hovered: impl Var<bool>) -> impl UiNode {
         when *#is_checked {
             border = {
                 widths: 2,
-                sides: zng_wgt_text::FONT_COLOR_VAR.map(|c| c.with_alpha(20.pct()).into()),
+                sides: colors::ACCENT_COLOR_VAR.shade_into(-2),
             };
             #[easing(0.ms())]
             background_color = zng_wgt_text::FONT_COLOR_VAR;
diff --git a/crates/zng-wgt-tooltip/src/lib.rs b/crates/zng-wgt-tooltip/src/lib.rs
index e5b566c13..c60d1e56d 100644
--- a/crates/zng-wgt-tooltip/src/lib.rs
+++ b/crates/zng-wgt-tooltip/src/lib.rs
@@ -542,7 +542,7 @@ impl DefaultStyle {
         widget_set! {
             self;
             replace = true;
-            padding = (2, 4);
+            padding = (4, 6);
             corner_radius = 3;
             base_color = light_dark(rgb(235, 235, 235), rgb(20, 20, 20));
             background_color = colors::BASE_COLOR_VAR.rgba();
diff --git a/crates/zng-wgt-window/src/window_properties.rs b/crates/zng-wgt-window/src/window_properties.rs
index 66bc47532..290d79f24 100644
--- a/crates/zng-wgt-window/src/window_properties.rs
+++ b/crates/zng-wgt-window/src/window_properties.rs
@@ -90,6 +90,7 @@ set_properties! {
     modal: bool,
 
     color_scheme: Option<ColorScheme>,
+    accent_color: Option<LightDark>,
 
     frame_capture_mode: FrameCaptureMode,
 
diff --git a/crates/zng/src/button.rs b/crates/zng/src/button.rs
index e3df29c4d..1b8bbcc91 100644
--- a/crates/zng/src/button.rs
+++ b/crates/zng/src/button.rs
@@ -118,4 +118,4 @@
 //!
 //! See [`zng_wgt_button`] for the full widget API.
 
-pub use zng_wgt_button::{style_fn, Button, DefaultStyle, LightStyle, LinkStyle, BUTTON};
+pub use zng_wgt_button::{style_fn, Button, DefaultStyle, LightStyle, LinkStyle, PrimaryStyle, BUTTON};
diff --git a/crates/zng/src/lib.rs b/crates/zng/src/lib.rs
index 3671a02c8..eb9fc8f2e 100644
--- a/crates/zng/src/lib.rs
+++ b/crates/zng/src/lib.rs
@@ -911,14 +911,7 @@ mod defaults {
                 #[cfg(feature = "inspector")]
                 let child = zng_wgt_inspector::inspector(child, zng_wgt_inspector::live_inspector(true));
 
-                // setup COLOR_SCHEME_VAR for all windows, this is not done in `Window!` because
-                // WindowRoot is used directly by some headless renderers.
-                zng_wgt::node::with_context_var_init(child, zng_color::COLOR_SCHEME_VAR, || {
-                    use zng_ext_window::WINDOW_Ext as _;
-                    use zng_var::Var as _;
-
-                    zng_app::window::WINDOW.vars().actual_color_scheme().boxed()
-                })
+                child
             });
             tracing::debug!("defaults init, root_extender set");
 
diff --git a/examples/window/src/main.rs b/examples/window/src/main.rs
index 0215d9493..f171a4a3b 100644
--- a/examples/window/src/main.rs
+++ b/examples/window/src/main.rs
@@ -15,7 +15,6 @@ use zng::{
     layout::*,
     prelude::*,
     scroll::ScrollMode,
-    text::Strong,
     var::ArcVar,
     widget::{background_color, corner_radius, enabled, visibility, LineStyle},
     window::{native_dialog, FocusIndicator, FrameCaptureMode, FrameImageReadyArgs, WindowChangedArgs, WindowState},
@@ -760,8 +759,9 @@ fn close_dialog(mut windows: Vec<WindowId>, state: ArcVar<CloseState>) -> impl U
                         spacing = 4;
                         children = ui_vec![
                             Button! {
+                                style_fn = button::PrimaryStyle!();
                                 focus_on_init = true;
-                                child = Strong!("Close");
+                                child = Text!("Close");
                                 on_click = hn_once!(state, |_| {
                                     state.set(CloseState::Close);
                                     windows.retain(|w| WINDOWS.is_open(*w));


Test patch: