1
0
Fork 0
mirror of https://github.com/Video-Nomad/qt-css-engine.git synced 2026-05-01 10:54:07 +02:00
CSS transition animations for PyQt6/PySide6 — extends static QSS with transition and other properties
Find a file
George Sladkovsky 5dc9ea299a
Merge pull request #5 from Video-Nomad/dev (v0.1.1 Release Changes)
feat(release): v0.1.1 release changes

New Features: 
- added `:clicked` pseudo that plays the whole animation on click
- added `:active` pseudo support (window focus animation)
- added text-shadow alias for box-shadow
- WA_Hover is checked automatically and added if needed
- added optional event logging and env var
- performance improvments
- many new tests

Fixes:
- vastly improved nested layout size calculations
- mid/right click handling filters
- force-clear hover on focus shift
- hot-reload fixes
2026-04-28 10:00:04 +02:00
.github fix(workflow): workflow version fixes 2026-04-24 11:36:00 +02:00
demo refactor(engine): size calc improvements, tests, refactoring 2026-04-28 09:36:45 +02:00
qt_css_engine refactor(engine): size calc improvements, tests, refactoring 2026-04-28 09:36:45 +02:00
tests refactor(engine): size calc improvements, tests, refactoring 2026-04-28 09:36:45 +02:00
.gitignore initial commit 2026-04-20 11:58:47 +02:00
LICENSE.md initial commit 2026-04-20 11:58:47 +02:00
pyproject.toml fix(workflows): updated & fixed workflows 2026-04-24 11:34:16 +02:00
README.md feat(logging): basic event logging 2026-04-24 22:09:08 +02:00

Qt CSS Engine

A CSS animation engine for PyQt6/PySide6 that extends Qt's static stylesheet system (QSS) with dynamic CSS transitions and extra properties like box-shadow, opacity and CSS gradients. Qt's stylesheet engine has no concept of time or interpolation — this project implements an out-of-band animation system that intercepts transition: declarations from a stylesheet, tracks widget pseudo-states (hover, pressed, focus), and drives smooth property animations via Qt's animation framework. Dynamic class change is also supported so .btn -> .btn.active -> .btn.other-state will animate based on the transition property.

All that is required is to install TransitionEngine as an event filter and it will take care of the rest.

CSS Hot reload is supported via TransitionEngine.reload_rules(new_rules)

Subcontrols (e.g ::item, ::handle) are not supported because they are not real QWidgets. They will be just passed through as classic QSS stylesheet blocks.

This engine was made primarily for use in YASB project and this will be the main focus for now, but it can be integrated into any Qt application.

WARNING: This project is still in early development, very experimental and is not ready for production use. There will be bugs and breaking changes.

Python and Qt version

Python 3.14+

PySide6/PyQt6 6.10+

Installation

uv add qt-css-engine

Usage

from qt_css_engine import TransitionEngine, extract_rules

app = QApplication([])
cleaned_qss, rules = extract_rules(stylesheet)
app.setStyleSheet(cleaned_qss)
engine = TransitionEngine(rules)
app.installEventFilter(engine)

Simple transition examples

/* A simple widget with a hover transition */
.btn {
    background-color: steelblue;
    color: white;
    border-radius: 4px;
    transition: background-color 300ms ease;
}

.btn:hover {
    background-color: royalblue;
}

/* A widget with a box-shadow transition */
#btn {
    background-color: steelblue;
    color: white;
    border-radius: 4px;
    box-shadow: 0px 0px 0px black;
    transition: box-shadow 300ms ease;
}

#btn:hover {
    box-shadow: 4px 4px 4px black;
}

More complex examples

Check the ./demo/main.py for hot reloading, dynamic class change and more.

Transition syntax and supported values

Shorthand

transition: <property> <duration> [<easing>] [<delay>];
transition: <property> <duration> [<delay>] [<easing>];

/* multiple */
transition: background-color 300ms ease, border-radius 200ms linear 50ms;

/* all animatable properties */
transition: all 300ms ease-in-out;

Longhands

transition-property: background-color, border-radius;
transition-duration: 300ms, 200ms;
transition-timing-function: ease, linear;
transition-delay: 0ms, 50ms;

Longhands override the shorthand when both are declared in the same block. Values cycle per the CSS spec when list lengths differ.

Time units

Unit Example
Milliseconds 300ms
Seconds 0.3s

Easing curves

Value Description
linear Constant speed
ease Slow in, slow out (default)
ease-in Slow start
ease-out Slow end
ease-in-out Slow start and end
cubic-bezier(x1, y1, x2, y2) Custom curve — values outside [0, 1] produce overshoot
steps(n) / steps(n, jump-end|jump-start|jump-none|jump-both) Discrete stepped animation
step-start / step-end Aliases for steps(1, jump-start) / steps(1, jump-end)

Delay

Positive delay: animation starts after the delay elapses. The property is frozen at its current rendered value during the delay period.

Negative delay: animation starts immediately but offset |delay| ms into the timeline, as if it had already been running that long.

transition: background 400ms ease 100ms;   /* 100ms positive delay */
transition: background 400ms ease -100ms;  /* starts 100ms in */
/* OR */
transition: background 400ms 100ms ease;
transition: background 400ms -100ms ease;

Supported pseudo-classes

Pseudo-class Description Transition Static
:hover Hover in-out
:focus Focus in-out
:pressed Hold-down mouse button
:checked Checked state on-off
:active Window gains/loses focus
:clicked (NEW) Play full animation loop on click

Supported properties

Color values accepted everywhere a color is listed: named (red, steelblue, …), #rrggbb, #rrggbbaa, rgb(), rgba(), hsl(), hsla().

Numeric values accepted everywhere a length is listed: <n>px, <n>pt, <n>em.

Property Description Supported values Transition Static
background-color / background Background color color values; linear-gradient(), radial-gradient(), conic-gradient() (static only) solid colors
color Text color color values
border-color Border color shorthand (→ 4 sides) color values
border-top-color, border-right-color, border-bottom-color, border-left-color Per-side border color color values
border-width Border width shorthand (→ 4 sides) length values
border-top-width, border-right-width, border-bottom-width, border-left-width Per-side border width length values
border-radius Border radius shorthand (→ 4 corners) length values
border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius Per-corner border radius length values
padding Padding shorthand (→ 4 sides) length values
padding-top, padding-right, padding-bottom, padding-left Per-side padding length values
margin Margin shorthand (→ 4 sides) length values
margin-top, margin-right, margin-bottom, margin-left Per-side margin length values
width, height Widget size length values
min-width, max-width, min-height, max-height Size constraints length values
font-size Font size length values
font-weight Font weight 100900
letter-spacing Letter spacing length values
word-spacing Word spacing length values
spacing Qt widget item spacing length values
opacity Widget opacity — not native QSS, applied via QGraphicsOpacityEffect 0.01.0
box-shadow Drop shadow — not native QSS, applied via QGraphicsDropShadowEffect. No inset, spread is ignored. First shadow wins when multiple are declared. <x> <y> [blur] [spread] <color>
cursor Mouse cursor — Qt QSS ignores cursor, applied via setCursor() default, pointer, text, crosshair, wait, progress, help, move, grab, grabbing, copy, alias, not-allowed, no-drop, cell, all-scroll, n-resize, s-resize, e-resize, w-resize, ne-resize, nw-resize, se-resize, sw-resize, ns-resize, ew-resize, nesw-resize, nwse-resize, row-resize, col-resize, none

Environment variables

Variable Description Default
CSS_ENGINE_LEFT_CLICK_ONLY Exclude middle and right click from triggering on-click animations False
CSS_ENGINE_EVENT_LOGGING Enable internal event debug logging (qt_css_engine.event logger) False