Effects & triggers
Effects#
An effect makes the symbol move: it bounces, pulses, lights up layer by layer, scales, appears, disappears or
swaps. Use one sparingly, to say that something changed or is still going on. An effect is an object in the
symbol's effect property:
{"kind": "pulse", "trigger": "repeating", "speed": 1.2}
| Property | Type | Default | Description |
|---|---|---|---|
kind | string | none | One of bounce, pulse, variableColor, scale, appear, disappear, replace. Required. |
trigger | string | onAppear | When the effect plays. One of onAppear, onChange, onHover, repeating. |
speed | number | 1 | How fast it plays, from 0.25 to 4. A value outside the range is clamped, and the validator warns. |
cumulative | boolean | false | variableColor only: layers stay lit instead of lighting one at a time. |
reversing | boolean | false | variableColor only: the effect plays back and forth. |
Triggers#
The trigger says what makes the effect play.
| Trigger | When it plays |
|---|---|
onAppear | Once, about 0.35 seconds after the banner appears. |
onChange | Each time something the symbol is bound to changes: its name after tokens are filled in, or a token in colors or variableValue. |
onHover | Each time the pointer enters the symbol. |
repeating | Continuously, for the effects that can run continuously: pulse, variableColor and scale. |
For onChange, bind something: a variableValue of {signal} or a name that is a token such as {statusSymbol}. With nothing bound, nothing ever changes.
Kinds#
Each kind reacts to the triggers in its own way.
| Kind | What it does | Triggers that work |
|---|---|---|
bounce | The symbol hops once. | onAppear, onChange, onHover. |
pulse | The layers fade in and out. | All four. With repeating it pulses for as long as the banner is up. |
variableColor | The layers light up in sequence. | All four. A single run lasts about 1.6 seconds divided by speed. repeating is continuous. |
scale | The symbol grows while the effect is active. | All four. Timing is the same as variableColor. |
appear | The symbol is hidden, then appears. | It plays once after the banner appears, whatever the trigger. |
disappear | The symbol fades away. | onAppear: after it appeared. onChange and onHover: when that happens. |
replace | The symbol swaps with a transition when its name changes. | onChange, with a name that contains a {token}. |
Choosing an effect:
- Use
pulse,variableColororscalefor a continuous effect. bounceis a single hop, andrepeatingdoes not play it.- A
replacewith anamethat has no token never changes, and the validator warns.
Examples by effect#
bounce hops when the count changes. The badge shows the bound value:
{"type": "badge", "binding": "{count}", "color": "#FF3B30",
"symbol": {"name": "envelope.fill", "scale": "small",
"effect": {"kind": "bounce", "trigger": "onChange"}}}
pulse runs continuously on the issuer icon, to show that something is live:
{"type": "issuerIcon", "size": 28,
"symbol": {"name": "dot.radiowaves.left.and.right", "renderingMode": "hierarchical",
"colors": ["accent"],
"effect": {"kind": "pulse", "trigger": "repeating", "speed": 0.8}}}
variableColor runs cumulative and reversing, driven by a field from the issuer:
{"type": "iconButton", "size": 22,
"symbol": {"name": "wifi", "renderingMode": "hierarchical", "variableValue": "{signal}",
"effect": {"kind": "variableColor", "trigger": "repeating",
"cumulative": true, "reversing": true}},
"action": {"id": "dismiss", "label": "Dismiss", "kind": "dismiss"}}
scale grows when the pointer enters:
{"type": "iconButton", "size": 22,
"symbol": {"name": "checkmark.circle.fill", "weight": "semibold",
"effect": {"kind": "scale", "trigger": "onHover"}},
"actionRef": "markRead"}
appear shows the symbol once the banner is up:
{"type": "issuerIcon", "size": 24,
"symbol": {"name": "checkmark.seal.fill", "effect": {"kind": "appear"}}}
disappear fades a glyph away when the pointer enters:
{"type": "iconButton", "size": 20,
"symbol": {"name": "xmark", "effect": {"kind": "disappear", "trigger": "onHover"}},
"action": {"id": "dismiss", "label": "Dismiss", "kind": "dismiss"}}
replace swaps the glyph when the issuer's status changes the symbol name. The issuer sends a field statusSymbol
with a value such as checkmark.circle or exclamationmark.triangle:
{"type": "iconButton", "size": 22,
"symbol": {"name": "{statusSymbol}", "effect": {"kind": "replace", "trigger": "onChange"}},
"action": {"id": "dismiss", "label": "Dismiss", "kind": "dismiss"}}
Common mistakes#
| Mistake | What happens | Fix |
|---|---|---|
| A misspelt name. | The default look is drawn, and the validator warns. | Copy the name from the SF Symbols app, or search with herald symbols. |
palette with no colours. | The tint and the tint at 55 percent are used, and the validator warns. | Give it two or three colours. |
multicolor with colors. | colors is ignored, and the validator warns. | Remove colors, or use palette. |
cumulative or reversing on bounce. | They are ignored, and the validator warns. | They apply only to variableColor. |
trigger: on bounce or disappear. | The effect never plays. | Use onAppear, onChange or onHover, or choose pulse for something continuous. |
replace with a name that has no token. | Nothing changes, and the validator warns. | Use a {token} in name and trigger:. |
Expecting an effect in render_preview. | Previews are static, so there is no motion. | Check in the Designer's live preview, or send a test banner. |
variableValue on a symbol without variable layers. | It is ignored. | Choose a symbol that supports it, such as wifi or speaker.wave.3. |
| Expecting effects on macOS 13, or with Reduce Motion. | They are ignored by design. | None. The banner is still readable. |
placement: "only" where nothing else says what the button does. | The label is dropped and only the tooltip keeps it. | Keep the label, or use a clear icon. |
placement: "only" on a badge. | The pill is drawn with neither the symbol nor the value. | Use leading or trailing on a badge. |