# How to Customize the Theme System with Qt Stylesheets in Fincept

> Customize Fincept's theme system using Qt stylesheets QSS. Learn how ThemeManager generates global QSS from ThemeTokens for runtime UI customization without recompiling code.

- Repository: [Fincept Corporation/FinceptTerminal](https://github.com/Fincept-Corporation/FinceptTerminal)
- Tags: how-to-guide
- Published: 2026-04-20

---

**Fincept's theme system uses a token-based architecture where ThemeManager generates global QSS from ThemeTokens and applies it via `qApp->setStyleSheet()`, allowing runtime customization without recompiling UI code.**

The FinceptTerminal repository provides a sophisticated theming engine built on Qt's stylesheet (QSS) system that enables complete UI customization through centralized tokens. By leveraging the singleton `ThemeManager` and token-based color helpers defined in `fincept-qt/src/ui/theme/`, developers can modify application appearance, add new color palettes, or create entirely new themes while keeping widget code clean and maintainable.

## Understanding Fincept's Theme Architecture

Fincept implements a **centralized token system** that separates visual values from UI implementation. The architecture follows a strict pipeline:

1. **[`ThemeTokens.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/ThemeTokens.h)** defines the `ThemeTokens` struct containing every visual property (colors, fonts, spacing) as plain data members like `const char* bg_base` or `int font_size_base`.
2. **`ThemeManager`** (singleton) stores the active token set in `current_` and generates the global QSS string through `build_global_qss()`.
3. **Token helpers** in [`Theme.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/Theme.h) provide `ColorToken` and `fonts` objects that resolve live values lazily via `operator const char*`, ensuring widgets always read current theme data.
4. **Global application** occurs through `qApp->setStyleSheet()`, followed by emission of the `theme_changed(const ThemeTokens&)` signal to notify custom-painted widgets.

The Advanced Docking System (ADS) receives separate styling through `ThemeManager::build_ads_qss()` (lines 86-104 in [`ThemeManager.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/ThemeManager.cpp)), ensuring dock widgets match the application theme.

## Adding Custom Color Tokens

To introduce a new **purple accent color** for custom UI elements:

First, extend the token structure in [`fincept-qt/src/ui/theme/ThemeTokens.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/ui/theme/ThemeTokens.h):

```cpp
struct ThemeTokens {
    // ... existing tokens ...
    const char* purple;  // new token
};

```

Next, populate the value in [`fincept-qt/src/ui/theme/ThemeManager.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/ui/theme/ThemeManager.cpp):

```cpp
const ThemeTokens THEME_OBSIDIAN = {
    // ... existing colors ...
    .purple = "#7e22ce",
};

```

Finally, expose the token via [`fincept-qt/src/ui/theme/Theme.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/ui/theme/Theme.h):

```cpp
namespace colors {
    // ... existing tokens ...
    inline constexpr ColorToken PURPLE{&ThemeTokens::purple};
}

```

You can now use the token in any stylesheet builder:

```cpp
QString customButtonStyle() {
    return QString(
        "QPushButton { background: %1; color: %2; border: 1px solid %3; }"
        "QPushButton:hover { background: %4; }"
    )
    .arg(colors::PURPLE(), colors::WHITE(), colors::BORDER_DIM(), colors::PURPLE());
}

```

## Creating Widget-Specific Stylesheet Overrides

For **targeted style changes** without modifying the global theme, concatenate custom rules to the existing application stylesheet. This approach preserves the global theme while overriding specific widgets:

```cpp
// In your widget implementation (e.g., NewsScreen.cpp)
QString newsCommandBarOverride = QString(
    "#newsCommandBar { background: %1; color: %2; border-bottom: 1px solid %3; }"
).arg(colors::PURPLE(), colors::WHITE(), colors::BORDER_BRIGHT());

// Append to existing global stylesheet
qApp->setStyleSheet(qApp->styleSheet() + newsCommandBarOverride);

```

Because Qt evaluates stylesheets in order, later rules take precedence. This technique allows module-specific styling while maintaining the token system for color values.

## Implementing New Theme Presets

Fincept currently ships with the **Obsidian** theme. To add a **light theme** (e.g., "Alabaster"), define a new constant in [`ThemeManager.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/ThemeManager.cpp):

```cpp
const ThemeTokens THEME_ALABASTER = {
    .name = "Alabaster",
    .bg_base = "#ffffff",
    .bg_surface = "#f5f5f5",
    .bg_raised = "#e0e0e0",
    .bg_hover = "#dddddd",
    .border_dim = "#c0c0c0",
    .border_med = "#a0a0a0",
    .border_bright = "#808080",
    .text_primary = "#000000",
    .text_secondary = "#333333",
    .text_tertiary = "#666666",
    .text_dim = "#999999",
    .accent = "#0066cc",
    .accent_dim = "#003366",
    .text_on_accent = "#ffffff",
    .icon_dim = "#444444",
    .icon_hover = "#111111",
    .positive = "#28a745",
    .positive_dim = "#1e7e34",
    .negative = "#dc3545",
    .negative_dim = "#a71d2a",
    .warning = "#ffc107",
    .info = "#17a2b8",
    .cyan = "#17a2b8",
    .accent_bg = "#e6f0ff",
    .positive_bg = "#e6ffea",
    .negative_bg = "#ffe6e6",
    .row_alt = "#fafafa",
    .font_family = "'Consolas','Courier New',monospace",
    .font_size_base = 14,
    .chart_colors = {"#0066cc", "#17a2b8", "#28a745", "#dc3545", "#ffc107", "#6c757d"},
};

```

Then extend the theme switching logic:

```cpp
void ThemeManager::apply_theme(const QString& name) {
    if (name == "Alabaster") 
        current_ = THEME_ALABASTER;
    else 
        current_ = THEME_OBSIDIAN;
    
    rebuild_and_apply();  // Regenerates QSS and applies to qApp
}

```

Users can now switch themes at runtime:

```cpp
ThemeManager::instance().apply_theme("Alabaster");

```

## Adjusting Content Density

Fincept supports **Compact**, **Default**, and **Comfortable** density modes that affect padding tokens. Modify density via:

```cpp
ThemeManager::instance().apply_density("Compact");  // 2px padding

```

The density setting updates spacing tokens (`%7` and `%8` placeholders in `build_global_qss`) without requiring code changes in individual widgets. For one-off padding adjustments in custom components, use hardcoded values in your local QSS snippets rather than the global density tokens.

## Handling Theme Changes in Custom-Painted Widgets

Widgets that perform custom painting must **connect to the theme change signal** to trigger repaints when colors update:

```cpp
// MyChartWidget.cpp
MyChartWidget::MyChartWidget(QWidget* parent) : QWidget(parent) {
    connect(&fincept::ui::ThemeManager::instance(),
            &fincept::ui::ThemeManager::theme_changed,
            this, [this](const fincept::ui::ThemeTokens&) { 
                update();  // Trigger repaint
            });
}

void MyChartWidget::paintEvent(QPaintEvent*) {
    QPainter p(this);
    
    // Query live tokens directly
    p.fillRect(rect(), colors::BG_SURFACE());
    p.setPen(colors::POSITIVE());
    
    // Draw data series...
}

```

This pattern ensures custom graphics stay synchronized with the active theme, as implemented in [`fincept-qt/src/ui/widgets/StatusBar.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/ui/widgets/StatusBar.cpp).

## Summary

- **Token pipeline**: Visual values flow from `ThemeTokens` → `ThemeManager` → global QSS string → `qApp->setStyleSheet()`.
- **Key files**: Define tokens in [`ThemeTokens.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/ThemeTokens.h), expose via [`Theme.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/Theme.h), and build QSS in [`ThemeManager.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/ThemeManager.cpp) or [`StyleSheets.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/StyleSheets.cpp).
- **Runtime switching**: Call `ThemeManager::instance().apply_theme()` to switch presets without restarting.
- **Widget integration**: Connect to `theme_changed` signals for custom-painted widgets, or concatenate specific QSS rules for one-off overrides.
- **ADS support**: Docking system styling is handled separately via `build_ads_qss()` in [`ThemeManager.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/ThemeManager.cpp) (lines 86-104).

## Frequently Asked Questions

### Where are theme tokens defined in the Fincept source code?

Theme tokens are defined in [`fincept-qt/src/ui/theme/ThemeTokens.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/ui/theme/ThemeTokens.h) as a plain struct with color, font, and spacing members. The active values are stored in `ThemeManager` and accessed through token helpers like `colors::BG_BASE()` defined in [`fincept-qt/src/ui/theme/Theme.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/ui/theme/Theme.h).

### How do I apply a custom Qt stylesheet to a single widget without affecting the global theme?

Concatenate your custom QSS to the existing application stylesheet using `qApp->setStyleSheet(qApp->styleSheet() + customQss)`, or call `setStyleSheet()` directly on the specific widget. Use the token helpers (e.g., `colors::PURPLE()`) to maintain consistency with the active theme.

### Can I switch themes at runtime in FinceptTerminal?

Yes. Call `ThemeManager::instance().apply_theme("ThemeName")` to switch presets dynamically. The method updates the internal `current_` token set, rebuilds the global QSS via `rebuild_and_apply()`, and emits `theme_changed` to notify all listening widgets to refresh their appearance.

### How do custom-painted widgets update when the theme changes?

Connect to the `ThemeManager::theme_changed(const ThemeTokens&)` signal in your widget constructor. When emitted, call `update()` to trigger a repaint, then query current colors through `ThemeManager::instance().tokens()` or the `colors::` helper functions in your `paintEvent` handler.