Qt Styling & Themes (QSS)

Qt Style Sheets (QSS), QStyle, custom themes, dark/light mode, embedded HMI styling — make your Qt apps look great.

7 min read
66469 chars

What Is QSS?

Qt Style Sheets (QSS) is a subset of CSS applied to Qt widgets. You can style colors, borders, fonts, spacing, and pseudo-states (:hover, :pressed, :checked, :disabled).

/* Apply to entire app */
QWidget {
    background-color: #1e1e2e;
    color: #cdd6f4;
    font-family: 'Fira Code', monospace;
    font-size: 13px;
}
// Apply globally
qApp->setStyleSheet(qssString);

// Apply to one widget (and its children)
window->setStyleSheet(qssString);

// Apply to one widget only
btn->setStyleSheet("QPushButton { background: #89b4fa; }");

Selectors

/* Type selector */
QPushButton { background: #313244; }

/* Class selector (widget objectName) */
QPushButton#dangerBtn { background: #f38ba8; }

/* Descendant selector */
QDialog QPushButton { border-radius: 4px; }

/* Property selector */
QLabel[status="error"] { color: #f38ba8; }
QLabel[status="ok"]    { color: #a6e3a1; }
// Set property for CSS selection
label->setProperty("status", "error");
label->style()->unpolish(label);  // force re-style
label->style()->polish(label);

Pseudo-States

QPushButton {
    background: #313244;
    border: 1px solid #45475a;
    border-radius: 8px;
    padding: 6px 16px;
    color: #cdd6f4;
}
QPushButton:hover    { background: #45475a; }
QPushButton:pressed  { background: #89b4fa; color: #1e1e2e; }
QPushButton:disabled { background: #181825; color: #585b70; }
QPushButton:checked  { background: #89b4fa; color: #1e1e2e; border-color: #89b4fa; }
QPushButton:focus    { border-color: #89b4fa; outline: none; }

Common Widget Styles

QLineEdit

QLineEdit {
    background: #181825;
    border: 1px solid #45475a;
    border-radius: 6px;
    padding: 4px 8px;
    color: #cdd6f4;
    selection-background-color: #89b4fa;
}
QLineEdit:focus {
    border-color: #89b4fa;
}
QLineEdit:read-only {
    background: #1e1e2e;
    color: #6c7086;
}

QComboBox

QComboBox {
    background: #313244;
    border: 1px solid #45475a;
    border-radius: 6px;
    padding: 4px 8px;
    color: #cdd6f4;
    min-width: 100px;
}
QComboBox::drop-down {
    border: none;
    width: 20px;
}
QComboBox::down-arrow {
    image: url(:/icons/chevron-down.svg);
    width: 12px; height: 12px;
}
QComboBox QAbstractItemView {
    background: #313244;
    border: 1px solid #45475a;
    selection-background-color: #89b4fa;
    selection-color: #1e1e2e;
}

QScrollBar

QScrollBar:vertical {
    background: #181825;
    width: 8px;
    border-radius: 4px;
}
QScrollBar::handle:vertical {
    background: #45475a;
    min-height: 30px;
    border-radius: 4px;
}
QScrollBar::handle:vertical:hover {
    background: #6c7086;
}
QScrollBar::add-line, QScrollBar::sub-line { height: 0; }

QProgressBar

QProgressBar {
    background: #181825;
    border: 1px solid #313244;
    border-radius: 6px;
    text-align: center;
    color: #cdd6f4;
    height: 12px;
}
QProgressBar::chunk {
    background: qlineargradient(x1:0, y1:0, x2:1, y2:0,
        stop:0 #89b4fa, stop:1 #a6e3a1);
    border-radius: 5px;
}

QTabWidget

QTabWidget::pane {
    border: 1px solid #313244;
    border-radius: 0 8px 8px 8px;
    background: #1e1e2e;
}
QTabBar::tab {
    background: #181825;
    color: #6c7086;
    padding: 6px 16px;
    border-radius: 6px 6px 0 0;
}
QTabBar::tab:selected {
    background: #1e1e2e;
    color: #cdd6f4;
    border-bottom: 2px solid #89b4fa;
}
QTabBar::tab:hover { color: #cdd6f4; }

Visual: QSS Box Model

┌──────────────────────────────────────┐
│              margin                  │
│  ┌────────────────────────────────┐  │
│  │           border               │  │
│  │  ┌──────────────────────────┐  │  │
│  │  │        padding           │  │  │
│  │  │  ┌────────────────────┐  │  │  │
│  │  │  │    content area    │  │  │  │
│  │  │  └────────────────────┘  │  │  │
│  │  └──────────────────────────┘  │  │
│  └────────────────────────────────┘  │
└──────────────────────────────────────┘

Dark & Light Theme System

class ThemeManager : public QObject {
    Q_OBJECT
public:
    enum Theme { Dark, Light };

    static ThemeManager& instance() {
        static ThemeManager t; return t;
    }

    void setTheme(Theme theme) {
        m_current = theme;
        qApp->setStyleSheet(theme == Dark ? darkQss() : lightQss());
        emit themeChanged(theme);
    }

    Theme current() const { return m_current; }

signals:
    void themeChanged(Theme t);

private:
    Theme   m_current = Dark;

    static QString darkQss() {
        return R"(
            QWidget { background:#1e1e2e; color:#cdd6f4; font-size:13px; }
            QPushButton {
                background:#313244; border:1px solid #45475a;
                border-radius:8px; padding:6px 16px; color:#cdd6f4;
            }
            QPushButton:hover   { background:#45475a; }
            QPushButton:pressed { background:#89b4fa; color:#1e1e2e; }
            QLineEdit {
                background:#181825; border:1px solid #45475a;
                border-radius:6px; padding:4px 8px; color:#cdd6f4;
            }
            QLineEdit:focus { border-color:#89b4fa; }
            QLabel { color:#cdd6f4; }
            QFrame { border:1px solid #313244; }
        )";
    }

    static QString lightQss() {
        return R"(
            QWidget { background:#eff1f5; color:#4c4f69; font-size:13px; }
            QPushButton {
                background:#dce0e8; border:1px solid #ccd0da;
                border-radius:8px; padding:6px 16px; color:#4c4f69;
            }
            QPushButton:hover   { background:#ccd0da; }
            QPushButton:pressed { background:#1e66f5; color:#eff1f5; }
            QLineEdit {
                background:#ffffff; border:1px solid #ccd0da;
                border-radius:6px; padding:4px 8px; color:#4c4f69;
            }
            QLineEdit:focus { border-color:#1e66f5; }
        )";
    }
};

// Toggle in app
connect(themeBtn, &QPushButton::clicked, [](){
    auto &tm = ThemeManager::instance();
    tm.setTheme(tm.current() == ThemeManager::Dark
                ? ThemeManager::Light : ThemeManager::Dark);
});

QPalette — System Colors

#include <QPalette>

// Set custom palette (alternative to QSS for system-level theming)
QPalette p = qApp->palette();
p.setColor(QPalette::Window,    QColor("#1e1e2e"));
p.setColor(QPalette::Base,      QColor("#181825"));
p.setColor(QPalette::Text,      QColor("#cdd6f4"));
p.setColor(QPalette::Button,    QColor("#313244"));
p.setColor(QPalette::ButtonText,QColor("#cdd6f4"));
p.setColor(QPalette::Highlight, QColor("#89b4fa"));
p.setColor(QPalette::HighlightedText, QColor("#1e1e2e"));
qApp->setPalette(p);

Load QSS from File

// Style from embedded resource
QFile f(":/styles/dark.qss");
if (f.open(QFile::ReadOnly | QFile::Text)) {
    QTextStream ts(&f);
    qApp->setStyleSheet(ts.readAll());
}

Lab — Themed Settings Panel

class SettingsPanel : public QWidget {
    Q_OBJECT
public:
    SettingsPanel(QWidget *p = nullptr) : QWidget(p) {
        setObjectName("settingsPanel");

        auto *lay = new QVBoxLayout(this);
        lay->setSpacing(12);
        lay->setContentsMargins(20, 20, 20, 20);

        // Title
        auto *title = new QLabel("Device Settings");
        title->setObjectName("panelTitle");
        title->setStyleSheet("font-size:18px; font-weight:bold; "
                             "color:#89b4fa; margin-bottom:8px;");

        // Form
        auto *form = new QFormLayout;
        form->setSpacing(8);

        auto *portEdit = new QLineEdit("/dev/ttyUSB0");
        auto *baudBox  = new QComboBox;
        baudBox->addItems({"9600","19200","38400","115200","921600"});
        baudBox->setCurrentText("115200");

        auto *themeBtn = new QPushButton("Toggle Theme");
        themeBtn->setObjectName("themeBtn");

        auto *saveBtn  = new QPushButton("Save");
        saveBtn->setObjectName("primaryBtn");
        saveBtn->setStyleSheet(
            "QPushButton#primaryBtn { background:#89b4fa; color:#1e1e2e; "
            "font-weight:bold; } "
            "QPushButton#primaryBtn:hover { background:#b4d0f8; }");

        form->addRow("Serial Port:", portEdit);
        form->addRow("Baud Rate:",   baudBox);

        lay->addWidget(title);
        lay->addLayout(form);
        lay->addWidget(themeBtn);
        lay->addStretch();
        lay->addWidget(saveBtn);

        connect(themeBtn, &QPushButton::clicked, []{
            auto &tm = ThemeManager::instance();
            tm.setTheme(tm.current() == ThemeManager::Dark
                        ? ThemeManager::Light : ThemeManager::Dark);
        });
    }
};

Interview Questions

Q1: What is the difference between QSS and QPalette?

QPalette controls system-level color roles (text, background, highlight). QSS is more powerful — CSS-like selectors, pseudo-states, box model, borders, images. QSS overrides QPalette for styled widgets. Use QPalette for system integration (e.g., OS dark mode detection); use QSS for custom app themes.

Q2: Why do you need style()->unpolish() and style()->polish() after changing a property used in QSS?

Qt caches style computations. After changing a dynamic property, you must tell Qt to re-evaluate the style for that widget by calling unpolish() then polish() on its style engine.

Q3: How do you apply a style to only one widget without affecting children?

Use the type selector with a specific objectName: QPushButton#myBtn { ... }. This targets only the widget with that objectName. Or, use setStyleSheet("QPushButton { ... }") on a child-less widget.

Q4: What are the performance implications of QSS?

QSS parsing and application has overhead — especially on large widget trees. Prefer applying the style sheet at the app level once, rather than per-widget. Avoid wildcard selectors (* { ... }) on large trees. For embedded/low-power targets, consider using QPalette or custom QStyle instead.

Q5: How do you detect the OS dark/light mode and apply the correct theme?

Check QPalette::window().color().lightness() — if < 128, the system is in dark mode. On Qt 6.5+, use QGuiApplication::styleHints()->colorScheme() which returns Qt::ColorScheme::Dark or Light. Connect to QGuiApplication::styleHints()->colorSchemeChanged() to react dynamically.


Application: Catppuccin-Themed Device Dashboard

class CatppuccinDashboard : public QMainWindow {
    Q_OBJECT
public:
    CatppuccinDashboard(QWidget *p = nullptr) : QMainWindow(p) {
        setWindowTitle("Device Dashboard");
        resize(1000, 650);

        // Apply Catppuccin Mocha theme
        setStyleSheet(R"(
            QMainWindow, QWidget {
                background:#1e1e2e; color:#cdd6f4;
                font-family:'Segoe UI',sans-serif; font-size:13px;
            }
            QMenuBar { background:#181825; padding:2px; }
            QMenuBar::item { padding:4px 12px; border-radius:4px; }
            QMenuBar::item:selected { background:#313244; }
            QMenu { background:#313244; border:1px solid #45475a; }
            QMenu::item:selected { background:#89b4fa; color:#1e1e2e; }

            QPushButton {
                background:#313244; border:1px solid #45475a;
                border-radius:8px; padding:6px 16px;
            }
            QPushButton:hover   { background:#45475a; }
            QPushButton:pressed { background:#89b4fa; color:#1e1e2e; }

            QGroupBox {
                border:1px solid #313244; border-radius:8px;
                margin-top:16px; padding:8px;
                font-weight:bold; color:#89b4fa;
            }
            QGroupBox::title {
                subcontrol-origin:margin; left:10px; padding:0 4px;
            }

            QStatusBar { background:#181825; color:#6c7086; }
            QStatusBar::item { border:none; }

            QTabWidget::pane { border:1px solid #313244; border-radius:0 8px 8px 8px; }
            QTabBar::tab { background:#181825; color:#6c7086; padding:6px 20px; }
            QTabBar::tab:selected { background:#1e1e2e; color:#cdd6f4; border-bottom:2px solid #89b4fa; }

            QProgressBar {
                background:#181825; border:1px solid #313244;
                border-radius:6px; height:10px;
            }
            QProgressBar::chunk {
                background:qlineargradient(x1:0,y1:0,x2:1,y2:0,
                    stop:0 #89b4fa, stop:1 #a6e3a1);
                border-radius:5px;
            }
        )");

        // Build UI
        auto *central = new QWidget;
        setCentralWidget(central);
        auto *grid = new QGridLayout(central);
        grid->setSpacing(16);
        grid->setContentsMargins(16,16,16,16);

        // Stats group
        auto *statsGroup = new QGroupBox("Live Readings");
        auto *statsLay   = new QHBoxLayout(statsGroup);

        auto mkStat = [&](const QString &label, const QString &value, const QString &color) {
            auto *frame = new QFrame;
            frame->setStyleSheet(QString("QFrame{background:#313244;border-radius:10px;padding:8px;}"
                                         "QLabel{color:%1;}").arg(color));
            auto *vl = new QVBoxLayout(frame);
            auto *lbl = new QLabel(label); lbl->setAlignment(Qt::AlignCenter);
            auto *val = new QLabel(value);
            val->setFont(QFont("Segoe UI", 24, QFont::Bold));
            val->setAlignment(Qt::AlignCenter);
            vl->addWidget(val); vl->addWidget(lbl);
            return frame;
        };

        statsLay->addWidget(mkStat("Temperature", "36.5 °C", "#fab387"));
        statsLay->addWidget(mkStat("Voltage",     "3.30 V",  "#a6e3a1"));
        statsLay->addWidget(mkStat("Humidity",    "60 %",    "#89dceb"));
        statsLay->addWidget(mkStat("Status",      "Online",  "#a6e3a1"));
        grid->addWidget(statsGroup, 0, 0, 1, 2);

        statusBar()->showMessage("Connected to /dev/ttyUSB0 @ 115200");
    }
};

References

ResourceLink
Qt Docs: Style Sheetshttps://doc.qt.io/qt-6/stylesheet.html
Qt Docs: Style Sheet Referencehttps://doc.qt.io/qt-6/stylesheet-reference.html
Qt Docs: QPalettehttps://doc.qt.io/qt-6/qpalette.html
Qt Docs: QStylehttps://doc.qt.io/qt-6/qstyle.html

Next tutorial → Qt Testing (QTest)