Skip to content
led-ticker

Reference: Config options

This page is the cross-cutting reference for every TOML knob in led-ticker, grouped by config section. Per-widget options live on each widget’s page — this page covers the top-level blocks that wrap them: [display], [title], [transitions], [[playlist.section]], [[source]], [busy_light], and [web]. Field names and defaults match the dataclasses in src/led_ticker/config.py. For per-widget knobs see Widgets.

Some config slips that would crash startup are silently corrected with a warning. Warnings show up in led-ticker validate output AND in the runtime log — fix the source to silence them.

Numeric strings → numbers. Every integer or float field accepts a string-of-digits in addition to a bare number:

font_size = "25" # works (coerced to 25, warns)
font_size = 25 # canonical, no warning

Applies to all numeric fields across [display], [[playlist.section]], and per-widget configs — including rows, cols, chain_length, parallel, gpio_slowdown, pwm_bits, pwm_lsb_nanoseconds, pwm_dither_bits, rp1_pio, font_size, font_threshold, hold_time, brightness, content_height, scale, loop_count, scroll_step_ms, start_hold, separator_font_size, and others.

Closed-set enum strings → case-insensitive. Fields with a fixed value set (text_align, text_valign, image_align, scroll_direction, fit, bottom_text_scroll, easing) are case-insensitive. The canonical form is lowercase:

image_align = "Left" # works (coerced to "left", warns)
image_align = "left" # canonical, no warning

What’s NOT coerced. Colors ([r, g, b]), booleans, file paths, and free-text fields stay strict — typos here surface as hard errors rather than silent fixes. The style keys inside inline-table providers (font_color = {style="rainbow", ...}) also stay case-sensitive.

Hardware and panel-layout settings. Most of these map straight onto rgbmatrix library options.

FieldTypeDefaultNotes
rowsint16Panel row count (per panel, not total across the chain). 16 for 16x32 panels; 32 for P3 32x64 panels.
colsint32Panel column count (per panel). 32 for 16x32; 64 for P3 32x64.
chain_lengthint1Number of panels chained together. The smallsign reference uses 5; the bigsign uses 8.
parallelint1Number of parallel panel chains. Both reference builds use 1.
pixel_mapper_configstring""rgbmatrix Remap string for non-trivial layouts (e.g. the bigsign’s 2x4 vertical-serpentine mapping). Empty string = no remap.
default_scaleint1Logical-to-physical scaling. 1 matches the smallsign reference (no scaling). 4 matches the bigsign (ScaledCanvas blows 16-tall content up to 64).
brightnessint100Panel brightness, 0-100. Both reference configs use 60.
gpio_slowdownint1GPIO timing slowdown. Smallsign uses 2; bigsign uses 3 (paired with the default RIO backend). Raise on flicker.
hardware_mappingstring"adafruit-hat"rgbmatrix GPIO pinout. "adafruit-hat" covers the Adafruit HAT and bonnet.
pwm_bitsint11PWM bit depth. Drop to 8 on the bigsign for ~8x faster refresh at a small color cost.
pwm_lsb_nanosecondsint130PWM LSB timing; raise toward 200+ if colors look wrong.
pwm_dither_bitsint0Temporal dithering depth (0=off, 1–2). Spreads PWM energy more evenly across rows; try 1 or 2 if a specific row appears brighter than its neighbors at sub-100% brightness.
show_refresh_rateboolfalseLog measured refresh Hz to stderr — useful when tuning the bigsign.
disable_hardware_pulsingboolfalseDisable hardware PWM and use a CPU fallback. Roughly 5x slower; almost always leave at false.
hot_reloadbooltrueWatch config.toml and live-reload sections/widgets/transitions/schedule/brightness without restarting ([display], [busy_light], [plugins], [web] changes still need a restart). Set false to disable.
rp1_pioint0Pi 5 only. 0 = RIO backend (library default — faster refresh, more CPU). 1 = force the PIO backend (lower CPU, slower refresh). Ignored on Pi 4.
limit_refresh_rate_hzint0Cap the hardware refresh rate in Hz. 0 = unlimited. Useful on long chains where uncapped RIO-mode refresh causes visible motion artifacts — try 100 as a starting point.
multiplexingint0Panel scan-row wiring scheme. 0 = direct. Try 15 if the bottom half of the panel shows inverted or garbled content. See the rgbmatrix docs for the full list.
row_address_typeint0Row address type. 0 = direct (default). Change only if the panel uses a non-standard address decoder (AB-addressed, ABC-shifted, etc.).
panel_typestring""Driver IC init sequence. Set to "FM6126A" or "FM6127" for panels that use those chips (common on cheap P2/P3 AliExpress panels). Without it, FM6126A panels power up in a bad state and show the bottom half mirrored or garbled.
led_rgb_sequencestring"RGB"RGB channel order. Change to "RBG", "GRB", etc. if colors appear wrong — e.g. purple/blue tint (missing green) often means green and blue are swapped; try "RBG".
backendstring"rgbmatrix"Rendering backend. "rgbmatrix" drives real hardware panels. "headless" runs with no panel (useful for development/testing without a Pi). Plugins may register additional backends.
scheduletable(disabled)Optional brightness schedule. Nested [display.schedule] block — see below.

These are the common knobs. The full set of rgbmatrix library options is broader — see CLAUDE.md for the additional Pi-5 tuning notes and what each setting interacts with.

Optional brightness schedule. Enabled by setting [display.schedule] with enabled = true. See Display scheduling for the full guide.

FieldTypeDefaultNotes
enabledboolfalseMaster switch. When false the schedule is not loaded and brightness is never adjusted by the scheduler.
timezonestring""IANA timezone name (e.g. "America/New_York"). Empty string = system local time. Set this explicitly — fresh Pis are often UTC.
windowsarray[]List of [[display.schedule.windows]] tables. Each defines a time range, brightness, and optional day-of-week filter. See below.

Each window is a [[display.schedule.windows]] table entry:

FieldTypeDefaultNotes
startstring(required)Window start time in "HH:MM" 24-hour local wall-clock format.
endstring(required)Window end time. When end < start (e.g. start = "23:00", end = "07:00") the window wraps past midnight.
brightnessint(required)Panel brightness to apply, 0–100. 0 makes the panel appear dark (≈ zero power draw from the LEDs); the Pi keeps running.
daysstring array[]Days this window is active: ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]. Empty list means every day. For midnight-wrapping windows (start > end), the window is anchored to its start day — see Day filtering.

Title-card timing.

FieldTypeDefaultNotes
delayint5Seconds the section title (e.g. “#DevOps News”) holds before the section’s first widget runs.

Global defaults for inter-widget and inter-section transitions.

FieldTypeDefaultNotes
defaultstring"cut"Fallback inter-widget transition. Overridden per-section by transition or widget_transition.
durationfloat0.5Default transition duration in seconds. Sprite-trail transitions (pokeball, nyancat, scroll) typically want a longer value.
easingstring"linear"Default easing curve. Accepted values: linear, ease_out, ease_in_out.
transition_fpsfloatnullAnimation frames per second for all transitions. null uses the built-in default (20 fps). Raise to 30–40 on large panels to reduce choppiness. Warn if < 5 or > 120.
between_sectionsstring / table"cut"Fallback transition fired at section boundaries. Lowest priority in the entry-transition chain — overridden by a section’s entry_transition or explicitly-set transition (see Tips below).
separator_colorlist / stringnullColor of the scroll transition’s separator mark. Same shapes as a widget font_color ([r,g,b], "rainbow", "color_cycle", {style=…}). Only honored by type = "scroll"; rejected on other transition types. null defaults to white.
separatorstringnullThe scroll transition’s separator glyph. null = the default dot; "" renders nothing (gap only). Only honored by type = "scroll".
separator_fontstringnullFont (BDF alias or hires name) for the scroll separator glyph. null uses the default 6×12 BDF font.
separator_font_sizeintnullPixel size for a hires separator_font (ignored for BDF). Required when separator_font is a hires font.
separator_sizeintnullSize (logical px) of the scroll transition’s separator dot. null = the default 2. No effect on a glyph separator. Only honored by type = "scroll".

Both default and between_sections accept either a bare string (transition name) or an inline table — e.g. between_sections = { type = "wipe_left", duration = 0.8, easing = "ease_out" }.

Each [[playlist.section]] is one item in the playlist. Sections run in order, then the playlist loops.

FieldTypeDefaultNotes
modestring"ticker"One of ticker, one_at_a_time, slideshow. See Sections and modes.
loop_countint1How many times to play this section’s widgets before moving on. 0 repeats forever within the section.
hold_timefloat3.0Seconds to hold each widget in slideshow mode. Ignored by ticker / one_at_a_time.
continuous_scrollboolfalseSkip the held pause for overflowing text in scroll modes.
transitionstring / tableinherits global defaultInter-widget transition for slideshow mode. When explicitly set, ALSO becomes the entry transition for this section (see Tips). Accepts a string ("wipe_left") or an inline table — see “Inline-table transition form” below.
entry_transitionstring / tablenullOverride the transition used when this section enters (the inter-section boundary). Takes precedence over both transition and the global between_sections. Useful when you want a dramatic entry but a quiet inter-widget swap, or vice versa. Same string / inline-table forms as transition.
widget_transitionstring / tablenullOverride the transition used for inter-widget swaps within this section. Takes precedence over transition. Does not affect the section’s entry. Same string / inline-table forms as transition.
transition_durationfloatinherits globalPer-section override for the transition duration. Does not affect entry_transition or widget_transition (set those durations inside their inline-table form).
transition_fpsfloatnullPer-section animation fps override. Equivalent to setting transition_fps inside the transition = {...} dict. Inherits from [transitions].transition_fps if not set.
transition_color[r, g, b]nullCustom color for color-aware transitions (e.g. color_flash, wipe_*).
scaleintdisplay.default_scalePer-section logical-to-physical scaling override. Useful for letterboxed countdowns at scale = 2 on the bigsign.
content_heightint16Logical canvas height in rows. Hard ceiling: content_height × scale ≤ panel_h_real (see Tips).
bg_color[r, g, b]nullSection-level background color. Widgets inherit this when they don’t set their own bg_color.
scroll_step_msintnull (= 50 ms)Milliseconds per logical pixel of scroll. Lower = faster marquee. Applies to ticker, one_at_a_time, and the post-hold scroll on slideshow mode.
separatorstringnull (= white )Per-section override for the bullet between ticker widgets. Empty string "" renders as two spaces (no glyph, minimum gap). Non-empty strings render as-is — include surrounding spaces if you want padding (e.g. " * "). On bigsign (default_scale > 1), the default separator renders as a smooth hi-res circle painted at physical resolution; set explicitly (e.g. separator = "•") to opt out and use BDF rendering instead. Rejected on non-ticker modes.
transition_colors[[r,g,b], ...]nullMulti-color list for transitions that cycle through colors (e.g., wipe_random with multiple sweep colors). Plural form of transition_color.
separator_fontstringnullFont name (BDF alias or hires file) for the separator glyph. null uses the default 6×12 BDF. Useful for matching the section’s widget font.
separator_font_sizeintnullRequired when separator_font is a hires font. Ignored for BDF (fixed cell height).
separator_sizeintnullSize (logical px) of the ticker-mode separator circle (radius = size ÷ 2). null = the default 8. Only affects the bigsign hi-res circle; the smallsign BDF is unaffected. Only honored by mode = "ticker".
separator_color[r, g, b] / string / tablenull (= white)Color for the separator. When separator is unset on bigsign, this colors the hi-res circle. When separator is set explicitly, it colors the BDF glyph. Accepts the same shapes as widget font_color: constant [r, g, b], "rainbow", "color_cycle", or {style = "gradient", from = [...], to = [...]}.
start_holdfloatnull (inherits [title] delay)Pre-roll delay (seconds) before this section’s first widget begins scrolling. Only honored on ticker / one_at_a_time. Setting it on slideshow is a validation error. start_hold = 0.0 makes the section start scrolling immediately, leaving the global [title] delay unchanged for other sections.
transition_specifiedboolfalseInternal flag set by the loader when the user wrote transition = ... in this section’s TOML. You don’t write this — it’s in the dataclass for completeness.

When the transition needs more than just the type, prefer the inline-table form: transition = { type = "wipe_left", duration = 0.8, easing = "ease_out", color = [255, 0, 0] }. The flat sibling form (transition = "wipe_left" + transition_duration = 0.8) is equivalent and remains fully supported.

transition_specified lets the engine tell user-written transition = X apart from the inherited default — the distinction drives entry-transition selection (see Tips).

A title block accepts the same field surface as a message widget with one alias: color is the title-specific spelling of font_color. Both work, and only on title blocks — writing color = "random" on a regular [[playlist.section.widget]] is a configuration error (the alias doesn’t apply outside titles). The only other difference from a regular widget is that it’s held for [title] delay seconds before the section’s first widget runs.

See the message widget page for the full field list.

Per-widget options live on each widget’s page. See Widgets.

Each [[source]] block declares a named value source that widget text = fields can reference with the :id: token syntax. Sources are global — they are not nested inside any section. You can declare as many [[source]] blocks as you need. See Value tokens for the full guide.

FieldTypeRequiredNotes
idstringyesToken name. Must match [a-z_][a-z0-9_.]* to be usable as a :token: — an id that doesn’t match the pattern is accepted by the validator but won’t be recognized at display time (the :...: renders as literal text). Must not equal an existing emoji slug; duplicate ids are rejected.
typestringyesSource class. Built-in: "clock", "date", "static". Plugins may register additional types via api.source.
formatstringnostrftime format string. Optional for clock and date (default "%H:%M"); ignored by static.
timezonestringnoIANA timezone name (e.g. "America/New_York"). Applies to clock and date. Omit to follow the Pi’s system clock (often UTC on a fresh Pi).
valuestringsee noteFixed string. Required for static; ignored by clock and date.

All sources refresh once per second. The version counter increments only when the computed value actually changes, so constant-width clock formats (%H:%M) never cause a layout reflow.

A persistent corner dot painted as an overlay over every section and transition while a local file exists. Disabled by default. See Busy light for the full guide, including the Docker file_path gotcha.

FieldTypeDefaultNotes
enabledboolfalseMaster switch. When false, nothing is built and there is zero per-frame cost.
file_pathstring"~/.busy"Light is on while this file exists; ~ expands. Under Docker, use the bind-mounted path /code/config/.busy and toggle config/.busy on the host.
poll_intervalfloat5.0Seconds between file checks.
cornerstring"top_right"One of top_left, top_right, bottom_left, bottom_right. Other values rejected at config load.
color[r, g, b][255, 0, 0]Three ints, each 0255. Length/range rejected at config load.
sizeint4Dot side in physical pixels (>= 1). ~4 on the 16-tall smallsign, ~8–10 on the 64-tall bigsign/longboi.
sourcestring"file""file" (poll a local file) or "http" (remote push from a LAN HTTP call).
http_hoststring"0.0.0.0"Listen address when source = "http". 0.0.0.0 = all interfaces.
http_portint8081Listen port when source = "http". Range 1–65535. Defaults to 8081 so it doesn’t collide with [web]’s 8080.
tokenstring""Shared secret for the HTTP source. Empty = open (trusted LAN only). Prefer LED_TICKER_BUSY_TOKEN in .env; this field is a fallback used only when that env var is unset, and logs a warning when so used.
ttl_secondsfloat0.0HTTP source: default auto-clear seconds for a state=on that omits ?ttl=. Per-request ?ttl=N overrides it. 0 = stay on until explicit off.

The optional read-only web status dashboard — a browser UI that shows what the sign is currently displaying, exposes the live config with secrets redacted, and lets you paste a candidate config through the validator. Presence of the [web] block (even empty) enables status publishing in the display process and allows the led-ticker webui sidecar to serve it. Absence disables the feature entirely — existing configs with no [web] block are unaffected. See Web status UI for setup and usage.

FieldTypeDefaultNotes
http_hoststring"0.0.0.0"Interface the sidecar binds. "0.0.0.0" listens on all interfaces (all LAN addresses); "127.0.0.1" limits to localhost only.
http_portint8080HTTP port the sidecar serves. The compose service uses host networking, so the Pi’s port is exposed directly.
tokenstring""Optional shared secret. Empty = open (trusted-LAN posture). Non-empty requires an X-Web-Token header or ?token= query parameter on every request. Prefer LED_TICKER_WEB_TOKEN in .env; this field is a fallback used only when that env var is unset, and logs a warning when so used.
status_pathstring"/run/led-ticker/status.json"Path where the display process writes its status snapshot; the sidecar reads the same file. Must match between both processes.
allow_restartboolfalseExpose a “Restart display” button in the web UI. When true, clicking the button drops a marker file that the display process detects on its next loop pass and calls sys.exit(0); Docker’s restart: unless-stopped brings it back. Set to false (default) to disable the control entirely.

Transition precedence: entry vs. inter-widget

Section titled “Transition precedence: entry vs. inter-widget”

Two independent transition slots fire per section: one when the section enters (inter-section boundary) and one for swaps between widgets within the section. Each slot resolves through its own precedence chain:

Entry transition (when this section appears): entry_transitiontransition (if explicitly set on this section) → global between_sections

Inter-widget transition (swaps within this section): widget_transitiontransition (if explicitly set on this section) → global default

The key implication: writing transition = "pokeball.forward" on a section sets both slots to that transition — it fires when the section enters AND between every widget inside it. This is usually what you want for single-widget sections. For multi-widget sections where you want a dramatic entry but a quiet per-widget swap, use entry_transition + widget_transition independently instead of transition.

If you want the global between_sections to fire on entry, leave transition unset (or use entry_transition explicitly).

The wrapper centers a logical canvas of height content_height × scale on the real panel. On the bigsign at scale = 4 and a 64-tall panel, that means content_height ≤ 16. Push above and the wrapper’s vertical offset goes negative — the logical canvas is taller than the panel — and the top + bottom rows of your content silently clip. BDF text is forgiving because cells sit near the vertical center, but hi-res emoji and large hi-res fonts surface the clip immediately. For per-row breathing room use text_y_offset on the widget rather than over-sizing content_height. See Hardware: Bigsign for the full discussion.