Styling PyQt/PySide Apps Installed via Pip on Linux

How to handle Qt styling and themes when distributing your Python GUI app through pip
Heads up! You've already completed this tutorial.

If you've built a PyQt or PySide application that looks great when you run it from your development environment, you might be surprised when you install it via pip and find it looking... not so great. On Linux especially, your app can end up with a flat, unstyled appearance — missing the native desktop look you expected.

This is a common issue, and it has a straightforward explanation. In this article, we'll look at why this happens and walk through several practical ways to fix it.

Why Does Styling Break on Linux?

When you run a Qt application on Linux, Qt tries to match the look and feel of your desktop environment (GNOME, KDE, etc.) by using a platform theme plugin. These plugins read your system's theme settings and apply them to your app's widgets — giving you native-looking buttons, scrollbars, and menus.

The problem is that these platform theme plugins are separate from Qt itself. They depend on system-level Qt libraries and desktop integration packages. When your app is installed via pip into a virtual environment (or a user install), it uses the PyQt5/PyQt6/PySide6 wheels from PyPI. These wheels bundle their own copy of the Qt libraries, but they don't include the platform theme plugins that integrate with your desktop environment.

The result? Qt falls back to a basic, unstyled look — sometimes called the "Windows 95" style.

Checking Your Current Style

Before applying fixes, it helps to see what's going on. You can check which style your app is using and what styles are available with a small script:

python
import sys
from PyQt6.QtWidgets import QApplication

app = QApplication(sys.argv)

print("Available styles:", app.style().objectName())
print("All styles:", [s for s in QApplication.style().objectName()])

# List all available style keys
from PyQt6.QtWidgets import QStyleFactory
print("Style factory keys:", QStyleFactory.keys())

On a Linux system using pip-installed PyQt6, you'll typically see only a handful of styles like Fusion and Windows, but not platform-native ones like gtk2 or Breeze.

Solution 1: Use the Fusion Style

The simplest and most portable solution is to explicitly set your app to use Qt's built-in Fusion style. Fusion is a modern, clean style that ships with Qt itself — so it's always available, regardless of how your app was installed.

python
import sys
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QPushButton,
    QVBoxLayout, QWidget, QCheckBox, QSlider,
    QComboBox, QLabel
)
from PyQt6.QtCore import Qt


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Fusion Style Example")

        layout = QVBoxLayout()
        layout.addWidget(QLabel("This app uses the Fusion style"))
        layout.addWidget(QPushButton("Click Me"))
        layout.addWidget(QCheckBox("Enable feature"))
        layout.addWidget(QSlider(Qt.Orientation.Horizontal))

        combo = QComboBox()
        combo.addItems(["Option A", "Option B", "Option C"])
        layout.addWidget(combo)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)


app = QApplication(sys.argv)
app.setStyle("Fusion")

window = MainWindow()
window.show()
sys.exit(app.exec())

By calling app.setStyle("Fusion") before showing any windows, you guarantee a consistent, modern appearance on every platform — Linux, macOS, and Windows. If you're new to building Qt applications, see our guide to creating your first window with PyQt6 for the fundamentals.

Solution 2: Apply a Custom Qt Stylesheet (QSS)

If you want more control over the look of your app, you can use Qt Style Sheets (QSS). These work similarly to CSS in web development — you define rules that target widgets and set visual properties like colors, borders, padding, and fonts.

This approach is completely independent of the system theme, so it works reliably everywhere.

python
import sys
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QPushButton,
    QVBoxLayout, QWidget, QLabel
)


STYLESHEET = """
    QMainWindow {
        background-color: #2b2b2b;
    }
    QLabel {
        color: #ffffff;
        font-size: 14px;
        padding: 4px;
    }
    QPushButton {
        background-color: #4a86c8;
        color: white;
        border: none;
        border-radius: 4px;
        padding: 8px 16px;
        font-size: 13px;
    }
    QPushButton:hover {
        background-color: #5a96d8;
    }
    QPushButton:pressed {
        background-color: #3a76b8;
    }
"""


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Custom Styled App")

        layout = QVBoxLayout()
        layout.addWidget(QLabel("Custom styled application"))
        layout.addWidget(QPushButton("Primary Action"))
        layout.addWidget(QPushButton("Secondary Action"))

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)


app = QApplication(sys.argv)
app.setStyle("Fusion")  # Use Fusion as the base style
app.setStyleSheet(STYLESHEET)

window = MainWindow()
window.show()
sys.exit(app.exec())

Setting Fusion as the base style before applying your stylesheet gives you a clean, neutral starting point that your QSS rules can build on top of.

Solution 3: Bundle a QSS File with Your Package

For larger projects, keeping your stylesheet in a separate .qss file makes it easier to maintain. You can bundle this file inside your Python package and load it at runtime.

Here's a typical project layout:

python
myapp/
├── __init__.py
├── __main__.py
├── main.py
└── styles/
    └── app.qss

Your app.qss file contains the stylesheet rules:

css
QPushButton {
    background-color: #4a86c8;
    color: white;
    border: none;
    border-radius: 4px;
    padding: 8px 16px;
}

QPushButton:hover {
    background-color: #5a96d8;
}

Then in your application code, load it using importlib.resources (Python 3.9+) or pathlib:

python
import sys
from pathlib import Path
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget


def load_stylesheet():
    """Load the QSS stylesheet from the package's styles directory."""
    style_path = Path(__file__).parent / "styles" / "app.qss"
    if style_path.exists():
        return style_path.read_text()
    return ""


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Bundled Style App")

        layout = QVBoxLayout()
        layout.addWidget(QPushButton("Styled Button"))
        layout.addWidget(QPushButton("Another Button"))

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)


def main():
    app = QApplication(sys.argv)
    app.setStyle("Fusion")
    app.setStyleSheet(load_stylesheet())

    window = MainWindow()
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()

To make sure your .qss file is included when you build your pip package, add it to your pyproject.toml:

toml
[tool.setuptools.package-data]
myapp = ["styles/*.qss"]

Or if you're using a MANIFEST.in:

python
recursive-include myapp/styles *.qss

This way the stylesheet travels with your package no matter how it's installed. For an alternative approach to bundling resources, you can also use the Qt Resource System to embed stylesheets and other assets directly into your application.

Solution 4: Set the Platform Theme via Environment Variable

If you want your pip-installed app to use the native system theme on Linux, you can try setting the QT_QPA_PLATFORMTHEME environment variable. This tells Qt which platform theme plugin to load.

For GNOME-based desktops:

bash
export QT_QPA_PLATFORMTHEME=gtk3

For KDE:

bash
export QT_QPA_PLATFORMTHEME=kde

You can also set this from within your application, before creating the QApplication:

python
import sys
import os

os.environ["QT_QPA_PLATFORMTHEME"] = "gtk3"

from PyQt6.QtWidgets import QApplication, QMainWindow

app = QApplication(sys.argv)
# ... rest of your app

However, this approach has a significant caveat: it only works if the corresponding platform theme plugin is installed on the user's system. For the gtk3 theme, the user would need the qt6-gtk-platformtheme package (or equivalent for their distribution) installed. Since you can't guarantee this for every user, this is less reliable than the Fusion or QSS approaches for apps distributed via pip.

Solution 5: Use QPalette with Fusion for a Custom Color Scheme

A middle ground between the plain Fusion style and a full stylesheet is to customize Fusion's color palette. This gives you a polished, consistent look while keeping the native widget behavior (hover effects, focus indicators, etc.) intact.

python
import sys
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QPushButton,
    QVBoxLayout, QWidget, QCheckBox, QLabel,
    QSlider, QComboBox, QLineEdit
)
from PyQt6.QtGui import QPalette, QColor
from PyQt6.QtCore import Qt


def create_dark_palette():
    """Create a dark color palette for the Fusion style."""
    palette = QPalette()

    # Base colors
    palette.setColor(QPalette.ColorRole.Window, QColor(53, 53, 53))
    palette.setColor(QPalette.ColorRole.WindowText, QColor(255, 255, 255))
    palette.setColor(QPalette.ColorRole.Base, QColor(35, 35, 35))
    palette.setColor(QPalette.ColorRole.AlternateBase, QColor(53, 53, 53))
    palette.setColor(QPalette.ColorRole.ToolTipBase, QColor(25, 25, 25))
    palette.setColor(QPalette.ColorRole.ToolTipText, QColor(255, 255, 255))
    palette.setColor(QPalette.ColorRole.Text, QColor(255, 255, 255))
    palette.setColor(QPalette.ColorRole.Button, QColor(53, 53, 53))
    palette.setColor(QPalette.ColorRole.ButtonText, QColor(255, 255, 255))
    palette.setColor(QPalette.ColorRole.BrightText, QColor(255, 0, 0))
    palette.setColor(QPalette.ColorRole.Link, QColor(42, 130, 218))
    palette.setColor(QPalette.ColorRole.Highlight, QColor(42, 130, 218))
    palette.setColor(QPalette.ColorRole.HighlightedText, QColor(35, 35, 35))

    # Disabled state
    palette.setColor(
        QPalette.ColorGroup.Disabled,
        QPalette.ColorRole.WindowText,
        QColor(127, 127, 127),
    )
    palette.setColor(
        QPalette.ColorGroup.Disabled,
        QPalette.ColorRole.Text,
        QColor(127, 127, 127),
    )
    palette.setColor(
        QPalette.ColorGroup.Disabled,
        QPalette.ColorRole.ButtonText,
        QColor(127, 127, 127),
    )
    palette.setColor(
        QPalette.ColorGroup.Disabled,
        QPalette.ColorRole.HighlightedText,
        QColor(127, 127, 127),
    )

    return palette


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Dark Fusion Theme")

        layout = QVBoxLayout()
        layout.addWidget(QLabel("Dark Fusion palette example"))
        layout.addWidget(QLineEdit("Editable text"))
        layout.addWidget(QPushButton("Enabled Button"))

        disabled_btn = QPushButton("Disabled Button")
        disabled_btn.setEnabled(False)
        layout.addWidget(disabled_btn)

        layout.addWidget(QCheckBox("A checkbox option"))
        layout.addWidget(QSlider(Qt.Orientation.Horizontal))

        combo = QComboBox()
        combo.addItems(["First", "Second", "Third"])
        layout.addWidget(combo)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)


app = QApplication(sys.argv)
app.setStyle("Fusion")
app.setPalette(create_dark_palette())

window = MainWindow()
window.show()
sys.exit(app.exec())

The QPalette approach works well because Fusion respects the palette colors throughout all its widget rendering. You get a cohesive dark (or custom-colored) theme with proper disabled states, hover effects, and focus indicators — all without writing a single line of QSS.

Which Approach Should You Use?

Here's a quick summary to help you decide:

Approach Pros Cons
Fusion style Always available, zero config Doesn't match native desktop theme
QSS stylesheet Full visual control, portable Can be verbose for complex UIs
Bundled QSS file Maintainable, separates style from logic Requires packaging setup
Environment variable Can get native theme Depends on user's system packages
QPalette + Fusion Clean, native-feeling widget behavior Limited to color changes

For most pip-distributed applications, Fusion style combined with either a QPalette or a QSS stylesheet is the most reliable approach. It gives you a consistent, professional appearance across all Linux distributions, as well as on macOS and Windows, without depending on anything outside your package.

The reality of distributing Python GUI apps via pip on Linux is that you can't rely on the system's Qt theme integration. By taking control of your styling explicitly, you ensure your users get a polished experience regardless of their desktop environment or how they installed your app. If you're ready to package your application for distribution, see our guide on packaging PyQt6 apps for Linux with PyInstaller for the next steps.

Well done, you've finished this tutorial! Mark As Complete
[[ user.completed.length ]] completed [[ user.streak+1 ]] day streak

PyQt/PySide 1:1 Coaching with Martin Fitzpatrick

Save yourself time and frustration. Get one on one help with your Python GUI projects. Working together with you I'll identify issues and suggest fixes, from bugs and usability to architecture and maintainability.

Book Now 60 mins ($195)

Martin Fitzpatrick

Styling PyQt/PySide Apps Installed via Pip on Linux was written by Martin Fitzpatrick.

Martin Fitzpatrick has been developing Python/Qt apps for 8 years. Building desktop applications to make data-analysis tools more user-friendly, Python was the obvious choice. Starting with Tk, later moving to wxWidgets and finally adopting PyQt. Martin founded PythonGUIs to provide easy to follow GUI programming tutorials to the Python community. He has written a number of popular Python books on the subject.