Dark theme for Qt widgets?
Asked Answered
C

8

48

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)?

Craigcraighead answered 15/1, 2018 at 3:59 Comment(1)
Check the following link github.com/ColinDuquesnoy/QDarkStyleSheetHumeral
F
75

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.

Forspent answered 15/1, 2018 at 4:5 Comment(6)
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.Farron
@Farron Feel free to post an issue and I can address it. Feature or pull requests would be wonderful.Forspent
@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 appCeja
@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.Forspent
@Ceja 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.Forspent
@AlexanderHuszagh It definitely answers my question, thank you!Ceja
M
38

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

Mandamandaean answered 2/7, 2019 at 11:31 Comment(5)
what about default icons? that has to be taken care separately right?Mcmath
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 properlyInterlink
@Interlink I would be interested to see what you did. also Michael great work thanks for thisTamatave
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.Giblets
If you're using PyQt6 you will need to add ColorRole to reach out the constants from QPalette: QPalette.ColorRole.WindowFlooded
N
9

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.

Narration answered 27/5, 2020 at 3:37 Comment(2)
Using this approach, how would you go back to the standard palette?Phonetic
Updated my post to reflect current way im handling both Light/Dark mode with Qsetting.Narration
L
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.

Loganiaceous answered 15/1, 2018 at 7:36 Comment(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.Barbarism
C
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/

Chargeable answered 29/9, 2022 at 9:52 Comment(0)
A
3

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.

Ahn answered 25/5, 2023 at 17:59 Comment(0)
P
0

In Qt 6.5 (PyQt6), there is a proper built-in way to get the system light/dark theme.

If all you want is for your widgets to respect the system settings, you can set your application style to 'fusion', and let Qt do the rest. Beware that this might make stuff look horrible if you have stylesheets or are using custom colors.

with QApplication(sys.argv) as qapp:
    qapp.setApplicationName("My Awesome App")
    qapp.setStyle('fusion')

    w = MainUIWindow()
    w.show()

    sys.exit(qapp.exec())

If you want to catch the change from light to dark mode so you can update the stylesheet/custom color, you can connect to the QApplication.styleHints().colorSchemeChanged signal, which will give you information about the new Qt.ColorScheme (either Light, Dark or Unknown).

For more information, and some other suggestions on how to have a bit more control over the process, have a look at this answer and the related article on dark mode for windows 11

Phenomenal answered 25/2 at 5:5 Comment(0)
B
0

This is working great, i tested it on python 3.12

https://pypi.org/project/pyqtdarktheme/

Brittanybritte answered 3/6 at 1:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.