Theming
Rinch’s theme system is CSS-variable-based, Mantine-inspired, and designed to be extended rather than fought against. Enable it with the theme feature (auto-enabled by components):
rinch = { git = "...", features = ["desktop", "theme"] }
Quick Start
fn main() {
let theme = ThemeProviderProps {
primary_color: Some("cyan".into()),
default_radius: Some("md".into()),
dark_mode: false,
..Default::default()
};
run_with_theme("My App", 800, 600, app, theme);
}
That’s it. Every component picks up your colors, radius, and spacing through CSS variables. Toggle dark_mode: true and the whole UI flips. All semantic colors — body background, text, borders, placeholders — adjust automatically.
Use run() instead of run_with_theme() to get the default theme (blue primary, light mode, small radius).
ThemeProviderProps
| Prop | Type | Default | Description |
|---|---|---|---|
primary_color | Option<String> | "blue" | Primary color name |
primary_shade | Option<u8> | 6 | Shade index (0-9) |
default_radius | Option<String> | "sm" | Border radius (xs, sm, md, lg, xl) |
font_family | Option<String> | System fonts | Primary font stack |
font_family_monospace | Option<String> | System mono | Monospace font stack |
dark_mode | bool | false | Dark mode |
CSS Variables
The theme generates CSS custom properties you can use anywhere in your styles.
Colors
14 named palettes, each with 10 shades (0 = lightest, 9 = darkest):
var(--rinch-color-blue-0) /* Lightest blue */
var(--rinch-color-blue-5) /* Mid blue */
var(--rinch-color-blue-9) /* Darkest blue */
var(--rinch-primary-color) /* Primary at your chosen shade */
var(--rinch-primary-color-0) /* Lightest primary */
var(--rinch-primary-color-9) /* Darkest primary */
Available palettes: dark, gray, red, pink, grape, violet, indigo, blue, cyan, teal, green, lime, yellow, orange.
Semantic Colors
These flip automatically in dark mode:
var(--rinch-color-body) /* Background (#f8f9fa light, dark gray dark) */
var(--rinch-color-text) /* Primary text */
var(--rinch-color-dimmed) /* Secondary text */
var(--rinch-color-border) /* Borders */
var(--rinch-color-placeholder) /* Input placeholders */
Spacing
var(--rinch-spacing-xs) /* 10px */
var(--rinch-spacing-sm) /* 12px */
var(--rinch-spacing-md) /* 16px */
var(--rinch-spacing-lg) /* 20px */
var(--rinch-spacing-xl) /* 32px */
Border Radius
var(--rinch-radius-xs) /* 2px */
var(--rinch-radius-sm) /* 4px */
var(--rinch-radius-md) /* 8px */
var(--rinch-radius-lg) /* 16px */
var(--rinch-radius-xl) /* 32px */
var(--rinch-radius-default) /* Whatever you set in ThemeProviderProps */
Typography
var(--rinch-font-size-xs) /* 12px */
var(--rinch-font-size-sm) /* 14px */
var(--rinch-font-size-md) /* 16px */
var(--rinch-font-size-lg) /* 18px */
var(--rinch-font-size-xl) /* 20px */
var(--rinch-line-height-xs) /* 1.4 */
var(--rinch-line-height-md) /* 1.55 */
var(--rinch-font-family)
var(--rinch-font-family-monospace)
/* Heading sizes (h1-h6) */
var(--rinch-h1-font-size)
var(--rinch-h1-line-height)
var(--rinch-h1-font-weight)
Shadows
var(--rinch-shadow-xs)
var(--rinch-shadow-sm)
var(--rinch-shadow-md)
var(--rinch-shadow-lg)
var(--rinch-shadow-xl)
Using Theme Variables in Your Styles
#![allow(unused)]
fn main() {
rsx! {
div { style: "
background: var(--rinch-color-body);
padding: var(--rinch-spacing-md);
border-radius: var(--rinch-radius-default);
box-shadow: var(--rinch-shadow-sm);
",
h1 { style: "color: var(--rinch-primary-color);", "Themed!" }
p { style: "color: var(--rinch-color-dimmed);", "Using theme variables." }
}
}
}
Or use CSS shorthand props, which resolve scale values automatically:
#![allow(unused)]
fn main() {
rsx! {
div { p: "md", m: "sm", // -> var(--rinch-spacing-md), var(--rinch-spacing-sm)
"Shorthand is nicer"
}
}
}
Extending the Theme
Scoped Overrides
CSS variables cascade. Override them on a container div and everything inside picks up the change:
#![allow(unused)]
fn main() {
rsx! {
div { style: "
--rinch-primary-color: #ff6b6b;
--rinch-radius-default: 0px;
",
// Red primary, sharp corners — only inside this div
Button { "I'm red and sharp" }
TextInput { placeholder: "Me too" }
}
}
}
This is how you create themed sections, cards with different accent colors, or “danger zone” areas with red buttons — without changing the global theme.
Replacing the Theme Entirely
The theme is just CSS. If you hate it, replace it:
#![allow(unused)]
fn main() {
use rinch::theme::{Theme, ColorName};
let theme = Theme::builder()
.primary_color(ColorName::Cyan)
.dark_mode(true)
.build();
// Generate the CSS string and do whatever you want with it
let css = rinch_theme::generate_theme_css(&theme);
}
Or ignore the theme system entirely and write your own CSS variables, your own classes, your own inline styles. Rinch components read from --rinch-* variables. If those variables exist, components use them. If they don’t, components fall back to hardcoded defaults. You’re never locked in.
Dark Mode
#![allow(unused)]
fn main() {
let theme = ThemeProviderProps {
dark_mode: true,
..Default::default()
};
}
In dark mode, semantic colors flip:
--rinch-color-bodybecomes dark gray--rinch-color-textbecomes light- Component backgrounds, borders, and hover states all adjust
For dynamic dark mode toggling (e.g., a switch in your app), use dark_mode_fn on WASM or re-create the theme and call update_theme() on desktop.
Color Palette Reference
| Color | Shade 0 (lightest) | Shade 6 (primary) | Notes |
|---|---|---|---|
dark | #C1C2C5 | #1A1B1E | Dark grays |
gray | #f8f9fa | #868e96 | gray-0 = default body background |
red | #fff5f5 | #fa5252 | |
pink | #fff0f6 | #e64980 | |
grape | #f8f0fc | #be4bdb | |
violet | #f3f0ff | #7950f2 | |
indigo | #edf2ff | #4c6ef5 | |
blue | #e7f5ff | #228be6 | Default primary |
cyan | #e3fafc | #15aabf | |
teal | #e6fcf5 | #12b886 | |
green | #ebfbee | #40c057 | |
lime | #f4fce3 | #82c91e | |
yellow | #fff9db | #fab005 | |
orange | #fff4e6 | #fd7e14 |
Gotcha:
--rinch-color-gray-0is#f8f9fa, which matches the default body background. If you use it as a card background, it’ll be invisible. Usegray-1or higher for visible backgrounds.