SCSS Hot Reload¶
QtPie provides built-in support for SCSS (Sassy CSS) compilation and hot-reloading. This means you can write your stylesheets in SCSS with variables, imports, and nesting, and see changes reflected instantly in your running application.
Why SCSS?¶
SCSS is a superset of CSS that adds powerful features: - Variables: Define colors, sizes, and other values once and reuse them - Imports: Split styles across multiple files for better organization - Nesting: Write cleaner, more maintainable selectors - Functions and mixins: Create reusable style patterns
Since Qt stylesheets (QSS) are CSS-like, SCSS compiles perfectly to QSS.
Declarative Alternatives¶
Before diving into manual watch_scss() calls, consider these simpler declarative options:
Using @entrypoint (App-Wide Styles)¶
The simplest approach - add stylesheet options directly to your entry point:
from qtpie import entrypoint, widget, make
from qtpy.QtWidgets import QWidget, QPushButton
@entrypoint(
stylesheet="styles.scss",
watch_stylesheet=True,
scss_search_paths=["partials/"]
)
@widget
class MyApp(QWidget):
button: QPushButton = make(QPushButton, "Click Me")
This applies styles to the entire application with hot-reload enabled.
Using @stylesheet (Component Styles)¶
For component-level styles, use the @stylesheet decorator:
from qtpie import stylesheet, widget, make
from qtpy.QtWidgets import QWidget, QLabel
@stylesheet("card.scss", watch=True)
@widget
class Card(QWidget):
title: QLabel = make(QLabel, "Card Title")
This scopes styles to the widget and its children.
When to Use Each Approach¶
| Approach | Use When |
|---|---|
@entrypoint(stylesheet=...) |
App-wide styles, simplest setup |
@stylesheet on widget |
Component-scoped styles, reusable widgets |
watch_scss() (below) |
Need manual control, dynamic paths, or conditional loading |
If the declarative options work for your use case, you may not need the manual approach described below.
Quick Start¶
The simplest way to use SCSS with hot reload is the watch_scss() function:
from qtpie import entrypoint, widget, make
from qtpie.styles import watch_scss
from qtpy.QtWidgets import QWidget, QPushButton
@entrypoint
@widget
class MyApp(QWidget):
button: QPushButton = make(QPushButton, "Click Me")
def setup(self) -> None:
# Watch SCSS file and auto-reload on changes
self.watcher = watch_scss(
target=self,
scss_path="styles.scss",
qss_path="output.qss",
)
Create styles.scss:
$primary: #007bff;
$padding: 8px;
QPushButton {
background-color: $primary;
padding: $padding;
color: white;
}
Now when you edit and save styles.scss, your app automatically reloads the styles!
How It Works¶
QtPie's SCSS hot reload system has three parts:
- Compiler (
compile_scss) - Compiles SCSS to QSS - Watcher (
watch_scss) - Watches files and triggers recompilation - Hot reload - Applies new styles to your widget without restarting
The watcher handles tricky edge cases like: - Editor save behaviors (delete + recreate) - Imported file changes - Files that don't exist yet - Debouncing rapid changes
Basic Setup¶
Single SCSS File¶
For a simple app with one SCSS file:
from qtpie import widget, App
from qtpie.styles import watch_scss
from qtpy.QtWidgets import QWidget
@widget
class MainWindow(QWidget):
def setup(self) -> None:
self.watcher = watch_scss(
target=self,
scss_path="styles.scss",
qss_path="styles.qss",
)
app = App("My App")
window = MainWindow()
window.show()
app.run()
Application-Level Styles¶
To apply styles to the entire application:
from qtpie import App
from qtpie.styles import watch_scss
class MyApp(App):
def setup(self) -> None:
# Apply to entire app (self is QApplication)
self.watcher = watch_scss(
target=self,
scss_path="app.scss",
qss_path="app.qss",
)
app = MyApp("My App")
# ... create and show widgets
app.run()
Using @import¶
Split your styles across multiple files for better organization.
Project Structure¶
myapp/
├── styles/
│ ├── main.scss # Main file
│ └── partials/ # Imported files
│ ├── _variables.scss
│ └── _buttons.scss
└── main.py
partials/_variables.scss¶
partials/_buttons.scss¶
main.scss¶
Python Code¶
from qtpie import widget
from qtpie.styles import watch_scss
from qtpy.QtWidgets import QWidget
@widget
class MyWidget(QWidget):
def setup(self) -> None:
self.watcher = watch_scss(
target=self,
scss_path="styles/main.scss",
qss_path="styles/output.qss",
search_paths=["styles/partials"], # Where to find imports
)
When you change any file in partials/, the watcher detects it and recompiles automatically!
Multiple Search Paths¶
For larger projects, you might have imports from multiple directories:
myapp/
├── styles/
│ ├── main.scss
│ ├── core/ # Base variables, mixins
│ │ └── _variables.scss
│ └── themes/ # Theme-specific styles
│ └── _theme.scss
self.watcher = watch_scss(
target=self,
scss_path="styles/main.scss",
qss_path="styles/output.qss",
search_paths=[
"styles/core",
"styles/themes",
],
)
The watcher monitors all search paths, so changing any imported file triggers a recompile.
Watch Functions¶
QtPie provides three watch functions:
watch_scss()¶
For SCSS files that need compilation:
from qtpie.styles import watch_scss
watcher = watch_scss(
target=widget, # QWidget or QApplication
scss_path="styles.scss", # Input SCSS file
qss_path="output.qss", # Output QSS file
search_paths=None, # Optional: list of import directories
)
watch_qss()¶
For plain QSS files (no compilation):
watch_styles()¶
Convenience function that auto-detects:
from qtpie.styles import watch_styles
# With SCSS
watcher = watch_styles(
target=widget,
qss_path="output.qss",
scss_path="styles.scss", # If provided, uses watch_scss
search_paths=["partials"],
)
# Without SCSS (just watches QSS)
watcher = watch_styles(
target=widget,
qss_path="styles.qss", # No scss_path = uses watch_qss
)
Compile Without Watching¶
If you just want to compile once (e.g., in a build script):
from qtpie.styles import compile_scss
compile_scss(
scss_path="styles.scss",
qss_path="output.qss",
search_paths=["partials"],
)
This raises FileNotFoundError if the SCSS file doesn't exist, or SassError if there are syntax errors.
Watcher Lifecycle¶
Keep a reference to the watcher! If it gets garbage collected, the watching stops:
# GOOD - watcher stays alive
class MyWidget(QWidget):
def setup(self) -> None:
self.watcher = watch_scss(...) # Stored as instance variable
# BAD - watcher gets garbage collected
class MyWidget(QWidget):
def setup(self) -> None:
watch_scss(...) # No reference! Stops working immediately
To manually stop watching:
Watching Non-Existent Files¶
The watcher works even if files don't exist yet:
# File doesn't exist yet
watcher = watch_qss(widget, "styles.qss")
# Later... create the file
Path("styles.qss").write_text("QPushButton { color: red; }")
# Watcher detects it and applies styles automatically!
This is useful during development when you're creating files on the fly.
Editor Compatibility¶
The watcher handles different editor save behaviors:
- VSCode, Sublime: Modify file in-place (works great)
- Vim, Emacs: Delete and recreate (works great - watcher re-arms)
- All editors: Debounces rapid changes (150ms) to avoid flicker
Signals¶
Both QssWatcher and ScssWatcher emit a stylesheetApplied signal when styles are reloaded:
from qtpie.styles import watch_scss
watcher = watch_scss(...)
def on_styles_applied() -> None:
print("Styles reloaded!")
watcher.stylesheetApplied.connect(on_styles_applied)
Development Workflow¶
Here's a typical workflow:
- Create your SCSS file with variables and structure
- Set up
watch_scss()in your widget'ssetup()method - Run your app
- Edit SCSS files in your favorite editor
- Save the file
- See changes instantly in your running app!
No restarts, no manual recompilation - just edit and save.
Examples from Tests¶
Simple Hot Reload¶
from qtpie.styles import watch_qss
from qtpy.QtWidgets import QWidget
widget = QWidget()
# Create and watch QSS file
qss_file = Path("styles.qss")
qss_file.write_text("QWidget { background-color: red; }")
watcher = watch_qss(widget, str(qss_file))
# Change the file
qss_file.write_text("QWidget { background-color: blue; }")
# Widget stylesheet updates automatically!
SCSS with Imports¶
from qtpie.styles import watch_scss
from qtpy.QtWidgets import QWidget
widget = QWidget()
# Create partials
partials = Path("partials")
partials.mkdir()
variables = partials / "_variables.scss"
variables.write_text("$bg: orange;")
# Main file with import
main = Path("main.scss")
main.write_text("@import 'variables'; QWidget { background: $bg; }")
# Watch with search path
watcher = watch_scss(
widget,
str(main),
"output.qss",
search_paths=["partials"],
)
# Change imported file
variables.write_text("$bg: pink;")
# Watcher detects the change and recompiles!
See Also¶
- Styling Basics - CSS classes and selectors
- Color Schemes - Dark/light mode
- App & Entry Points - Using App.setup()