Skip to main content

Modifiers

A modifier transforms a component, producing a modified version of it. Modifiers are applied using a with block, which can follow a component constructor call or any other expression that produces a component.

Applying Modifiers​

import { Text, Padding, Background, Rectangle } from ui

export var main: Component = Text("Hello") with {
Padding(8)
Background {
Rectangle(color: Color(0xFF222222))
}
}

Modifiers are applied in order from top to bottom. A modifier applied later wraps the result of all previous modifiers, so order affects the visual result.

Applying Modifiers to Expressions​

A with block is not restricted to component constructor calls. It can follow any expression that evaluates to a component — most commonly a template invocation such as self.children() or self.item(…):

import { VStack, Text, Padding, Background, Rectangle } from ui

component Panel {
@property children: Template()
@property item: Template(String)

VStack {
self.item("Featured") with {
Background {
Rectangle(color: Color(0xFF3333AA))
}
}
self.children() with {
Padding(8)
}
}
}

export var main: Component = Panel(item: template (text) { Text(text) }) {
Text("First")
Text("Second")
}

It works the same way for properties or state of type Component, and for expressions that select one out of several — an array element, or a parenthesized ternary:

import { VStack, Text, Padding } from ui

component Gallery {
@property highlight: Component

slides: [Component] = [Text("One"), Text("Two")]

VStack {
self.highlight with {
Padding(8)
}
self.slides[0] with {
Padding(4)
}
}
}

export var main: Component = Gallery(highlight: Text("Highlight"))

Because a template invocation can yield more than one component, the rules in Modifiers on Multi-Child Components apply to it as well: in the first example, Padding(8) is applied to each of the two children separately, by one shared modifier instance.

A call to a function or method returning a Component takes a with block just the same:

import { VStack, Text, Padding } from ui

fun labelled(text: String) -> (Component) {
return Text(text)
}

component Panel {
boxed(text: String) -> (Component) {
return Text(text)
}

VStack {
labelled(text: "From a function") with {
Padding(4)
}
self.boxed(text: "From a method") with {
Padding(6)
}
}
}

export var main: Component = Panel()

Producing components from a function or method comes with a reactivity cost, though — every argument becomes a dependency of the surrounding context, so the component is recreated whenever one changes. Prefer a template for that purpose: see Templates vs Functions.

Declaring Custom Modifiers​

Define a modifier with the modifier keyword. The modified component is available inside the body as the implicit child variable. Like component bodies, modifier bodies can produce multiple sibling components. Declarative if and for are also available — see Declarative Control Flow.

import { Padding } from ui

modifier LargePadding {
child with {
Padding(24)
}
}

Apply it just like any built-in modifier:

import { Padding, Text } from ui

modifier LargePadding {
child with {
Padding(24)
}
}

export var main: Component = Text("Hello") with {
LargePadding()
}

Modifier Properties, State, and Bindings​

Like components, modifiers support @property, @binding, and state.

import { Background, Rectangle } from ui

modifier ColoredBackground {
@property color: Color

child with {
Background {
Rectangle(color: self.color)
}
}
}

Modifier bodies follow the same member order as components: properties and bindings first, then constructors, then state and methods, and the child expression last.

Modifiers on Multi-Child Components​

When a modifier is applied to a component that yields multiple children, the modifier is applied separately to each child — but it is the same modifier instance for all of them. This means any state inside the modifier is shared across every child it wraps.

import { VStack, Text, TapGesture, Background, Rectangle } from ui

component Two {
Text("First")
Text("Second")
}

modifier Highlight {
active: Bool = false

child with {
TapGesture(fun (_) {
self.active = not self.active
})
Background {
Rectangle(color: self.active ? Color(0xFFFF0000) : Color(0xFFDFDFDF))
}
}
}

component Main {
VStack {
Two() with {
Highlight()
}
}
}

export var main: Component = Main()

In this example both Text components are siblings in the VStack and are spaced by it independently. Each gets its own background applied by Highlight. However, because both children share the same Highlight instance, clicking either one toggles self.active for the whole modifier — turning both backgrounds red simultaneously.

Generic Modifiers Experimental​

Modifiers can declare type parameters, e.g. modifier Selectable<Value>. See Generic Modifiers for an example.

Exporting Modifiers​

Modifiers can be exported from a module:

import { Padding } from ui

export modifier LargePadding {
child with {
Padding(24)
}
}

See Modules for details.