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:
| Option | Type | Default | What it does |
|---|---|---|---|
Rotate | number | — | Degrees 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. |
Scale | number | — | Multiplier on the icon's size. Clamped to 0-10. Hover only — a click is too short to read a size change. |
Icon | string | — | Swap to a different icon for the duration, then swap back. |
Text | string | — | Swap the button's caption for the duration. |
Hold | number | — | Seconds 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#
main:Button({
Text = "Reload data",
ButtonText = "Refresh",
ButtonIcon = "refresh-cw",
Click = { Rotate = 360 },
Callback = reload,
})Confirm — becomes a tick, then goes back#
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#
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#
main:Button({
Text = "Open settings",
ButtonText = "Open",
ButtonIcon = "settings",
Hover = { Scale = 1.12 },
Callback = openSettings,
})Nudge — a quarter turn that means "this expands"#
main:Button({
Text = "Show advanced",
ButtonText = "Expand",
ButtonIcon = "chevron-right",
Hover = { Rotate = 90 },
Callback = expand,
})Overdrive — two full turns#
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:
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:
| Option | Type | Default | What it does |
|---|---|---|---|
HoverColor | Color3 | — | The colour the border and glyph light up with. Defaults to the theme accent. |
HoverFill | number | — | How strongly the surface fills on hover, 0 to 1. |
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:
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:
Ember.Configure({ Effects = { MaxTiles = 200, Particles = 20 } })Or turn all animation off:
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:
| Name | TweenInfo | Used for |
|---|---|---|
fast | 0.14s Quad Out | Colour changes |
base | 0.22s Quint Out | Hover, most state changes |
open | 0.34s Quint Out | Menus and groups expanding |
shut | 0.24s Quart In | The same, closing |
local TweenService = game:GetService("TweenService")
local BASE = TweenInfo.new(0.22, Enum.EasingStyle.Quint, Enum.EasingDirection.Out)
TweenService:Create(myFrame, BASE, { BackgroundTransparency = 0 }):Play()