native-theme
Cross-platform native theme detection and loading for Rust GUI applications.
Overview
native-theme provides a toolkit-agnostic theme data model with 22 semantic color roles, 25 per-widget resolved themes, bundled TOML presets, and optional OS theme reading. It gives your Rust GUI application access to consistent, structured theme data regardless of which toolkit you use.
Quick Start
Add the dependency:
Load a preset and access theme fields:
let theme = preset.unwrap;
let dark = theme.dark.as_ref.unwrap;
let accent = dark.defaults.accent.unwrap; // Option<Rgba>
let bg = dark.defaults.background.unwrap; // Option<Rgba>
// Convert to f32 for toolkits that use normalized colors
let = accent.to_f32_array;
For fully resolved themes (all fields guaranteed populated), use the resolve + validate pipeline:
let mut variant = theme.pick_variant.unwrap.clone;
variant.resolve; // Apply inheritance rules
let resolved = variant.validate.unwrap; // -> ResolvedThemeVariant
let accent = resolved.defaults.accent; // Rgba (not Option)
Preset Workflow
Start with a bundled preset, then layer sparse user overrides on top.
The merge() method fills in only the fields present in the overlay,
leaving everything else from the base preset intact.
use ThemeSpec;
let mut theme = preset.unwrap;
let user_overrides = from_toml.unwrap;
theme.merge;
Runtime Workflow
Use from_system() to read the current OS theme at runtime. It returns a
SystemTheme with both light and dark ResolvedThemeVariant variants:
use SystemTheme;
let system = from_system.unwrap;
let active = system.active; // &ResolvedThemeVariant for current OS mode
let light = system.pick; // Explicit variant selection
let is_dark = system.is_dark; // OS dark mode state
Apply user overrides on top of the OS theme:
let customized = system.with_overlay_toml(r#"
[light.defaults]
accent = "#ff6600"
"#).unwrap();
Platform behavior:
- Linux (KDE): Reads live theme from
~/.config/kdeglobals(requireskdefeature). - Linux (GNOME/other): Returns the bundled Adwaita preset. For live
portal data (accent color, dark mode preference, contrast setting), call
from_gnome().awaitdirectly -- this requires theportal-tokioorportal-async-iofeature. - macOS: Reads system appearance via NSAppearance (requires
macosfeature). - Windows: Reads accent colors and system metrics (requires
windowsfeature). - Other platforms: Returns
Error::Unsupported.
system_is_dark() provides a lightweight cached check for the OS dark mode
preference on all platforms (Linux, macOS, and Windows), without running the
full theme reader pipeline.
Toolkit Connectors
Official connector crates handle the full mapping from ThemeSpec to
toolkit-specific theme types:
native-theme-iced-- iced toolkit connector with Catalog support for all built-in widget stylesnative-theme-gpui-- gpui + gpui-component toolkit connector
For other toolkits, map ResolvedThemeVariant fields directly. After
resolve + validate, all fields are guaranteed populated:
let resolved = variant.validate.unwrap; // ResolvedThemeVariant
let bg = resolved.defaults.background; // Rgba
let accent = resolved.defaults.accent; // Rgba
let font = &resolved.defaults.font.family; // &String
let radius = resolved.defaults.radius; // f32
Custom Icon Roles
Apps can define domain-specific icons (e.g., play/pause, git-branch, thermometer)
via TOML definitions and the native-theme-build
crate. Generated icons integrate with the same loading pipeline as built-in
IconRole variants, so they work across all icon sets (Material, Lucide,
freedesktop, SF Symbols, Segoe Fluent).
Workflow:
- Define icons in a TOML file with per-set name mappings and bundled SVGs
- Add
native-theme-buildas a build dependency - Call
generate_icons()inbuild.rsto generate a Rust enum implementingIconProvider - Include the generated code and use
load_custom_icon()to load icons at runtime
// build.rs
use UnwrapOrExit;
generate_icons
.unwrap_or_exit
.emit_cargo_directives;
// src/lib.rs
include!;
use ;
let icon = load_custom_icon;
See the native-theme-build docs for the
full TOML schema, builder API, and DE-aware mapping support.
Animated Icons
loading_indicator() returns a platform-native loading spinner animation
matching the requested icon set (Material, Lucide, macOS, Windows, Adwaita,
or a freedesktop theme's process-working animation):
use ;
if let Some = loading_indicator
Toolkit connectors provide playback helpers:
animated_frames_to_image_sources() and
with_spin_animation() for gpui,
animated_frames_to_svg_handles() and
spin_rotation_radians() for iced.
Feature Flags
| Feature | Enables | Platform |
|---|---|---|
kde |
from_kde() sync KDE reader |
Linux |
portal |
Base for GNOME portal reader | Linux |
portal-tokio |
from_gnome() with tokio runtime |
Linux |
portal-async-io |
from_gnome() with async-io runtime |
Linux |
windows |
from_windows() Windows reader |
Windows |
macos |
from_macos() macOS reader |
macOS |
system-icons |
Platform icon theme lookup with bundled fallback | All |
material-icons |
Bundle Material Symbols SVGs | All |
lucide-icons |
Bundle Lucide SVGs | All |
svg-rasterize |
SVG-to-RGBA rasterization via resvg | All |
By default, no features are enabled. The preset API (ThemeSpec::preset(),
ThemeSpec::from_toml(), ThemeSpec::from_file(), ThemeSpec::list_presets(),
.to_toml()) works without any features.
Available Presets
All presets are embedded at compile time via include_str!() and available
through ThemeSpec::preset("name"). Each provides both light and dark variants.
Platform: kde-breeze, adwaita, windows-11, macos-sonoma, material, ios
Community: catppuccin-latte, catppuccin-frappe, catppuccin-macchiato,
catppuccin-mocha, nord, dracula, gruvbox, solarized, tokyo-night,
one-dark
Use ThemeSpec::list_presets() to get all 16 names programmatically, or
list_presets_for_platform() for only those appropriate on the current OS.
TOML Format
Theme files use TOML. All fields are optional -- omit any you don't need.
See the ThemeSpec::from_toml()
documentation for the full format reference.
License
Licensed under either of
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be triple licensed as above, without any additional terms or conditions.