2025, Dec 14 11:00

Why QSS Can't Recolor SVG Icons in PySide6 and How to Switch Dark/Light Icon Sets with QDir.setSearchPaths

Learn why PySide6 QSS can't recolor SVG icons via currentColor and how to handle dark/light themes with QIcon and QDir.setSearchPaths by swapping icon sets.

Switching a PySide6 app between light and dark themes often leads to the same follow-up question: can SVG icon colors be driven by QSS, like in the browser world with currentColor? The short answer is no. And if your UI relies on QIcon with SVG assets, trying to inherit color from the stylesheet will not have the intended effect.

Problem statement

It is tempting to assume that a style rule like the one below would recolor an SVG icon used by a widget, especially if the SVG uses fill="currentColor" and you expect the icon to inherit the color property:

#nav_btn {
  color: white;
}

In practice, that does not work in PySide6. SVGs loaded through QIcon do not receive context about the widget color, so the icon will not change with the QSS color property.

Why it happens

QIcon is not an image; it’s an abstraction that widgets query whenever they need a pixmap at a specific size or state. Each QIcon uses an icon engine underneath, and for SVG files that engine knows how to parse and render the SVG content into a pixmap when requested.

When a widget draws its icon, Qt calls QIcon::pixmap(), which forwards to the icon engine. The crucial part is the signature the engine receives:

QIconEngine::pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state)

There’s no context about the widget’s foreground color or stylesheet in that call. The icon engine renders without knowing what color is set on the target widget, so changing the palette or a QSS color property cannot affect the rendering. While there is also a paint() function that receives a QPainter, relying on it for color is not a supported path for this case, and even Qt’s own style sheet handling uses pixmap() for button icons. As a result, “colorizing” icons via QSS is not achievable here.

Another frequent source of confusion is assuming that Qt style sheets follow CSS behavior one-to-one. They are based on CSS syntax but are not a browser engine and do not implement the full cascade or all CSS semantics. That includes the inability to pass CSS-driven color context into an icon engine.

Practical solution

The reliable approach is to maintain separate icon sets per color scheme and switch them at runtime. Qt offers a small but useful mechanism to make this ergonomic: QDir.setSearchPaths(). You define a logical prefix and map it to a physical directory depending on the current color scheme. The same code paths then load the correct assets without conditional logic scattered across the UI.

The sequence looks like this: prepare two directories with identically named SVG files, one for light and one for dark, then set a search path prefix to the active directory based on the current scheme. This works with file system paths and also with Qt resources.

Here is the initialization logic:

ui_meta = QApplication.styleHints()
if ui_meta.colorScheme() == Qt.ColorScheme.Dark:
    asset_dir = '<path-to-light-icons>'
else:
    asset_dir = '<path-to-dark-icons>'
QDir.setSearchPaths('gfx', [asset_dir])

After that, setting icons is straightforward and decoupled from the physical location of the files:

main_button.setIcon(QIcon('gfx:toolbar_play.svg'))

One important behavior to keep in mind is that setSearchPaths() does not retroactively update existing QIcon instances. It should be set before any icons are created, typically at app startup. If you support switching themes at runtime, you need to reload the icons by calling setIcon() again after the scheme changes. You can keep only the logical names and reapply them on change.

Minimal example of reapplying an icon after the system color scheme changes:

hint_src = QApplication.styleHints()
icon_key = 'toolbar_play.svg'

main_button.setIcon(QIcon('gfx:' + icon_key))

def on_scheme_flip():
    main_button.setIcon(QIcon('gfx:' + icon_key))

hint_src.colorSchemeChanged.connect(on_scheme_flip)

Why this matters

The icon rendering path in Qt is driven by QIcon and its engine, not by a CSS-like cascade. Expecting QSS to recolor SVGs leads to dead ends and brittle hacks. Separating assets by theme and selecting them through QDir.setSearchPaths() keeps the code clean, predictable, and maintainable, while allowing you to centralize the decision of which asset set is active.

Takeaways

If your PySide6 app supports light and dark modes, do not rely on QSS color to affect SVG icons. Prepare two icon sets with identical filenames, choose the active set using QDir.setSearchPaths() before creating icons, and when theme changes at runtime, reapply the icons so they pick up the new search path. This approach aligns with how QIcon and QIconEngine actually work and avoids coupling icon rendering to unsupported stylesheet assumptions.