Open-source project
5yutan5/PyQtDarkTheme avatar
5yutan5/PyQtDarkTheme

PyQtDarkTheme: a flat dark theme for PySide and PyQt that follows the OS

A flat dark theme for PySide and PyQt.

757 stars113 forksPythonMIT

At a glance

What is it?
qdarktheme applies a flat dark or light theme to a QtWidgets application in one call, and its setup_theme function can follow the operating system theme and accent colour instead of guessing. The last tagged release is 2.1.0 from 2022, while the repository has been pushed to as recently as 2026-10-19.
Who is it for?
Adopt qdarktheme if you ship a desktop Qt application and want a coherent dark and light theme without writing a stylesheet, since the whole thing is one setup_theme call and it follows the OS theme and accent when you ask it to. Keep away if your app is QML based rather than QtWidgets, because the scope is widgets, or if you need a pinned release to reason about, since the last tagged release is 2.1.0 from 2022-12-25 while the branch has continued to move.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One call to theme the whole application

qdarktheme applies a flat dark theme to a QtWidgets application, and there is a light theme too, with the colours balanced from the dark theme for easy viewing in daylight.

The integration is a single function call in your main file:

Python
import sys

from PySide6.QtWidgets import QApplication, QMainWindow, QPushButton

import qdarktheme

app = QApplication(sys.argv)
# Apply the complete dark theme to your Qt App.
qdarktheme.setup_theme()

main_win = QMainWindow()
push_button = QPushButton("PyQtDarkTheme!!")
main_win.setCentralWidget(push_button)

main_win.show()

app.exec()

The ordering matters and the example shows it. setup_theme is called after the QApplication is constructed, because the theme applies to an existing application object. The comment on the line says exactly that, and it is the kind of detail that costs an hour the first time if you get it wrong.

The feature list names six things. There is a flat dark and light theme, support for both PySide and PyQt, syncing with the operating system theme and accent across Mac, Windows and Linux, resolution of the style differences between Qt versions, a dark and light theme QPalette, and an override of the old Qt standard icons.

That last two are the ones that matter for real applications. A QPalette is how you colour native widgets and dialogs that do not take a stylesheet, and the old standard icons are the small arrows and grips that look wrong on a dark background unless they are replaced. A theme that only sets a stylesheet always leaves some of the application looking like it belongs to a different program.

The project is MIT licensed, the homepage is the readthedocs documentation, the default branch is main, and the last push was on 2026-10-19. The version in the manifest is 2.1.0 and the last tagged release listed is 2.1.0 from 2022-12-25.

The OS sync mode, and why it is not the default

The most interesting feature is the automatic mode. Instead of a fixed theme, setup_theme accepts the string auto, which syncs with the operating system theme and accent on Mac, Windows and Linux:

Python
qdarktheme.setup_theme("auto")

On macOS the library also syncs with the accent colours, so the theme picks up the user's chosen system accent rather than a hardcoded one. The README ships a gif of the sync behaviour for both the theme and the accent.

That is more useful than it sounds, because the light and dark choice is a system setting that changes during the day on some systems and per user on others, and a desktop application that ignores it looks wrong to the person using it.

The light theme is the same function with a different argument:

Python
qdarktheme.setup_theme("light")

The default, with no argument, is the dark theme.

One thing worth noting is that the accent sync depends on a separate package. The project metadata lists darkdetect at 0.7.1 or newer as its only runtime dependency, and the licence section says the accent detector, which lives under a path in the package, is inspired by darkdetect under a 3-clause BSD licence. So the OS detection is delegated rather than reimplemented, which is the right call for the part of the library that has to interrogate platform settings.

The README also notes the requirement of Python 3.7 or newer and Qt 5.15 or newer, with PySide6, PyQt6, PyQt5 or PySide2 as the supported bindings.

Customising colours, corners and the palette separately

Three levels of customisation are documented, and they map onto how much control you want.

The first is the accent colour, through a custom_colors dict:

python
# Customize accent color.
qdarktheme.setup_theme(custom_colors={"primary": "#D0BCFF"})

The full set of colour keys is in a theme colour reference in the documentation, so the primary key is the example rather than the limit.

The second is the corner style. The default is rounded, and the sharp option changes it:

python
# Default is "rounded".
stylesheet = qdarktheme.setup_theme(corner_shape="sharp")

Note what that call returns, because it is the same call doing two jobs. setup_theme returns the stylesheet, which is useful, but it also means you are applying the theme by calling the function and capturing its output, and if you were expecting a None you will be mildly surprised.

The third level is loading the pieces without applying them. If your application manages its own theme switching, or you want the palette for a native dialog, you can load either part on its own:

Python
palette = qdarktheme.load_palette(theme="dark")
stylesheet = qdarktheme.load_stylesheet(theme="dark")

The README notes that setup_theme uses these two functions internally, which is a hint about the structure of the code. It also means the palette and the stylesheet are consistent with each other by construction, because they come from the same source.

That last point is the real argument for using the library rather than hand-writing a stylesheet. Hand-written Qt stylesheets are where applications acquire their inconsistency, because a developer styles a button and forgets the menu, and the palette and the stylesheet drift apart.

HiDPI, the widget gallery and the example projects

Two operational details are worth covering.

The first is HiDPI scaling, which has to be enabled before the QApplication is instantiated:

Python
# enable_hi_dpi() must be called before the instantiation of QApplication.
qdarktheme.enable_hi_dpi()
app = QApplication(sys.argv)
qdarktheme.setup_theme()

The ordering requirement is in the comment, and it is the correct one because the Qt high DPI attributes have to be set before the application object exists. The README also says that for Qt6 bindings the HiDPI support just works without using this function, which is another of the style differences between Qt versions that the library resolves.

The second is the widget gallery, a module you can run to see every Qt widget under the theme:

plaintext
python -m qdarktheme.widget_gallery

That is a genuinely useful diagnostic. When a widget looks wrong under a theme, the gallery shows you how it looks next to every other widget, and you can tell whether the problem is the theme or your own styling.

The repository has an examples/ directory with seven projects, one per feature area: apply_theme, customize_color, customize_style, icons, qpalette, toggle_theme and use_stylesheet. The toggle_theme example is the one to look at if you are planning a theme switcher in your own application, since it is the case the library does not handle for you.

There is also a style/ directory, a tests/ directory, a tools/ directory, a docs/ directory, an images/ directory with the widget gallery screenshots, and a poetry.lock, with the project built by Poetry.

The licence stack, and what the theme is actually built from

The licence section is worth reading carefully because this project is assembled from other people's work, and the terms are stated per component.

The SVG files for PyQtDarkTheme are derived from the design icon set linked in the licence section under the Apache License 2.0. The Qt stylesheets are originally a fork of QDarkStyleSheet under the MIT licence, credited to ColinDuquesnoy. Other files are covered by PyQtDarkTheme's own MIT licence. And the accent detector under the mac detection path in the package is inspired by darkdetect under a 3-clause BSD licence.

So a flat dark theme in this package is really four things layered: a stylesheet forked from an existing Qt dark stylesheet, icon assets from that design icon set, a palette, and a small platform accent detector.

That composition is the reason the library exists rather than each project writing its own. The QDarkStyleSheet fork gives the widget styling, the icon overrides replace the old standard icons that look wrong on dark backgrounds, and the accent detector ties the palette to the platform. The project is not claiming to have invented a colour scheme.

The tooling around it is conventional. Black and isort format the code, pre-commit runs the hooks, and the flake8 group is unusually thorough, with plugins for return statements, pie, print, docstrings, bugbear, simplify, comprehensions, eradicate, rst docstrings, pyproject integration, pytest style and pep8 naming. The test group has pytest with coverage, random ordering, pytest-qt, pytest-xvfb for headless Qt runs, and pytest-mock.

That last one matters more than it looks. pytest-xvfb means the test suite runs a GUI library on a headless CI machine, which is the only way to test a Qt theme automatically at all.

Where qdarktheme is the wrong choice

The first wrong case is a QML application. The package applies a theme to QtWidgets, so an application built with QML and Qt Quick is untouched by it. The QML side has its own styling route, and mixing a widgets stylesheet into a QML application achieves nothing.

The second is a project that needs more than flat. Flat is the design centre of this theme, which is a deliberate look and also a limit. If you want a theme with gradients, raised surfaces and a specific platform look, this is not the library, and you would be fighting the stylesheet rather than using it.

The third is a project with a hard dependency policy. The runtime dependency list is short, one package, but the version floor is Python 3.7 with the manifest capping below 3.12, and the classifiers stop at Python 3.11. Meanwhile the last tagged release is 2.1.0 from 2022-12-25 even though the branch has been pushed to as recently as 2026-10-19. If you need a versioned, auditable dependency, that gap between the tag and the branch is something to think about, because you are choosing between an old tag and an untagged head.

The fourth is an application that already has a working theme. If you have a stylesheet your users like, the cost of switching to a flat look is a visual redesign, and the benefit is only that you did not have to write one.

The alternative worth weighing is QDarkStyleSheet directly, which is the upstream project this stylesheet is forked from. It is one dependency fewer and no OS sync, and you own the whole look. The reason to choose qdarktheme over it is the extra layer the package adds: the OS theme and accent sync, the icon overrides, the palette, and the resolution of the style differences between Qt versions, which is the part that takes real work.

Editorial conclusion

Adopt qdarktheme if you ship a desktop Qt application and want a coherent dark and light theme without writing a stylesheet, since the whole thing is one setup_theme call and it follows the OS theme and accent when you ask it to. Keep away if your app is QML based rather than QtWidgets, because the scope is widgets, or if you need a pinned release to reason about, since the last tagged release is 2.1.0 from 2022-12-25 while the branch has continued to move. Before you ship, check the Qt version and binding you target against the stated requirement of Qt 5.15 or newer with PySide6, PyQt6, PyQt5 or PySide2, and remember the palette and stylesheet are loadable separately if you need to apply them yourself.

Frequently asked questions

How do I apply the dark theme to a PySide or PyQt application?

Call qdarktheme.setup_theme() after you have constructed the QApplication, since the theme applies to an existing application object. The README example creates the QApplication, calls setup_theme, and then builds the window.

Can qdarktheme follow the operating system dark mode and accent colour?

Yes. Call qdarktheme.setup_theme("auto") to sync with the OS theme and accent on Mac, Windows and Linux, and on macOS it also picks up the accent colours. The detection relies on the darkdetect package, which is the only runtime dependency.

How do I change the accent colour or the corner style in qdarktheme?

The accent colour goes through the custom_colors dict, for example qdarktheme.setup_theme(custom_colors={"primary": "#D0BCFF"}), with the full set of keys listed in the theme colour reference in the documentation. The corner style is set with the corner_shape argument, where the default is rounded and the other option is sharp.

Can I load the palette and stylesheet separately in qdarktheme?

Yes. qdarktheme.load_palette(theme="dark") and qdarktheme.load_stylesheet(theme="dark") return the two pieces independently, and setup_theme uses those same functions internally, so applying them yourself keeps them consistent with the built-in theme.

Official sources

  1. 5yutan5/PyQtDarkTheme on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/5yutan5-pyqtdarktheme.svg)](https://hysenlabs.com/projects/5yutan5-pyqtdarktheme)

Was this useful?