# How to Create Custom Qt6 Widgets with the Obsidian Design System

> Learn to create custom Qt6 widgets using the Obsidian design system. Leverage token-based helpers and apply styles for a consistent look and feel. Enhance your Qt applications today.

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

---

**To create custom Qt6 widgets with the Obsidian design system, derive from a Qt base class, use token-based helpers from [`Theme.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/Theme.h) (e.g., `colors::ACCENT`), and apply styles via [`StyleSheets.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/StyleSheets.cpp) helpers or manual palette updates.**

FinceptTerminal implements its complete Obsidian design system through code-generated **design tokens** that centralize every color, font, and spacing value. By leveraging the token architecture in `fincept-qt/src/ui/theme/`, developers can build custom widgets that automatically adopt the dark-themed aesthetic and respond dynamically to runtime theme changes.

## Understanding the Obsidian Design Token Architecture

The Obsidian design system is built on four foundational components that separate definition from implementation:

- **[`ThemeTokens.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/ThemeTokens.h)** – Defines the `ThemeTokens` struct containing raw values for every color, font size, and spacing constant used across the application.
- **[`Theme.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/Theme.h)** – Exposes lightweight `ColorToken` and `FontToken` wrappers through the `colors::` and `fonts::` namespaces. These tokens resolve to current runtime values on access.
- **[`ThemeManager.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/ThemeManager.cpp)** – Maintains the active theme instance and emits the `theme_changed` signal whenever the user adjusts density, fonts, or color presets.
- **[`StyleSheets.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/StyleSheets.cpp)** – Provides pre-baked QSS generator functions like `styles::button()` and `styles::card_frame()` that embed token references.

Because tokens resolve dynamically, a widget painted with `colors::BG_BASE` automatically updates if the user switches from Obsidian to a future light-mode variant.

## Step-by-Step Implementation Guide

### Include the Token System Headers

Start by including the runtime token accessors and stylesheet helpers:

```cpp
#include "ui/theme/Theme.h"
#include "ui/theme/StyleSheets.h"

```

The `colors::` namespace provides constexpr instances such as `colors::TEXT_PRIMARY`, `colors::ACCENT`, and `colors::BORDER`, while `fonts::` exposes typography tokens. These objects implicitly convert to `QColor`, `QString`, or `int` as needed.

### Derive from Qt Base Classes

Create your widget by inheriting from standard Qt6 classes like `QWidget`, `QFrame`, or `QPushButton`. For interactive elements, `QPushButton` provides the necessary event handling infrastructure.

### Apply Token-Based Styling

Use one of two approaches to apply the Obsidian aesthetic:

**Stylesheet approach** – Call helpers from [`StyleSheets.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/StyleSheets.cpp):

```cpp
setStyleSheet(styles::button());  // Embeds %9 (accent), %10 (secondary text), etc.

```

**Palette approach** – Modify the widget palette directly:

```cpp
QPalette p = palette();
p.setColor(QPalette::Window, colors::BG_BASE);
p.setColor(QPalette::ButtonText, colors::TEXT_PRIMARY);
setPalette(p);

```

### Handle Runtime Theme Changes

Connect to `ThemeManager::theme_changed` to refresh styling when the global theme updates:

```cpp
connect(&ThemeManager::instance(), &ThemeManager::theme_changed,
        this, &YourWidget::applyStyle);

```

The `applyStyle()` slot should re-apply all token-dependent colors to ensure consistency.

## Complete Code Examples

### Minimal ObsidianButton Implementation

This example demonstrates a `QPushButton` derivative that uses the built-in button stylesheet and updates its palette when themes change.

**Header** ([`src/ui/widgets/ObsidianButton.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/src/ui/widgets/ObsidianButton.h)):

```cpp
#pragma once
#include <QPushButton>
#include "ui/theme/Theme.h"

namespace fincept::ui {

class ObsidianButton : public QPushButton {
    Q_OBJECT
public:
    explicit ObsidianButton(const QString& text, QWidget* parent = nullptr);

private:
    void applyStyle();
};

} // namespace fincept::ui

```

**Implementation** ([`src/ui/widgets/ObsidianButton.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/src/ui/widgets/ObsidianButton.cpp)):

```cpp
#include "ObsidianButton.h"
#include "ui/theme/StyleSheets.h"

namespace fincept::ui {

ObsidianButton::ObsidianButton(const QString& text, QWidget* parent)
    : QPushButton(text, parent) {
    setStyleSheet(styles::button());
    applyStyle();

    connect(&ThemeManager::instance(), &ThemeManager::theme_changed,
            this, &ObsidianButton::applyStyle);
}

void ObsidianButton::applyStyle() {
    QPalette p = palette();
    p.setColor(QPalette::ButtonText, colors::TEXT_PRIMARY);
    setPalette(p);
}

} // namespace fincept::ui

```

The `styles::button()` function references token placeholders that `ThemeManager` populates from the active `ThemeTokens` structure.

### Complex InfoCard with Dynamic Updates

This `QFrame` derivative mimics the built-in `Card` class from [`fincept-qt/src/ui/widgets/Card.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/ui/widgets/Card.cpp), demonstrating manual layout construction with token-aware styling.

**Header** ([`src/ui/widgets/InfoCard.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/src/ui/widgets/InfoCard.h)):

```cpp
#pragma once
#include <QFrame>
#include <QVBoxLayout>
#include <QLabel>
#include "ui/theme/Theme.h"

namespace fincept::ui {

class InfoCard : public QFrame {
    Q_OBJECT
public:
    explicit InfoCard(const QString& title, QWidget* parent = nullptr);
    void setInfo(const QString& label, const QString& value);

private:
    QLabel* titleLabel_;
    QLabel* label_;
    QLabel* value_;
    QVBoxLayout* contentLayout_;

    void applyStyle();
};

} // namespace fincept::ui

```

**Implementation** ([`src/ui/widgets/InfoCard.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/src/ui/widgets/InfoCard.cpp)):

```cpp
#include "InfoCard.h"
#include "ui/theme/StyleSheets.h"

namespace fincept::ui {

InfoCard::InfoCard(const QString& title, QWidget* parent)
    : QFrame(parent) {
    setStyleSheet(styles::card_frame());
    auto* vl = new QVBoxLayout(this);
    vl->setContentsMargins(0,0,0,0);
    vl->setSpacing(0);

    // Title bar construction
    auto* titleBar = new QWidget;
    titleBar->setFixedHeight(28);
    titleBar->setStyleSheet("background: transparent;");
    auto* hl = new QHBoxLayout(titleBar);
    hl->setContentsMargins(8,0,4,0);
    titleLabel_ = new QLabel(title);
    titleLabel_->setStyleSheet(styles::card_title());
    hl->addWidget(titleLabel_);
    hl->addStretch();
    vl->addWidget(titleBar);

    // Separator using border token
    auto* sep = new QFrame;
    sep->setFixedHeight(1);
    sep->setStyleSheet(QString("background: %1; border: none;")
                       .arg(colors::BORDER()));
    vl->addWidget(sep);

    // Content area
    auto* content = new QWidget;
    content->setStyleSheet("background: transparent;");
    contentLayout_ = new QVBoxLayout(content);
    contentLayout_->setContentsMargins(8,4,8,8);
    label_ = new QLabel;
    value_ = new QLabel;
    label_->setStyleSheet(QString("color: %1;").arg(colors::TEXT_SECONDARY));
    value_->setStyleSheet(QString("color: %1; font-weight: 600;").arg(colors::TEXT_PRIMARY));
    contentLayout_->addWidget(label_);
    contentLayout_->addWidget(value_);
    vl->addWidget(content, 1);

    applyStyle();

    connect(&ThemeManager::instance(), &ThemeManager::theme_changed,
            this, &InfoCard::applyStyle);
}

void InfoCard::setInfo(const QString& label, const QString& value) {
    label_->setText(label);
    value_->setText(value);
}

void InfoCard::applyStyle() {
    titleLabel_->setStyleSheet(QString("color: %1; background: %2;")
                               .arg(colors::TEXT_ON_ACCENT)
                               .arg(colors::ACCENT()));
}

} // namespace fincept::ui

```

The `InfoCard` class uses `colors::TEXT_SECONDARY` for metadata and `colors::ACCENT` for highlights, ensuring consistency with the Obsidian palette defined in [`ThemeTokens.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/ThemeTokens.h).

## Key Source Files in the Obsidian Design System

Refer to these implementation files when building custom widgets:

- **[`fincept-qt/src/ui/theme/ThemeTokens.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/ui/theme/ThemeTokens.h)** – C++ struct enumerating all design tokens (colors, fonts, spacing).
- **[`fincept-qt/src/ui/theme/Theme.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/ui/theme/Theme.h)** – `ColorToken` and `FontToken` wrappers with `operator QColor()` and `operator const char*()` conversions.
- **[`fincept-qt/src/ui/theme/ThemeManager.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/ui/theme/ThemeManager.cpp)** – Singleton managing the active theme and global QSS generation.
- **[`fincept-qt/src/ui/theme/StyleSheets.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/ui/theme/StyleSheets.cpp)** – Helper functions returning token-embedded stylesheet strings.
- **[`fincept-qt/src/ui/widgets/Card.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/fincept-qt/src/ui/widgets/Card.cpp)** – Reference implementation of a fully-themed container widget.

## Summary

- **Derive from Qt base classes** (`QWidget`, `QFrame`, `QPushButton`) to maintain standard Qt6 event handling.
- **Access design tokens** through the `colors::` and `fonts::` namespaces in [`Theme.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/Theme.h), which resolve to current values at runtime.
- **Apply styles** using either [`StyleSheets.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/StyleSheets.cpp) helpers (for complex QSS) or direct palette manipulation with token values.
- **Subscribe to theme changes** via `ThemeManager::theme_changed` to ensure widgets update when the user modifies the global theme.

## Frequently Asked Questions

### How do I access color tokens in a custom Qt6 widget?

Include [`ui/theme/Theme.h`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/ui/theme/Theme.h) and use the `colors::` namespace. Tokens like `colors::BG_BASE`, `colors::TEXT_PRIMARY`, and `colors::ACCENT` convert implicitly to `QColor` or `QString` when passed to Qt APIs. For example, `palette.setColor(QPalette::Window, colors::BG_BASE)` sets the background using the current theme's base color.

### What is the difference between StyleSheets.cpp and manual palette updates?

[`StyleSheets.cpp`](https://github.com/Fincept-Corporation/FinceptTerminal/blob/main/StyleSheets.cpp) provides pre-built QSS strings via functions like `styles::button()` and `styles::card_frame()`, which embed token references for complex styling rules. Manual palette updates using `QPalette` are preferable for simple color changes or when you need to modify specific widget roles without parsing CSS-like syntax.

### How does the Obsidian design system handle theme switching at runtime?

`ThemeManager` holds the active `ThemeTokens` instance and rebuilds the global stylesheet when settings change. Custom widgets should connect to the `theme_changed` signal and re-apply token-dependent colors in their `applyStyle()` slots. Because tokens resolve dynamically on each access, widgets automatically reflect the new theme without hard-coded color values.

### Where are the base design tokens defined in FinceptTerminal?

All base values reside 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 C++ struct. This file defines every color (e.g., background, accent, semantic states like `positive` and `negative`), font sizes, and spacing constants used throughout the application. The `ThemeManager` loads these values into the `colors::` and `fonts::` namespaces at runtime.