Sweets
Configuration

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

FamilyDefaultAnimates
window_openfade, 160 msA window appearing for the first time
window_closefade, 160 msA window's final image after it closes
window_minimizefade, 160 msMinimize and restore, in both directions
window_move220 msEvery geometry change: move, resize, or both
window_opacity160 msThe focused, unfocused, and dragged opacity step
border160 msThe border colour step
workspace_switch220 msSwitching numbered workspaces on a monitor
special_workspacefade, 220 msShowing and hiding the special workspace
layer_openfade, 160 msA layer surface appearing
layer_closefade, 160 msA 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:

FieldTypeDefaultValues
enabledbooleantrue
durationintegerper family0 to 5000 ms
curvename or arraydefaultA curve name or four numbers

window_open, window_close, window_minimize, and special_workspace also take:

FieldTypeDefaultValues
typestring"fade""fade", "zoom", "slide", "slide_fade"
scalenumber0.80.1 to 1.0 (zoom only)
directionstring"auto""auto", "left", "right", "up", "down" (slide only)
distancestring or integer"auto""auto", or 0 to 4096 pixels (slide only)

layer_open and layer_close take the same names, without zoom:

FieldTypeDefaultValues
typestring"fade""fade", "slide", "slide_fade"
directionstring"auto""auto", "left", "right", "up", "down"
distancestring 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:

typeTravelsFades
"fade"NoYes
"zoom"NoYes
"slide"YesNo
"slide_fade"YesYes
  • fade changes only alpha.
  • zoom scales the whole window from scale to its final size while fading.
  • slide moves it in from distance pixels away in direction, at its natural size and full opacity.
  • slide_fade does 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 alongEnters from
One edge, or that edge plus both sidesThat edge
A cornerThe edge on the layout's stack axis
All four edges, two opposite edges, or noneBelow

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 toEnters from
One edge (or that edge plus both sides)That edge
A cornerThe left or right edge of that corner
All four edges, or noneBelow

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.

directionBehavior
"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:

NameControl 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.

On this page