Animations and effects

Every built-in hover and click effect, the recipes worth stealing, and the knobs that control the dissolve.

You do not write tweens. You describe what should happen and the library plays it.

Hover and Click#

Any button — Button, IconButton, and each entry in an icon strip — takes a Hover and a Click table. Both understand the same five keys:

OptionTypeDefaultWhat it does
RotatenumberDegrees to turn the icon. Added to its resting angle, so 360 is a full spin and 90 is a quarter turn that stays turned while hovered.
ScalenumberMultiplier on the icon's size. Clamped to 0-10. Hover only — a click is too short to read a size change.
IconstringSwap to a different icon for the duration, then swap back.
TextstringSwap the button's caption for the duration.
HoldnumberSeconds to keep a Click's icon or text before reverting.

Hover reverts itself, Click needs Hold

A hover effect ends when the cursor leaves. A click has no natural end, so an Icon or Text swap on Click snaps straight back unless you give it Hold.

The recipes#

These are the combinations worth knowing. Each one is a complete, working control.

Refresh — the classic spin#

Luau
main:Button({
    Text = "Reload data",
    ButtonText = "Refresh",
    ButtonIcon = "refresh-cw",
    Click = { Rotate = 360 },
    Callback = reload,
})

Confirm — becomes a tick, then goes back#

Luau
main:Button({
    Text = "Save configuration",
    ButtonText = "Save",
    ButtonIcon = "save",
    Click = { Icon = "check", Text = "Saved", Hold = 1.4 },
    Callback = save,
})

The single most useful one: the button reports its own success, and you write no state to undo it.

Destructive — arm before firing#

Luau
main:Button({
    Text = "Delete configuration",
    ButtonText = "Delete",
    ButtonIcon = "trash-2",
    Danger = true,
    Hover = { Icon = "triangle-alert", Scale = 1.1 },
    Click = { Text = "Deleted", Hold = 1.6 },
    Callback = deleteConfig,
})

Hovering swaps the bin for a warning triangle and grows it — the control tells the user it is dangerous before they commit.

Lift — the quiet one#

Luau
main:Button({
    Text = "Open settings",
    ButtonText = "Open",
    ButtonIcon = "settings",
    Hover = { Scale = 1.12 },
    Callback = openSettings,
})

Nudge — a quarter turn that means "this expands"#

Luau
main:Button({
    Text = "Show advanced",
    ButtonText = "Expand",
    ButtonIcon = "chevron-right",
    Hover = { Rotate = 90 },
    Callback = expand,
})

Overdrive — two full turns#

Luau
main:Button({
    Text = "Rebuild everything",
    ButtonText = "Rebuild",
    ButtonIcon = "rotate-cw",
    Click = { Rotate = 720, Text = "Working…", Hold = 1.2 },
    Callback = rebuild,
})

Per-entry, on an icon strip#

Each button in a strip carries its own:

Luau
main:IconButton({
    Text = "Playback",
    Buttons = {
        { Icon = "play",       Callback = play,    Hover = { Scale = 1.15 } },
        { Icon = "pause",      Callback = pause,   Hover = { Scale = 1.15 } },
        { Icon = "refresh-cw", Callback = restart, Click = { Rotate = 360 } },
        { Icon = "trash-2",    Callback = clear,   Danger = true,
          Hover = { Icon = "triangle-alert" } },
    },
})

Spamming is safe#

Every effect is generation-guarded: clicking a spinning button again restarts the spin rather than letting the first click's completion snap the icon back mid-turn. You do not need a debounce for the animation's sake.

Hover colours#

Separate from the icon effects, any hoverable control takes:

OptionTypeDefaultWhat it does
HoverColorColor3The colour the border and glyph light up with. Defaults to the theme accent.
HoverFillnumberHow strongly the surface fills on hover, 0 to 1.
Luau
main:Button({
    Text = "Custom hover",
    ButtonText = "Run",
    HoverColor = Color3.fromRGB(120, 180, 255),
    HoverFill = 0.6,
    Callback = run,
})

The dissolve#

The window breaks into tiles when hidden and reassembles when shown. It is on by default and needs no setup, but every part of it can be tuned:

Luau
Ember.Configure({
    Effects = {
        MaxTiles  = 460,   -- how finely the window breaks up
        Samples   = 3,     -- colour samples per tile
        Particles = 52,    -- sparks trailing the dissolve edge
    },
})

Lower the budget for weaker machines:

Luau
Ember.Configure({ Effects = { MaxTiles = 200, Particles = 20 } })

Or turn all animation off:

Luau
Ember.Configure({ Effects = { Enabled = false } })

Those defaults are measured, not guessed

460 tiles keeps cells around 36px on a normal window; 3 samples is the point at which a control still reads as itself in the mosaic rather than a smear; 52 particles is what the leading edge needs to look like sparks rather than dots. Lower them for performance, but expect the effect to lose its character before it gets meaningfully faster.

The motion curves#

For your own tweens, matching the library's feel means matching its curves:

NameTweenInfoUsed for
fast0.14s Quad OutColour changes
base0.22s Quint OutHover, most state changes
open0.34s Quint OutMenus and groups expanding
shut0.24s Quart InThe same, closing
Luau
local TweenService = game:GetService("TweenService")
local BASE = TweenInfo.new(0.22, Enum.EasingStyle.Quint, Enum.EasingDirection.Out)
 
TweenService:Create(myFrame, BASE, { BackgroundTransparency = 0 }):Play()