Animations
led-ticker has one animation right now: typewriter — a character-by-character reveal. Set animation = "typewriter" on a supported widget and the text types out one character at a time the moment the widget appears. The characters land in their final positions (no shifting), so the message assembles itself in place rather than scrolling in.
Animations are a separate axis from color providers. A widget can carry both simultaneously — for example animation = "typewriter" and font_color = "rainbow" — and each effect tracks its own frame counter. The characters type out in rainbow with no interference between the two systems.
Animations may also emit a per-frame rotation (degrees, clockwise) that the message widget renders through an offscreen rotate around the text block’s center — the flair plugin’s propeller effect uses this seam. On scaled displays (bigsign), the spin runs at half physical resolution from a pre-rendered snapshot that captures hires fonts and emoji at their correct size; the resting frame always renders at full detail. Animated font_color providers (rainbow, color_cycle, shimmer) pause for the duration of the spin and resume at settle — the color phase keeps advancing in the background, so on settle the sweep continues exactly where it left off.

Minimal
[[playlist.section.widget]]type = "message"text = "Hello, world!"animation = "typewriter"Tuning frames_per_char
Section titled “Tuning frames_per_char”The cadence is frames_per_char × 50 ms per character — the engine ticks at 50 ms and advances the typewriter counter once per tick. The default of 3 gives about 150 ms per character, which feels snappy but still readable at roughly 7 characters per second. A value of 5 or 6 reads as deliberate and is a good choice when you want the audience to register each character dropping in. Values above 10 read as comically slow. There is no separate animation clock — everything runs on the same 50 ms engine tick.
To override frames_per_char, switch from the string shorthand to an inline TOML table with a style key:
[[playlist.section.widget]]type = "message"text = "Slow type"animation = {style = "typewriter", frames_per_char = 6} # ~300ms per charKeeping the reveal inside hold_time
Section titled “Keeping the reveal inside hold_time”Typing takes frames_per_char × 50 ms per character — so a 20-character message at the default frames_per_char = 3 takes roughly 3 seconds to finish. If hold_time is shorter than the typing duration, the transition fires mid-reveal and the message never fully appears. led-ticker validate catches this (rule 61) and prints the computed typing duration alongside the effective hold time, so you can raise hold_time to cover the full reveal — or shorten the text.
When a reveal is nearly done at transition time, the engine waits up to roughly one second for the final characters to land before switching scenes, so a message that’s one or two characters short doesn’t cut away abruptly.
Composing with font_color
Section titled “Composing with font_color”animation and font_color are independent axes on the same widget. Both can be set at the same time, and each effect tracks its own counter. The result is that characters type out AND each character’s color comes from the provider — a rainbow sweeps across the text as it appears, rather than the whole message flashing in at once.
[[playlist.section.widget]]type = "message"text = "Now Enrolling"animation = "typewriter"font_color = "rainbow"Because rainbow is a continuous-phase provider (its counter does not reset between loop_count repetitions), the color sweep keeps flowing even as the typewriter retypes from scratch on each loop.
Restart-on-visit behavior
Section titled “Restart-on-visit behavior”Typewriter has restart_on_visit = true — the reveal counter resets every time the section shows the widget. With loop_count = 2, the widget retypes from scratch on each loop rather than resuming mid-word. With loop_count = 1 and a multi-widget section in slideshow mode, the widget retypes fresh each time it appears in the rotation. This is the intentional, expected behavior — typewriter is designed to be a reveal, not a persistent animation state. See frame counters for the full model of restart-on-visit versus continuous-phase behavior.
Where it works
Section titled “Where it works”| Widget type | animation = "typewriter"? | animation = "flair.propeller"? | animation = "flair.fisheye"? |
|---|---|---|---|
message | yes | yes | yes |
countdown | no | no | no |
gif | yes — single-row only (see note below) | accepted but rotation is ignored 1 | accepted but the lens is ignored 2 |
image | yes — single-row only (see note below) | accepted but rotation is ignored 1 | accepted but the lens is ignored 2 |
two_row | no | no | no |
weather.current, rss.feed | no | no | no |
On gif and image widgets, typewriter is restricted to single-row text overlays. It will raise a configuration error if bottom_text is also set (which activates two-row mode), if text_align is "scroll" or "scroll_over" (incompatible with a fixed-position reveal), or if text is empty.
animation is only valid on message, gif, and image
Section titled “animation is only valid on message, gif, and image”Other widget types (two_row, the crypto and feeds widgets) have their own draw paths and ignore animation silently — the validator rejects it explicitly to make the no-op visible. For dynamic color on those widgets, use font_color = "rainbow" or a gradient.
Typewriter is single-row only on gif and image
Section titled “Typewriter is single-row only on gif and image”On top of the widget-type check above, typewriter on gif / image rejects three combinations: bottom_text set, text_align = "scroll" / "scroll_over", or empty text. Typewriter still composes with font_color = "rainbow" and border = "rainbow".
flair.propeller (flair pack)
Section titled “flair.propeller (flair pack)”flair.propeller is a plugin animation from the flair pack (led-ticker-flair >= 0.2.0, core >= 4.3). The message widget’s text spins in-plane like a propeller the moment the widget appears — easing out over the configured number of revolutions and settling exactly flat — then holds readable for the rest of hold_time. Transitions wait for the spin to finish before firing.

Install
Section titled “Install”Add one line to config/requirements-plugins.txt:
led-ticker-flairSee Plugins for the install flow.
Minimal config
Section titled “Minimal config”Minimal
[[playlist.section.widget]]type = "message"text = "PROPELLER"center = truefont_color = [255, 200, 0]animation = "flair.propeller"Tuning the spin
Section titled “Tuning the spin”Switch to an inline table to control speed and direction:
[[playlist.section.widget]]type = "message"text = "PROPELLER"center = truefont_color = [255, 200, 0]animation = {style = "flair.propeller", revolutions = 3, spin_seconds = 1.5, direction = "ccw"}| Knob | Default | Notes |
|---|---|---|
revolutions | 2 | Integer >= 1. Number of full 360° turns before settling. |
spin_seconds | 1.0 | Seconds for the full spin (ease-out). Shorter feels snappy; 2–3 s reads as deliberate. |
direction | "cw" | "cw" (clockwise) or "ccw" (counter-clockwise) — viewed from the front of the panel. |
How the spin interacts with hold_time
Section titled “How the spin interacts with hold_time”led-ticker validate rule 62 warns when spin_seconds is longer than the section’s hold_time — a spin that outlasts the hold cuts away before the text settles, so the message never reads cleanly. Raise hold_time to cover spin_seconds plus at least a moment of readable hold. The engine waits up to about a second for the spin to settle before firing a transition; a spin that outlasts the hold by more than that cuts away mid-spin (the rule 62 warning covers this).
Composing with font_color and border
Section titled “Composing with font_color and border”flair.propeller composes with font_color providers and border effects using the same independent-axes model as typewriter. Animated color providers (rainbow, color_cycle, shimmer) freeze for the duration of the spin and resume at settle — the color phase keeps advancing in the background, so the sweep continues exactly where it left off the moment the text lands flat. This means a propeller + rainbow combo reads as: spin in (colors frozen), land flat, rainbow resumes sweeping.
On scaled signs (bigsign)
Section titled “On scaled signs (bigsign)”On signs with default_scale > 1, the mid-spin frames render from a pre-rendered snapshot at half physical resolution — this captures hires fonts and emoji at their correct size but at reduced detail. The resting (settled) frame always renders at full detail. led-ticker-flair >= 0.2.0 and core >= 4.5 are required for physical-resolution hires spin frames; on core 4.3–4.4 the spin runs but hires fonts draw unrotated (validate rule 63 warns when this condition is detected).
Pairing with flair.spinout
Section titled “Pairing with flair.spinout”flair.propeller and flair.spinout are designed to pair: the widget spins in at entry and spins out at exit. See the full-cycle demo below for the combined config.

Full cycle (propeller in + spinout)
[transitions]default = "flair.spinout"duration = 1.0
[[playlist.section]]mode = "slideshow"hold_time = 3.5
[[playlist.section.widget]]type = "message"text = "FULL CYCLE"center = truefont_color = [255, 200, 0]animation = "flair.propeller"
[[playlist.section]]mode = "slideshow"hold_time = 2.5
[[playlist.section.widget]]type = "message"text = "REPEAT"center = truefont_color = [255, 100, 255]flair.fisheye (flair pack)
Section titled “flair.fisheye (flair pack)”flair.fisheye is a plugin animation from the flair pack (led-ticker-flair >= 0.4.0, core >= 4.7). A message widget’s scrolling text passes through a stationary “fisheye lens” centered on the panel: letters enter compressed at the edges, swell as they cross the middle, and compress again on exit. The lens is fixed — the scroll provides all the motion, so the effect runs for the whole scroll and a section can cut away at any moment.

Install
Section titled “Install”Add one line to config/requirements-plugins.txt:
led-ticker-flairSee Plugins for the install flow.
Minimal config
Section titled “Minimal config”Minimal
[[playlist.section.widget]]type = "message"text = "OPEN LATE TONIGHT"animation = "flair.fisheye"Tuning the lens
Section titled “Tuning the lens”Stronger lens
[[playlist.section.widget]]type = "message"text = "FLASH SALE TODAY"animation = {style = "flair.fisheye", magnify = 1.33, edge_squeeze = 0.45}
| Knob | Default | Meaning |
|---|---|---|
magnify | 1.3 | Center scale (both axes). There is a ceiling — magnify × font line-height ≤ content_height — above which the bulged text would run off the panel; a widget over the ceiling is skipped and the rest of the sign keeps running. For the default 6×12 font in a 16-row band the ceiling is about 1.33. |
edge_squeeze | 0.6 | Edge scale (0 < edge_squeeze ≤ magnify). Lower means more edge compression — a more dramatic lens. |
profile | "cosine" | The edge-to-center falloff curve. "cosine" is the only value today. |
What the lens does to the scroll
Section titled “What the lens does to the scroll”The lens is width-preserving: a message takes exactly as long to scroll across as it would without the lens. But because the edges are compressed, they show more text than a plain scroll would — roughly two extra characters per side at the default squeeze, more with a stronger one. That is the lens working as intended, not a glitch.
Live colors through the lens
Section titled “Live colors through the lens”Unlike flair.propeller (whose colors freeze during the spin), flair.fisheye keeps font_color live: a rainbow or color_cycle provider keeps sweeping as the text scrolls through the lens.

Static center bulge
Section titled “Static center bulge”Held (non-scrolling) text gets a fixed emphasis: the center of the message swells while the ends compress. Center a short message with center = true and skip the scroll — good for a shop sign or a headline.

Static bulge
[[playlist.section.widget]]type = "message"text = "GRAND OPENING TODAY"center = trueanimation = "flair.fisheye"Inline emoji through the lens
Section titled “Inline emoji through the lens”:slug: emoji ride the lens with the text — an emoji swells as it crosses the center and compresses at the edges, just like the letters around it.

With inline emoji
[[playlist.section.widget]]type = "message"text = "NOW OPEN :star: WELCOME IN :star:"animation = "flair.fisheye"Also works with borders and hi-res fonts
Section titled “Also works with borders and hi-res fonts”- Borders: the border frames the panel un-warped while the text warps inside it.
- Hi-res fonts: on a scaled sign (bigsign) a hi-res font warps too, subject to the same
magnify × font line-height ≤ content_heightceiling — a too-tall hi-res font is skipped (and the rest of the sign keeps running). On a smallsign (scale 1) a hi-res font renders normally (unwarped) andled-ticker validateemits a warning: the pixel-precise warp needs a scaled display.
On scaled signs (bigsign)
Section titled “On scaled signs (bigsign)”The lens re-renders the scrolling text every frame at reduced resolution, so the whole scroll shows at half detail on the bigsign — the distortion masks it, and it matches the reduced detail you already see mid-spin with flair.propeller.
Pairing with flair.spinout
Section titled “Pairing with flair.spinout”A fisheye message scrolls in through the lens; flair.spinout — the flair pack’s text-spin transition, documented on the flair transitions page — can spin that message out as the section changes. Set it as the transition (here as the global default) and the outgoing fisheye text spins away before the next section appears:
Fisheye scroll with a spinout exit
[transitions]default = "flair.spinout"duration = 1.0
[[playlist.section]]mode = "slideshow"hold_time = 3.0
[[playlist.section.widget]]type = "message"text = "GRAND OPENING TODAY"animation = "flair.fisheye"
[[playlist.section]]mode = "slideshow"hold_time = 2.5
[[playlist.section.widget]]type = "message"text = "SEE YOU SOON"center = trueFootnotes
Section titled “Footnotes”-
flair.propelleris accepted ongifandimageat config-load so a section can carry both a spinningmessageand an image widget without a validation error, but the rotation has no visible effect — onlymessagewidgets render the spin. ↩ ↩2 -
flair.fisheyeis accepted ongifandimageat config-load so a section can carry both a lens-scrollingmessageand an image widget without a validation error, but the lens has no visible effect — onlymessagewidgets warp. ↩ ↩2