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?
- Checking Your Current Style
- Solution 1: Use the Fusion Style
- Solution 2: Apply a Custom Qt Stylesheet (QSS)
- Solution 3: Bundle a QSS File with Your Package
- Solution 4: Set the Platform Theme via Environment Variable
- Solution 5: Use QPalette with Fusion for a Custom Color Scheme
- Which Approach Should You Use?
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:
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.
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.
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:
myapp/
├── __init__.py
├── __main__.py
├── main.py
└── styles/
└── app.qss
Your app.qss file contains the stylesheet rules:
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:
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:
[tool.setuptools.package-data]
myapp = ["styles/*.qss"]
Or if you're using a MANIFEST.in:
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:
export QT_QPA_PLATFORMTHEME=gtk3
For KDE:
export QT_QPA_PLATFORMTHEME=kde
You can also set this from within your application, before creating the QApplication:
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.
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.
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.