Skip to content

Reactivity

Reactivity

Reactivity is core concept within Slint. It allows the creation of complex dynamic user interfaces with a fraction of the code. The following examples will help you understand the basics of reactivity.

export component MyComponent {
width: 400px; height: 400px;
Rectangle {
background: #151515;
}
ta := TouchArea {}
myRect := Rectangle {
x: ta.mouse-x;
y: ta.mouse-y;
width: 40px;
height: 40px;
background: ta.pressed ? orange : white;
}
Text {
x: 5px; y: 5px;
text: "x: " + myRect.x / 1px;
color: white;
}
Text {
x: 5px; y: 15px;
text: "y: " + myRect.y / 1px;
color: white;
}
}
slint

As the name suggests, Reactivity is all about parts of the user interface automatically updating or ‘reacting’ to changes. The above example looks simple, but when run it does several things:

  • The Rectangle will follow the mouse around as you move it.
  • If you click anywhere the Rectangle will change color.
  • The Text elements will update their text to show the current position of the Rectangle.

The ‘magic’ here is built into the Slint language directly. There is no need to opt into this or define specific stateful items. The Rectangle will automatically updates because it’s x and y properties are bound to the mouse-x and mouse-y properties of the TouchArea element. This was done by giving the TouchArea a name to identify it ta and then using the name in what Slint calls an expression to track values. Its as simples as x: ta.mouse-x; and y: ta.mouse-y;. The mouse-x and mouse-y properties are built into the TouchArea and automatically update as the cursor moves over them.

The TouchArea also has a pressed property that is only true when the cursor is pressed or clicked down. So the ternary expression background: ta.pressed ? orange : white; will change the background color of the Rectangle to orange when ta.pressed is true, or white when it isn’t.

Similarly the 2 text items are updating by tracking the rectangle’s x and y position.

Performance

From a performance perspective, Slint works out what properties are changed. It then finds all the expressions that depend on that value. These dependencies are then re-evaluated based on the new values and the UI will update.

The re-evaluation happens lazily when the property is queried.

Internally, a dependency is registered for any property accessed while evaluating a binding. When a property changes, the dependencies are notified and all dependent bindings are marked as dirty.

Property Expressions

Expressions can vary in complexity:

// Tracks the `x` value of an element called foo
x: foo.x;
// Tracks the value, but sets it to 0px or 400px based on if
// foo.x is greater than 400px
x: foo.x > 100px ? 0px : 400px;
// Tracks the value, but clamps it between 0px and 400px
x: clamp(foo.x, 0px, 400px);
slint

As the last example shows functions can be used as part of a property expression. This can be useful for when an expression is too complex to be readable or maintained as a single line.

export component MyComponent {
width: 400px; height: 400px;
pure function lengthToInt(n: length) -> int {
return (n / 1px);
}
Rectangle {
background: #151515;
}
ta := TouchArea {}
myRect := Rectangle {
x: ta.mouse-x;
y: ta.mouse-y;
width: 40px;
height: 40px;
background: ta.pressed ? orange : white;
}
Text {
x: 5px; y: 5px;
text: "x: " + lengthToInt(myRect.x);
}
Text {
x: 5px; y: 15px;
text: "y: " + lengthToInt(myRect.y);
}
}
slint

Here the earlier example was updated to use a function to convert the length to an integer. This also truncates the x and y values to be more readable i.e. ‘4’ instead of ‘4.124488’.

Purity

For any reactive system to work well, evaluating a property shouldn’t change any observable state but the property itself. If this is the case, then the expression is “pure”, otherwise it’s said to have side-effects. Side-effects are problematic because it’s not always clear when they will happen: Lazy evaluation may change their order or affect whether they happen at all. In addition, changes to properties during their binding evaluation due to a side-effect may result in unexpected behavior.

For this reason, bindings in Slint must be pure. The Slint compiler enforces code in pure contexts to be free of side effects. Pure contexts include binding expressions, bodies of pure functions, and bodies of pure callback handlers. In such a context, it’s not allowed to change a property, or call a non-pure callback or function.

Annotate callbacks and public functions with the pure keyword to make them accessible from property bindings and other pure callbacks and functions.

The purity of private functions is automatically inferred. You may declare private functions explicitly “pure” to have the compiler enforce their purity.

export component Example {
pure callback foo() -> int;
public pure function bar(x: int) -> int
{ return x + foo(); }
}
slint

Two-Way Bindings

Create two-way bindings between properties with the <=> syntax. These properties will be linked together and always contain the same value.

The right hand side of the <=> must be a reference to a property of the same type. The property type is optional with two-way bindings, it will be inferred if not specified. The initial value of a linked property will be the value of the right hand side of the binding. The two linked properties must be compatible in terms of input/output.

export component Example {
in property<brush> rect-color <=> r.background;
// It's allowed to omit the type to have it automatically inferred
in property rect-color2 <=> r.background;
r:= Rectangle {
width: parent.width;
height: parent.height;
background: blue;
}
}
slint

© 2025 SixtyFPS GmbH