46

Background

I'm building a PyQt5 application, that I'd like to have a dark theme for. Previously I've worked with Android development where there was a dark theme that I could set for a whole application

Question

Is there a dark theme built into Qt (that applies to all widgets in an application, and that is cross-platform)?

sunyata
  • 1,843
  • 5
  • 27
  • 41

6 Answers6

73

No, but you may use my fairly comprehensive stylesheets that should look excellent on most platforms (it's inspired by KDE's Breeze Theme, which is a dark theme that is quite elegant). This was (hard) forked from the excellent QDarkStylesheet, which I felt had theme issues in numerous areas, so I modified it extensively for my own needs and added a light theme.

Simple Use

A sample of the theme is here. To use it in PyQt5, simply add the following lines to a project:

import sys
from PyQt5.QtCore import QFile, QTextStream
from PyQt5.QtWidgets import QApplication
import breeze_resources

app = QApplication(sys.argv)
file = QFile(":/dark.qss")
file.open(QFile.ReadOnly | QFile.Text)
stream = QTextStream(file)
app.setStyleSheet(stream.readAll())

enter image description here

Dynamic Stylesheet Toggling

In response to a comment, the easiest way to adjust the stylesheet to use either the light or the dark stylesheet dynamically is to wrap it in a function. You may then use the function as a slot to a Qt signal (warning: I primarily develop using C++, so there may be small errors in my code for the signal/slot mechanism).

def toggle_stylesheet(path):
    '''
    Toggle the stylesheet to use the desired path in the Qt resource
    system (prefixed by `:/`) or generically (a path to a file on
    system).

    :path:      A full path to a resource or file on system
    '''

    # get the QApplication instance,  or crash if not set
    app = QApplication.instance()
    if app is None:
        raise RuntimeError("No Qt Application found.")

    file = QFile(path)
    file.open(QFile.ReadOnly | QFile.Text)
    stream = QTextStream(file)
    app.setStyleSheet(stream.readAll())

Now we can add generic application logic that can use this function in a signal/slot mechanism (using a lambda as a convenient wrapper, if needed, to provide the path to the stylesheet toggler):

# add logic for setting up application
app = QApplication(sys.argv)
# more logic for creating top-level widgets, application logic ...

parent = ...
light_btn = QPushButton("Toggle light.", parent)
light_btn.clicked.connect(lambda: toggle_stylesheet(":/light.qss"))

dark_btn = QPushButton("Toggle dark.", parent)
dark_btn.clicked.connect(lambda: toggle_stylesheet(":/dark.qss"))

# add to the layout, do other stuff
# ...

# end the Qt application
sys.exit(app.exec_())

This allows users to dynamically change the theme of an application developed with PyQt5 (or using analogous logic in C++, Qt5) to either a light or dark theme.

Disclaimer: Obviously I am the maintainer.

Alex Huszagh
  • 13,272
  • 3
  • 39
  • 67
  • 3
    All of the other dark themes I tried have missing parts and don't look good, are buggy and hacked together, and mess around too much with padding and layout. This one is the best by far. That said it's not perfect, but probably only needs a little adjusting. The Dock list with checkboxes of active docks has the checkboxes overlapping the dock names. – leetNightshade Sep 02 '18 at 01:17
  • @leetNightshade Feel free to post an issue and I can address it. Feature or pull requests would be wonderful. – Alex Huszagh Sep 02 '18 at 15:35
  • @AlexanderHuszagh Is it possible to toggle between enabling the dark theme and disabling it? It seems like after I call "sys.exit(app.exec_())" I can no longer set the stylesheet of the app – aoh Oct 05 '18 at 17:56
  • @aoh, Yes there is, but I currently am on a road trip and have no access to a computer. I'll put this on my todo-list, but a reminder in 5 days would be nice. – Alex Huszagh Oct 08 '18 at 23:44
  • 2
    @aoh Let me know if that addresses your issues, it explains simply how you can toggle between the light and dark styles at runtime, and to use a native style, you could simply call `app.setStyleSheet("")`. Binding these to signals in the UI would allow users to change the stylesheet at runtime, preferably storing their preferred UI in a config file. – Alex Huszagh Oct 11 '18 at 17:38
  • 2
    @AlexanderHuszagh It definitely answers my question, thank you! – aoh Oct 12 '18 at 14:55
36

There's no dark theme built into Qt. But you can quite easily create one yourself with the following code:

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QPalette, QColor


app = QApplication([])
# Force the style to be the same on all OSs:
app.setStyle("Fusion")

# Now use a palette to switch to dark colors:
palette = QPalette()
palette.setColor(QPalette.Window, QColor(53, 53, 53))
palette.setColor(QPalette.WindowText, Qt.white)
palette.setColor(QPalette.Base, QColor(25, 25, 25))
palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
palette.setColor(QPalette.ToolTipBase, Qt.black)
palette.setColor(QPalette.ToolTipText, Qt.white)
palette.setColor(QPalette.Text, Qt.white)
palette.setColor(QPalette.Button, QColor(53, 53, 53))
palette.setColor(QPalette.ButtonText, Qt.white)
palette.setColor(QPalette.BrightText, Qt.red)
palette.setColor(QPalette.Link, QColor(42, 130, 218))
palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
palette.setColor(QPalette.HighlightedText, Qt.black)
app.setPalette(palette)

The nice thing about this is that it introduces no external dependencies. If you're interested what the above changes look like, I created an example PyQt5 app with a dark theme. Here is a screenshot:

Qt dark theme

pipeline
  • 5
  • 4
Michael Herrmann
  • 4,832
  • 3
  • 38
  • 53
  • what about default icons? that has to be taken care separately right? – Alex Jones Jan 02 '21 at 18:17
  • 1
    Yes, using a dark palette seems to be the solution. What I did: I used my macOS, changed it to dark theme, Qt recognises this (5.12) and renders everything in a dark theme. Then, I extracted all the colors from the palette, and set them, so that I can also use them on Windows. Yes, icons need to be used separately. I have SVGs that are flat/unicolor, so I just run a script to duplicate them, text-replace the color, and have a wrapper function that returns bright/dark icons based on the theme. By the way, I also needed to replace Light/Midlight/Dark/Mid/Shadow of the palette to work properly – IceFire Jan 27 '21 at 06:50
  • @IceFire I would be interested to see what you did. also Michael great work thanks for this – Abdessabour Mtk Nov 17 '21 at 16:53
  • 2
    Thank you for this great answer ! I am not sure if it applies to all versions of Qt but I had to add `QToolTip::setPalette(palette);` for the settings to take effect on tooltips. – Arnaud Nov 18 '21 at 09:06
  • 2
    If you're using PyQt6 you will need to add `ColorRole` to reach out the constants from `QPalette`: `QPalette.ColorRole.Window` – Ramon Moraes Sep 27 '22 at 23:32
8

I was trying to apply this to my fbs based app and found the below easily allowed me to style the app by applying it to the AppContext

class AppContext(ApplicationContext):
    def run(self):
        self.main_window.show()
        return self.app.exec_()

    @cached_property
    def main_window(self):
        return MainWindow(self)

    if theme_selection == 'Dark':
        QApplication.setStyle("Fusion")
        #
        # # Now use a palette to switch to dark colors:
        dark_palette = QPalette()
        dark_palette.setColor(QPalette.Window, QColor(53, 53, 53))
        dark_palette.setColor(QPalette.WindowText, Qt.white)
        dark_palette.setColor(QPalette.Base, QColor(35, 35, 35))
        dark_palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
        dark_palette.setColor(QPalette.ToolTipBase, QColor(25, 25, 25))
        dark_palette.setColor(QPalette.ToolTipText, Qt.white)
        dark_palette.setColor(QPalette.Text, Qt.white)
        dark_palette.setColor(QPalette.Button, QColor(53, 53, 53))
        dark_palette.setColor(QPalette.ButtonText, Qt.white)
        dark_palette.setColor(QPalette.BrightText, Qt.red)
        dark_palette.setColor(QPalette.Link, QColor(42, 130, 218))
        dark_palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
        dark_palette.setColor(QPalette.HighlightedText, QColor(35, 35, 35))
        dark_palette.setColor(QPalette.Active, QPalette.Button, QColor(53, 53, 53))
        dark_palette.setColor(QPalette.Disabled, QPalette.ButtonText, Qt.darkGray)
        dark_palette.setColor(QPalette.Disabled, QPalette.WindowText, Qt.darkGray)
        dark_palette.setColor(QPalette.Disabled, QPalette.Text, Qt.darkGray)
        dark_palette.setColor(QPalette.Disabled, QPalette.Light, QColor(53, 53, 53))
        QApplication.setPalette(dark_palette)
    elif theme_selection == 'Light':
        QApplication.setStyle("")
        pass
    else:
        pass

You can use Qsettings to save a preference for which mode like this and restore on start.

if settings.contains("theme_selection"):
    # there is the key in QSettings
    print('Checking for theme preference in config')
    theme_selection = settings.value('theme_selection')
    print('Found theme_selection in config:' + theme_selection)
else:
    if not is_mac():
        print('theme_selection not found in config. Using default Darkmode')
        settings.setValue('theme_selection', 'Dark')
        theme_selection = settings.value('theme_selection')
    elif is_mac():
        print('theme_selection not found in config. Using default Lightmode')
        settings.setValue('theme_selection', 'Light')
        theme_selection = settings.value('theme_selection')
    pass

Looks amazing could not comment on Michael Herrmann's post to say thanks but did upvote it.

Before: How it looked before dark pallete mode enabled

After: With Pallete mode enabled

Middle part is xterm.js so that is why its still white for now as its not a QT styled thing.

Mike R
  • 679
  • 7
  • 13
3

Founded in my bookmarks. I don't remember the original source.

QApplication::setStyle(QStyleFactory::create("Fusion"));
QPalette p;
p = qApp->palette();
p.setColor(QPalette::Window, QColor(53,53,53));
p.setColor(QPalette::Button, QColor(53,53,53));
p.setColor(QPalette::Highlight, QColor(142,45,197));
p.setColor(QPalette::ButtonText, QColor(255,255,255));
qApp->setPalette(p);

P.S. it may be adjusted with QSS if necessary.

Dmitry Sazonov
  • 8,801
  • 1
  • 35
  • 61
  • 1
    +1 for making me aware of the "Fusion" `QStyleFactory` and copying the palette before making changes to it. So much simpler, but the list of `setColor` calls is incomplete: `Base`, `Dark`, `ButtonText` (disabled), `Text` (regular/disabled), `Button` (Active), and possibly others are missing. – MasterHD Jul 07 '22 at 10:29
3

I suggest you qt-material package which has beautiful dark themes. It is easy to use, just install it as Python package and add these lines:

from qt_material import apply_stylesheet

...

app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()

# setup stylesheet
apply_stylesheet(app, theme='dark_teal.xml')

Documentation is here : https://pypi.org/project/qt-material/

wooshuwu
  • 133
  • 1
  • 3
tCot
  • 307
  • 2
  • 7
2

PyQtDarkTheme

pip3 install pyqtdarktheme

https://pyqtdarktheme.readthedocs.io/en/stable/how_to_use.html
https://pypi.org/project/pyqtdarktheme/


pyqtdarktheme make life esaier.

qdarktheme.setup_theme("auto")

It can follow OS dark-mode dynamically.

yurenchen
  • 1,897
  • 19
  • 17