Animation reference
Tetra's animation primitives change a widget's position, scale, or rotation
from frame to frame. Load animation.star as animation and render.star as
render before building an animated widget tree.
The transformation API may change as Tetra's animation system evolves. Test animations after upgrading Tetra.
Easing curves
An easing curve controls the rate of change between two keyframes. Use one of the built-in names:
"linear""ease_in""ease_out""ease_in_out"
You can also provide a CSS-style cubic Bézier string such as
"cubic-bezier(0.42, 0, 0.58, 1)", or a function that accepts and returns a
value between 0.0 and 1.0.
load("animation.star", "animation")
load("render.star", "render")
def smooth_step(t):
return t * t * (3 - 2 * t)
def main():
movement = animation.Transformation(
child = render.Circle(diameter = 6, color = "#22d3ee"),
duration = 48,
width = 64,
height = 32,
direction = "alternate",
keyframes = [
animation.Keyframe(
percentage = 0.0,
transforms = [animation.Translate(2, 13)],
),
animation.Keyframe(
percentage = 1.0,
transforms = [animation.Translate(56, 13)],
curve = smooth_step,
),
],
)
return render.Root(
delay = 50,
child = movement,
)

Keyframe
Keyframe defines the transforms at a specific point in an animation. Its
percentage is a floating-point value from 0.0 at the start to 1.0 at the
end.
| Parameter | Type | Required | Purpose |
|---|---|---|---|
percentage | float | Yes | Position within the animation from 0.0 to 1.0. |
transforms | [Transform] | Yes | Transforms to interpolate to or from. |
curve | str or function | No | Easing curve; defaults to "linear". |
load("animation.star", "animation")
load("render.star", "render")
def main():
movement = animation.Transformation(
child = render.Box(width = 8, height = 8, color = "#a855f7"),
duration = 48,
width = 64,
height = 32,
direction = "alternate",
keyframes = [
animation.Keyframe(
percentage = 0.0,
transforms = [animation.Translate(3, 12)],
),
animation.Keyframe(
percentage = 0.5,
transforms = [
animation.Translate(28, 12),
animation.Scale(1.5, 1.5),
],
curve = "ease_in_out",
),
animation.Keyframe(
percentage = 1.0,
transforms = [animation.Translate(53, 12)],
),
],
)
return render.Root(
delay = 50,
child = movement,
)

When the keyframe list omits 0.0 or 1.0, Tetra inserts a default keyframe
with no transforms and linear easing at the missing endpoint.
Origin
Origin is the relative anchor used by scale and rotation transforms. The
default Origin(0.5, 0.5) places the anchor at the center of the child.
| Parameter | Type | Required | Purpose |
|---|---|---|---|
x | float | Yes | Horizontal anchor position. |
y | float | Yes | Vertical anchor position. |
This example rotates an L shape around its top-left corner:
load("animation.star", "animation")
load("render.star", "render")
def main():
spinner = animation.Transformation(
child = render.Stack(
children = [
render.Box(width = 14, height = 3, color = "#fbbf24"),
render.Box(width = 3, height = 14, color = "#fbbf24"),
],
),
duration = 60,
width = 64,
height = 32,
origin = animation.Origin(0.0, 0.0),
keyframes = [
animation.Keyframe(
percentage = 0.0,
transforms = [
animation.Translate(32, 16),
animation.Rotate(0),
],
),
animation.Keyframe(
percentage = 1.0,
transforms = [
animation.Translate(32, 16),
animation.Rotate(360),
],
curve = "ease_in_out",
),
],
)
return render.Root(
delay = 50,
child = spinner,
)

Translate
Translate moves the child by a horizontal and vertical pixel offset.
| Parameter | Type | Required | Purpose |
|---|---|---|---|
x | float or int | Yes | Horizontal offset. |
y | float or int | Yes | Vertical offset. |
load("animation.star", "animation")
load("render.star", "render")
def main():
movement = animation.Transformation(
child = render.Box(width = 6, height = 6, color = "#34d399"),
duration = 48,
width = 64,
height = 32,
direction = "alternate",
keyframes = [
animation.Keyframe(
percentage = 0.0,
transforms = [animation.Translate(2, 13)],
),
animation.Keyframe(
percentage = 1.0,
transforms = [animation.Translate(56, 13)],
curve = "ease_in_out",
),
],
)
return render.Root(
delay = 50,
child = movement,
)

Scale
Scale resizes the child relative to the transformation origin. A value of
1 preserves the original size.
| Parameter | Type | Required | Purpose |
|---|---|---|---|
x | float or int | Yes | Horizontal scale factor. |
y | float or int | Yes | Vertical scale factor. |
load("animation.star", "animation")
load("render.star", "render")
def main():
pulse = animation.Transformation(
child = render.Circle(diameter = 10, color = "#f472b6"),
duration = 40,
width = 64,
height = 32,
origin = animation.Origin(0.5, 0.5),
direction = "alternate",
keyframes = [
animation.Keyframe(
percentage = 0.0,
transforms = [
animation.Translate(27, 11),
animation.Scale(0.5, 0.5),
],
),
animation.Keyframe(
percentage = 1.0,
transforms = [
animation.Translate(27, 11),
animation.Scale(2, 2),
],
curve = "ease_in_out",
),
],
)
return render.Root(
delay = 50,
child = pulse,
)

Rotate
Rotate turns the child clockwise around the transformation origin.
| Parameter | Type | Required | Purpose |
|---|---|---|---|
angle | float or int | Yes | Rotation in degrees. |
load("animation.star", "animation")
load("render.star", "render")
def main():
spinner = animation.Transformation(
child = render.Box(width = 16, height = 4, color = "#fb923c"),
duration = 60,
width = 64,
height = 32,
origin = animation.Origin(0.5, 0.5),
keyframes = [
animation.Keyframe(
percentage = 0.0,
transforms = [
animation.Translate(24, 14),
animation.Rotate(0),
],
),
animation.Keyframe(
percentage = 1.0,
transforms = [
animation.Translate(24, 14),
animation.Rotate(360),
],
curve = "ease_in_out",
),
],
)
return render.Root(
delay = 50,
child = spinner,
)

Transformation
Transformation animates a child by interpolating between keyframes. Each
keyframe can combine translation, scale, and rotation. Transform order matters:
Tetra applies them in the order they appear in transforms.
Durations and delays are measured in frames, not milliseconds.
| Parameter | Type | Required | Purpose |
|---|---|---|---|
child | Widget | Yes | Widget to animate. |
keyframes | [Keyframe] | Yes | Keyframes to interpolate between. |
duration | int | Yes | Animation duration in frames. |
delay | int | No | Frames to wait before starting. |
width | int | No | Animation canvas width. |
height | int | No | Animation canvas height. |
origin | Origin | No | Scale and rotation anchor; defaults to the center. |
direction | str | No | Playback direction; defaults to "normal". |
fill_mode | str | No | Transform retained after playback; defaults to "forwards". |
rounding | str | No | Translation-coordinate rounding; defaults to "round". |
wait_for_child | bool | No | Wait for every animated child frame before restarting. |
Direction
direction accepts:
| Value | Behavior |
|---|---|
"normal" | Play forward. |
"reverse" | Play backward. |
"alternate" | Play forward, then backward. |
"alternate-reverse" | Play backward, then forward. |
Fill mode
fill_mode controls the transform applied after the animation finishes:
| Value | Behavior |
|---|---|
"forwards" | Retain the final keyframe. |
"backwards" | Return to the first keyframe. |
Translation rounding
Interpolated positions often fall between physical pixels. Set rounding to
"round", "floor", "ceil", or "none". This setting affects only
translation; scale and rotation keep their interpolated values.
Example
This animation moves a circle across the display while rotating it. The
alternate direction brings it back to the starting point:
load("animation.star", "animation")
load("render.star", "render")
def main():
movement = animation.Transformation(
child = render.Circle(diameter = 6, color = "#22d3ee"),
duration = 60,
width = 64,
height = 32,
origin = animation.Origin(0.5, 0.5),
direction = "alternate",
fill_mode = "forwards",
keyframes = [
animation.Keyframe(
percentage = 0.0,
transforms = [
animation.Translate(2, 13),
animation.Rotate(0),
],
curve = "ease_in_out",
),
animation.Keyframe(
percentage = 1.0,
transforms = [
animation.Translate(56, 13),
animation.Rotate(360),
],
),
],
)
return render.Root(
delay = 50,
child = movement,
)
