# The Styling System
`standout-render` uses a theme-based styling system where named styles are applied to content through bracket notation tags. Instead of embedding ANSI codes in your templates, you define semantic style names (`error`, `title`, `muted`) and let the theme decide the visual representation.
This separation provides several benefits:
- **Readability**: Templates use meaningful names, not escape codes
- **Maintainability**: Change colors in one place, update everywhere
- **Adaptability**: Themes can respond to light/dark mode automatically
- **Consistency**: Enforce visual hierarchy across your application
---
## Themes
A `Theme` is a named collection of styles. Each style maps a name (like `title` or `error`) to visual attributes (bold cyan, dim red, etc.).
### CSS Themes
Define styles in standard CSS syntax — a subset of CSS Level 3 tailored for terminals:
```css
/* theme.css */
.title {
color: cyan;
font-weight: bold;
}
.error {
color: red;
font-weight: bold;
}
.muted {
opacity: 0.5; /* maps to dim */
}
.success {
color: green;
}
/* Shorthand works too */
.warning { color: yellow; }
```
Load CSS themes:
```rust
use standout_render::Theme;
let theme = Theme::from_css(css_content)?;
let theme = Theme::from_css_file("styles/theme.css")?;
```
CSS gives you syntax highlighting in editors, linting tools, and familiarity for web developers.
### Programmatic Themes
Build themes in code using the builder pattern:
```rust
use standout_render::Theme;
use console::Style;
let theme = Theme::new()
.add("title", Style::new().bold().cyan())
.add("error", Style::new().red().bold())
.add("muted", Style::new().dim())
.add("success", Style::new().green());
```
> **Legacy format:** YAML themes are still supported via `Theme::from_yaml()` and `Theme::from_yaml_file()`. CSS is the recommended format for all new projects.
---
## Supported Attributes
### Colors
| `fg` | `color` | Foreground (text) color |
| `bg` | `background` | Background color |
### Color Formats
```css
/* Named colors (16 ANSI colors) */
.example { color: red; }
.example { color: green; }
.example { color: cyan; }
.example { color: magenta; }
.example { color: yellow; }
.example { color: white; }
.example { color: black; }
/* Bright variants */
.example { color: bright_red; }
.example { color: bright_green; }
/* 256-color palette (0-255) */
.example { color: 208; }
/* RGB hex */
.example { color: #ff6b35; }
.example { color: #f63; } /* shorthand */
/* Theme-relative cube colors */
.example { color: cube(60%, 20%, 0%); }
```
Cube colors express a position in a color cube whose 8 corners are the base ANSI
colors of the user's terminal theme. The same `cube(60%, 20%, 0%)` produces earthy
tones in Gruvbox, pastels in Catppuccin, and muted shades in Solarized.
Interpolation is done in CIE LAB space for perceptually uniform gradients.
Attach a palette to a theme with `Theme::with_palette()`.
### Text Attributes
| `font-weight: bold` | Bold text |
| `opacity: 0.5` | Dimmed/faint text |
| `font-style: italic` | Italic text |
| `text-decoration: underline` | Underlined text |
| `text-decoration: blink` | Blinking text |
| `text-decoration: line-through` | Strikethrough |
| `visibility: hidden` | Hidden text |
---
## Adaptive Styles (Light/Dark Mode)
Terminal applications run in both light and dark environments. A color that looks great on a dark background may be illegible on a light one. `standout-render` solves this with adaptive styles.
### How It Works
Instead of defining separate "light theme" and "dark theme" files, you define mode-specific overrides at the style level:
```css
.panel {
font-weight: bold;
color: gray; /* Default/fallback */
}
@media (prefers-color-scheme: light) {
.panel { color: black; } /* Override for light mode */
}
@media (prefers-color-scheme: dark) {
.panel { color: white; } /* Override for dark mode */
}
```
When resolving `panel` in dark mode:
1. Start with base attributes (`bold`, `gray`)
2. Merge dark overrides (`white` replaces `gray`)
3. Result: bold white text
This is efficient: most styles (bold, italic, semantic colors like green/red) look fine in both modes. Only a handful need adjustment—typically foreground colors for contrast.
### Programmatic API
```rust
use standout_render::Theme;
use console::{Style, Color};
let theme = Theme::new()
.add_adaptive(
"panel",
Style::new().bold(), // Base (shared)
Some(Style::new().fg(Color::Black)), // Light mode
Some(Style::new().fg(Color::White)), // Dark mode
);
```
### Color Mode Detection
`standout-render` auto-detects the OS color scheme:
```rust
use standout_render::{detect_color_mode, ColorMode};
let mode = detect_color_mode();
match mode {
ColorMode::Light => println!("Light mode"),
ColorMode::Dark => println!("Dark mode"),
}
```
Override for testing:
```rust
use standout_render::set_theme_detector;
---
## Style Aliasing
Aliases let semantic names resolve to visual styles. This is useful when multiple concepts share the same appearance:
```rust
let theme = Theme::new()
// Define the visual style once
.add("title", Style::new().bold().cyan())
// Aliases — pass a string to reference another style by name
.add("commit-message", "title")
.add("section-header", "title")
.add("heading", "title");
```
Now `[commit-message]`, `[section-header]`, and `[heading]` all render identically to `[title]`.
Benefits:
- Templates use meaningful, context-specific names
- Visual changes propagate automatically
- Refactoring visual design doesn't touch templates
Aliases can chain: `a` → `b` → `c` → concrete style. Cycles are detected and rejected at load time.
---
## Unknown Style Tags
When a template references a style not defined in the theme, `standout-render` handles it gracefully:
| `Term` | Unknown tags get a `?` marker: `[unknown?]text[/unknown?]` |
| `Text` | Tags stripped (plain text) |
| `TermDebug` | Tags preserved as-is |
The `?` marker helps catch typos during development without crashing production apps.
### Validation
For strict checking at startup:
```rust
use standout_render::validate_template;
let errors = validate_template(template, &sample_data, &theme);
if !errors.is_empty() {
for error in errors {
eprintln!("Unknown style: {}", error.tag_name);
}
std::process::exit(1);
}
```
---
## Built-in Styles
`Theme::default()` includes adaptive styles for alternating table row backgrounds. These are used automatically when you pass `row_styles=true` (or a tint name) to the `table()` template function.
| `table_row_even` | Even rows — no background (transparent) |
| `table_row_odd` | Odd rows — subtle gray background shift |
| `table_row_even_gray` | Alias for `table_row_even` |
| `table_row_odd_gray` | Alias for `table_row_odd` |
| `table_row_even_blue` | Even rows for blue tint |
| `table_row_odd_blue` | Odd rows — dark navy / lavender bg |
| `table_row_even_red` | Even rows for red tint |
| `table_row_odd_red` | Odd rows — dark crimson / blush bg |
| `table_row_even_green` | Even rows for green tint |
| `table_row_odd_green` | Odd rows — dark forest / mint bg |
| `table_row_even_purple` | Even rows for purple tint |
| `table_row_odd_purple` | Odd rows — dark plum / lilac bg |
All odd-row styles are adaptive: they resolve to a dark variant when the terminal is in dark mode, and a light variant in light mode. You can override any of these by defining the same style name in your theme.
---
## Best Practices
### Semantic, Presentation, and Visual Layers
Organize your styles in three conceptual layers:
**1. Visual primitives** (low-level appearance):
```css
._cyan-bold { color: cyan; font-weight: bold; }
._dim { opacity: 0.5; }
._red-bold { color: red; font-weight: bold; }
```
**2. Presentation roles** (UI concepts — use aliases in code):
```rust
theme.add("heading", "_cyan-bold")
.add("secondary", "_dim")
.add("danger", "_red-bold");
```
**3. Semantic names** (domain concepts — aliases to presentation):
```rust
// In templates, use these
theme.add("task-title", "heading")
.add("task-status-done", "success")
.add("task-status-pending", "warning")
.add("error-message", "danger");
```
Templates use semantic names (`task-title`), which resolve to presentation roles (`heading`), which resolve to visual primitives (`_cyan-bold`).
This layering lets you:
- Refactor visuals without touching templates
- Maintain consistency across domains
- Document the purpose of each style
### Naming Conventions
```css
/* Good: descriptive, semantic */
.error-message { ... }
.file-path { ... }
.command-name { ... }
/* Avoid: visual descriptions */
.red-text { ... }
.bold-cyan { ... }
```
### Keep Themes Focused
One theme per "look". Don't mix concerns:
```text
styles/
├── default.css # your app's default look
├── colorblind.css # accessibility variant
└── monochrome.css # for piped output
```
---
## API Reference
### Theme Creation
```rust
// From CSS string
let theme = Theme::from_css(css_str)?;
// From CSS file (hot reload in debug)
let theme = Theme::from_css_file(path)?;
// Empty theme (for programmatic building)
let theme = Theme::new();
// Legacy: YAML is still supported
let theme = Theme::from_yaml(yaml_str)?;
let theme = Theme::from_yaml_file(path)?;
```
### Adding Styles
```rust
// Static style
theme.add("name", Style::new().bold());
// Adaptive style
theme.add_adaptive("name", base_style, light_override, dark_override);
// Alias
theme.add("alias", "target_style");
```
### Resolving Styles
```rust
// Get resolved style for current color mode
let style: Option<Style> = theme.get("title");
// Get style for specific mode
let style = theme.get_for_mode("panel", ColorMode::Dark);
```
### Color Mode
```rust
use standout_render::{detect_color_mode, set_theme_detector, ColorMode};
// Auto-detect
let mode = detect_color_mode();
// Override (for testing)