Animations
Window, layer, and workspace transitions
Animations are off by default. Turn on the master switch, then tune each family.
sweets.animations({
enabled = true,
defaults = { curve = "standard" },
window_open = { type = "zoom", scale = 0.9, duration = 160 },
window_move = { duration = 220 },
window_close = { type = "fade", duration = 160 },
workspace_switch = { duration = 220, direction = "auto" },
layer_open = { type = "slide", duration = 160 },
})Families
| Family | Default | Animates |
|---|---|---|
window_open | fade, 160 ms | A window appearing for the first time |
window_close | fade, 160 ms | A window's final image after it closes |
window_minimize | fade, 160 ms | Minimize and restore, in both directions |
window_move | 220 ms | Every geometry change: move, resize, or both |
window_opacity | 160 ms | The focused, unfocused, and dragged opacity step |
border | 160 ms | The border colour step |
workspace_switch | 220 ms | Switching numbered workspaces on a monitor |
special_workspace | fade, 220 ms | Showing and hiding the special workspace |
layer_open | fade, 160 ms | A layer surface appearing |
layer_close | fade, 160 ms | A layer surface leaving |
A family's own enabled only matters when the top-level enabled is true.
A duration of 0 also makes that family immediate.
Fields
Every family takes these:
| Field | Type | Default | Values |
|---|---|---|---|
enabled | boolean | true | — |
duration | integer | per family | 0 to 5000 ms |
curve | name or array | default | A curve name or four numbers |
window_open, window_close, window_minimize, and special_workspace also
take:
| Field | Type | Default | Values |
|---|---|---|---|
type | string | "fade" | "fade", "zoom", "slide", "slide_fade" |
scale | number | 0.8 | 0.1 to 1.0 (zoom only) |
direction | string | "auto" | "auto", "left", "right", "up", "down" (slide only) |
distance | string or integer | "auto" | "auto", or 0 to 4096 pixels (slide only) |
layer_open and layer_close take the same names, without zoom:
| Field | Type | Default | Values |
|---|---|---|---|
type | string | "fade" | "fade", "slide", "slide_fade" |
direction | string | "auto" | "auto", "left", "right", "up", "down" |
distance | string or integer | "auto" | "auto", or 0 to 4096 pixels |
window_move, border, and window_opacity take only the three shared
fields — nothing about them travels or scales.
Window open, close, and minimize
type chooses the effect:
type | Travels | Fades |
|---|---|---|
"fade" | No | Yes |
"zoom" | No | Yes |
"slide" | Yes | No |
"slide_fade" | Yes | Yes |
fadechanges only alpha.zoomscales the whole window fromscaleto its final size while fading.slidemoves it in fromdistancepixels away indirection, at its natural size and full opacity.slide_fadedoes both.
A slide does not fade, because the window arrives whole from beyond the
screen edge rather than appearing out of nothing. Use slide_fade for both.
Content, border, corners, and backdrop move as one object. scale and the
slide fields stay validated even when type = "fade", so switching type later
keeps your tuning.
Distance
distance = "auto" is the default. It measures how far this window must travel
to sit just off the screen edge it slides toward, from its own position and
size. A window near the bottom of the screen sliding down travels a short way;
one near the top travels the height of the screen. Duration does not change
with distance, so a longer slide is faster rather than slower.
Write a number instead for a shorter nudge. With "slide" and a distance too
small to clear the screen, the window is still visible when the animation ends
and disappears in one frame — use "slide_fade", or leave distance at
"auto".
Direction
direction = "auto" is the default. Each window enters from the screen edge its
own slot lies along, so a layout slides in as a shape rather than as one window
repeated:
| Slot lies along | Enters from |
|---|---|
| One edge, or that edge plus both sides | That edge |
| A corner | The edge on the layout's stack axis |
| All four edges, two opposite edges, or none | Below |
A slot on a corner lies along two edges, so the layout decides. Tile, centered, deck and columns use the axis their stack grows along: a master on the left or right stacks downward, so those windows enter from the top or bottom edge; a master on the top or bottom stacks sideways, so they enter from the left or right. Grid and monocle use the left or right edge.
Dwindle decides per window, because it splits the screen a different way at each step. Each window enters along the split that placed it, so directions alternate as you open more: with the master on the left, the second window enters from the right, the third from the bottom, the fourth from the right again.
In tile with the master on the left and two windows, the master enters from the
left and the stack window from the right — neither is on a corner. Add a third
and it joins the stack below the second, which puts it on a corner, so it enters
from the bottom and follows the column it was added to. Rotate the layout with
master_position and the same
windows enter from the top and then the side.
A window on its own lies along every edge, derives nothing, and enters from below. So does a centered column, which lies along two opposite edges.
Write a direction instead to send every window the same way.
window_open runs on the first map only — remapping a window does not replay
it. window_close runs only after the client actually closes, so a close the
application refuses does not animate.
These apply to normal Wayland and X11 windows. Popups, input methods, and X11
override-redirect windows are excluded; layer surfaces use layer_open.
window_minimize runs in both directions and does not aim at a taskbar. A
restore that interrupts a minimize reverses from the frame on screen.
Window move and resize
window_move animates every geometry change: moving, resizing, or both.
Content, popups, border, corners, and backdrop travel together. One layout
change usually moves some windows and resizes others — they share one timeline
and settle together, so there is no separate resize duration.
It also paces the slide from
drag_center_cursor.
Moving a window again while it travels retargets it from where it is, including onto a third monitor. Pressing a movement key when the window is already at the edge of its layout does nothing and leaves the running animation alone.
A window sent or dragged to another monitor is drawn on both screens as it crosses, and resizes into its new slot on the way. Every other animation stays on its own monitor.
Maximize uses this family too. Fullscreen is always immediate, in both directions: applications rearrange what they draw when they go fullscreen, so animating the size would show new content growing out of the old window. Sweets still waits for the new frame, so there is no flash.
Layer surfaces
layer_open and layer_close animate launchers, notifications, and on-screen
displays. There is no zoom and no scale — a layer surface is drawn from its
live content, which the compositor can move and blend but not resize.
type behaves as it does for windows: "slide" travels at full opacity,
"slide_fade" travels and fades, and "fade" only fades.
Direction
"auto" works as it does for windows, but a layer surface has an anchor to
derive from rather than a slot:
| Anchored to | Enters from |
|---|---|
| One edge (or that edge plus both sides) | That edge |
| A corner | The left or right edge of that corner |
| All four edges, or none | Below |
A corner-anchored surface always uses the left or right edge. A layer surface is not part of a layout, so no stack axis decides it, and a notification stack growing from a corner keeps entering from one edge as it fills.
A close uses the same edge, so a surface slides back out the way it came in.
Override it for one surface with a
layer rule rather than fixing direction
globally:
sweets.layer_rule({ namespace = "sweetwall", animation_direction = "left" })Distance
distance = "auto" measures how far this surface must travel to sit just off
screen, and is the default. The right number differs per surface: a corner
notification clears the edge in its own width, while a centered launcher has to
cross half the monitor. One fixed number cannot serve both.
Set an integer to make a surface move less than "auto" would — a notification
that nudges instead of flying in. Duration does not change with distance, so a
longer trip is a faster one.
A surface dismissed before it finished arriving leaves from where it got to.
With "slide" and a distance too small to clear the screen, the surface is
still visible when the animation ends and disappears in one frame; slide_fade
always ends at zero alpha.
What animates
Only surfaces that reserve no space, on the top and overlay layers. A bar
with an exclusive zone appears immediately, because the windows it displaces
resize at that same moment. Wallpapers and other background or bottom
surfaces never animate.
Input and layout always use the settled position, so a launcher accepts keys and clicks at its final place from the first frame.
Turn it off for one surface:
sweets.layer_rule({ namespace = "swaync", animation = false })Popups and backdrop blur travel with their surface.
Workspace switching
workspace_switch animates explicit switches between numbered workspaces,
clipped to the monitor they happen on.
direction | Behavior |
|---|---|
"auto" | Horizontal, ordered by workspace number |
"horizontal" | Always horizontal |
"vertical" | Always vertical |
The monitor that animates is the one whose workspace changed, which is not always the one you are on. A switch that changes two monitors animates both. Selecting a workspace a monitor already shows only moves focus there, and leaves a slide running elsewhere alone.
Special workspace
special_workspace covers both showing and hiding the overlay, using the same
type, scale, direction, and distance as the window families. Hiding
reverses the effect, and a rapid toggle starts from the frame on screen.
The dimmer fades on the same curve, so an empty special workspace still has a
visible transition. A window mapped while it is already open uses
window_open; moving an existing window in does not replay the overlay.
Border colour
animations.border crossfades the border between the colours in
sweets.border.
sweets.animations({
enabled = true,
border = { enabled = true, duration = 160 },
})It covers every reason the colour changes: focus moving, urgency being set or cleared, and focus passing to the window that survives a close.
Window opacity
animations.window_opacity crossfades between the opacities a window resolves:
its focused and unfocused values, and the
dragged value while the pointer
is moving it. It carries
dim_inactive on the
same timeline, because both are the same focus change. Set them apart first, or
there is no step to animate:
sweets.window({
opacity = { focused = 1.0, unfocused = 0.92, dragged = 0.7 },
dim_inactive = 0.15,
})
sweets.animations({
enabled = true,
window_opacity = { enabled = true, duration = 160 },
})Both this and border show their value immediately when a window opens rather
than fading in, and both continue from the value on screen if focus moves away
and back mid-fade. Reloading new colours or opacities applies them at once — a
reload is not a focus change.
A dragged window starts returning to its normal opacity the moment you release
it, over this duration, while it is still moving into place. This and
window_move are independent: opacity always uses this duration, even while a
window is moving or resizing.
Shared defaults
defaults sets duration and curve for every family at once. A family that
names either field wins.
sweets.animations({
enabled = true,
defaults = { duration = 200, curve = "standard" },
window_open = { duration = 90 },
})It accepts only those two fields; type, scale, direction, and distance
stay per family. It is not a master switch — use animations.enabled or a
family's own enabled to turn things off.
Curves
curve takes a name or four numbers in CSS cubic Bézier order.
sweets.curve("quick", { 0.46, 1.0, 0.29, 0.99 })
sweets.animations({
enabled = true,
window_move = { duration = 180, curve = "quick" },
window_open = { duration = 160, curve = { 0.46, 1.0, 0.29, 0.99 } },
})Declare a name before the sweets.animations call that uses it. These names
always exist and cannot be redefined:
| Name | Control points |
|---|---|
default | { 0.33, 1.0, 0.68, 1.0 } |
linear | { 0.0, 0.0, 1.0, 1.0 } |
ease | { 0.25, 0.1, 0.25, 1.0 } |
ease_in | { 0.42, 0.0, 1.0, 1.0 } |
ease_out | { 0.0, 0.0, 0.58, 1.0 } |
ease_in_out | { 0.42, 0.0, 0.58, 1.0 } |
A name is 1 to 64 characters of letters, digits, _, or -, and one
configuration may declare 32 of them.
In the array form, entries 1 and 3 are between 0.0 and 1.0, and entries 2
and 4 between -4.0 and 4.0. Exactly four finite numbers are required.
Interruptions
A compatible reversal continues from the position on screen instead of
restarting. Opening several windows in quick succession does not cancel the
earlier animations: a window whose slot does not change finishes normally, and
one whose slot does change carries on into window_move without a jump.
Pointer move and resize are always immediate and pointer-matched. The windows making room for a drag still animate.
An operation that cannot animate — a window leaving for another monitor, one no longer shown, or a client that is too slow — takes its place at once without stopping the others.
Reload
A valid reload applies to later transitions. An animation already running keeps the settings it started with. Turning off the master switch or one family settles it immediately.