This page is a detailed companion to the Expressions guide. It covers every operator, pipe, and special variable available in {{ }} expressions and their Rx equivalents, with enough context to use each one confidently.
Every entry shows both the Python Rx syntax and the equivalent {{ }} protocol syntax. They compile to the same thing; choose whichever fits your context.
Operators
Operators let expressions compute values from state rather than just reading them. They follow standard mathematical precedence: multiplication and division before addition and subtraction, with parentheses for explicit grouping.
Arithmetic
| Python | Protocol | Description |
|---|
count + 1 | {{ count + 1 }} | Addition |
total - discount | {{ total - discount }} | Subtraction |
price * quantity | {{ price * quantity }} | Multiplication |
amount / 2 | {{ amount / 2 }} | Division |
-score | {{ -score }} | Negation |
Arithmetic expressions can appear anywhere a value is expected: in a Text component, in a SetState action value, or in a prop like Progress(value=...).
Comparison
| Python | Protocol | Meaning |
|---|
count > 0 | {{ count > 0 }} | Greater than |
count >= 10 | {{ count >= 10 }} | Greater than or equal |
count < 100 | {{ count < 100 }} | Less than |
count <= 50 | {{ count <= 50 }} | Less than or equal |
status == 'active' | {{ status == 'active' }} | Equal (loose) |
status != 'done' | {{ status != 'done' }} | Not equal |
Logical
| Python | Protocol | Meaning |
|---|
a & b | {{ a && b }} | AND |
a | b | {{ a || b }} | OR |
~a | {{ !a }} | NOT |
The protocol also accepts and, or, not as keyword alternatives. Both && and || short-circuit. || doubles as a falsy default: {{ name || 'Anonymous' }}.
Bitwise & and | bind tighter than comparisons in Python. Always parenthesize: (score > 0) & (score < 100).
Ternary
| Python | Protocol |
|---|
active.then("On", "Off") | {{ active ? 'On' : 'Off' }} |
(score > 90).then("Pass", "Fail") | {{ score > 90 ? 'Pass' : 'Fail' }} |
String Concatenation
F-strings are preferred: f"Hello, {name}!" compiles to Hello, {{ name }}!
The + operator concatenates when either operand is a string: {{ 'Hello, ' + name }}.
Pipes
Pipes transform expression values for display. A raw number like 2847500 becomes $2,847,500.00 with the currency pipe; an ISO date string becomes “March 15, 2025” with date:long. In Python, pipes are method calls on Rx objects; in the protocol, they chain with |.
Pipes process values left to right. In {{ todos | rejectattr:done | length }}, the array is first filtered, then counted. You can chain as many pipes as needed.
| Pipe | Python | Protocol | Result |
|---|
currency | .currency() / .currency("EUR") | {{ x | currency }} / {{ x | currency:EUR }} | $1,234.00 / €1,234.00 |
percent | .percent() / .percent(1) | {{ x | percent }} / {{ x | percent:1 }} | 76% / 75.6% |
number | .number() / .number(2) | {{ x | number }} / {{ x | number:2 }} | 1,234 / 1,234.00 |
round | .round(2) | {{ x | round:2 }} | 3.14 |
abs | .abs() | {{ x | abs }} | 42 |
percent multiplies by 100 before formatting. A value of 0.756 becomes 75.6%, not 0.756%. number and currency produce locale-formatted output (en-US by default).
Date and Time
| Pipe | Argument | Example Output |
|---|
date | short | 1/15/2025 |
date | (default: medium) | Jan 15, 2025 |
date | long | January 15, 2025 |
time | | 2:30 PM |
datetime | | Jan 15, 2025, 2:30 PM |
Input must be an ISO date string. The time pipe also accepts time-only strings like "14:30".
| Pipe | Argument | Description |
|---|
upper | | Uppercase the string |
lower | | Lowercase the string |
truncate | max length | Clamp to N characters, append ... if truncated |
pluralize | singular word (default item) | Returns the word as-is for count 1, appends s otherwise |
pluralize pairs well with length for labeling dynamic counts: Rx("items").length().pluralize("item") produces {{ items | length | pluralize:'item' }}, rendering as “1 item” or “3 items”.
Array Operations
| Pipe | Argument | Description |
|---|
length | | Number of elements (also works on strings) |
join | separator (default , ) | Join elements into a string |
first | | First element |
last | | Last element |
selectattr | attribute name | Keep items where the attribute is truthy |
rejectattr | attribute name | Remove items where the attribute is truthy |
Chaining
Pipes chain left to right; each pipe receives the output of the one before it:
| Python | Protocol |
|---|
name.lower().truncate(20) | {{ name | lower | truncate:20 }} |
todos.rejectattr("done").length() | {{ todos | rejectattr:'done' | length }} |
revenue.currency().upper() | {{ revenue | currency | upper }} |
Default Values
A bare literal after | acts as a default when the left side is null or undefined. In Python, use .default():
| Python | Protocol | Behavior |
|---|
name.default("Anonymous") | {{ name | 'Anonymous' }} | "Anonymous" if name is undefined |
count.default(0) | {{ count | 0 }} | 0 if count is undefined |
This checks specifically for null/undefined: an empty string "" or 0 will not trigger the default. For broader falsy defaults (including empty strings, zero, and false), use || instead: {{ name || 'Anonymous' }}.
Unknown pipe names pass the value through unchanged; no error, just unformatted output.
Context & Variables
Local Scope (let)
The let prop on any container introduces scoped bindings visible to its children only:
The first Text renders “Don’t Panic, Arthur”. The second renders “Don’t Panic, Ford”: the inner let shadows name with a new value, but greeting is inherited unchanged.
Use set_initial_state() for mutable data the user can change. Use let for fixed data passed into a section: labels, configuration, computed values. let bindings are read-only.
Capturing Loop Variables
When nesting ForEach loops, both define $index. Capture the outer index with let before entering the inner loop:
from prefab_ui.components import Text
from prefab_ui.components.control_flow import ForEach
with ForEach("groups", let={"gi": "{{ $index }}"}):
with ForEach("groups.{{ gi }}.todos"):
Text("Group {{ gi }}, item {{ $index }}")
Special Variables
These are runtime values injected by the framework at specific points in the component tree. They don’t exist in global state; access them with {{ }} template strings.
$event
Available inside action handlers. Contains the value emitted by the interaction:
| Component | $event value |
|---|
| Input / Textarea | Current text (string) |
| Slider | Current position (number) |
| Checkbox / Switch | Checked state (boolean) |
| Select | Selected value (string) |
| RadioGroup | Selected value (string) |
| Button | undefined |
To capture $event explicitly, pass EVENT as the value: SetState("last_volume", EVENT). Form controls update their own state key automatically, so this is mainly useful when writing the event value to a different key.
$error
Available inside on_error callbacks. Contains the error message from the failed action:
CallTool(
"save_data",
on_error=ShowToast("Failed: {{ $error }}", variant="error"),
)
$index
Available inside ForEach. The zero-based index of the current item. Essential for actions targeting a specific row: SetState("todos.{{ $index }}.done").
$item
Available inside ForEach. The entire current item object. Individual fields are accessible directly as {{ name }}, but $item is useful for passing the whole object to an action:
with ForEach("users"):
Text("{{ name }}")
Button("Edit", on_click=CallTool("edit_user", arguments={"user": "{{ $item }}"}))
Dot Paths
Attribute access on Rx builds dot paths: Rx("user").address.city compiles to {{ user.address.city }}.
Integer segments address array items: {{ todos.0.done }}. .length works on arrays and strings.
If any segment is null or undefined, the expression resolves to undefined rather than throwing.
Undefined Values
For sole-value templates ("{{ missing }}"): returns the literal template string unchanged, which helps with debugging.
For mixed templates ("Hi {{ missing }}!"): undefined resolves to an empty string, producing "Hi !".
Use the default pipe for fallbacks: {{ missing | 'N/A' }}.
Boolean prop gotcha. disabled="{{ waiting }}" with undefined waiting evaluates to the string {{ waiting }}, which is truthy. Fix with: disabled="{{ waiting | false }}".
Grammar
The full BNF for the expression language inside {{ }} delimiters. The Rx DSL handles expression construction for you, but this is useful for writing protocol JSON directly or building tooling.
expr -> pipe
pipe -> ternary ( '|' ( ident ( ':' arg )? | literal ) )*
ternary -> or ( '?' expr ':' expr )?
or -> and ( '||' and )*
and -> not ( '&&' not )*
not -> '!' not | comp
comp -> add ( ( '==' | '!=' | '>' | '<' | '>=' | '<=' ) add )?
add -> mul ( ( '+' | '-' ) mul )*
mul -> unary ( ( '*' | '/' ) unary )*
unary -> ( '-' | '+' ) unary | primary
primary -> '(' expr ')' | number | string | 'true' | 'false' | 'null' | ident
ident -> name ( '.' name )*
Pipe has the lowest precedence, so price * quantity | currency parses as (price * quantity) | currency. The ternary operator is next-lowest, meaning a > 0 ? a : -a | abs parses as (a > 0 ? a : -a) | abs. Use parentheses to override.
Keywords and, or, and not are interchangeable with &&, ||, and !. Strings use single quotes inside expressions: {{ status == 'active' }}.