Loading indicators for Plotly Dash, wrapping twelve React spinner libraries as one install. 461 components under stable namespaces, with a common factory over the top so you can switch families without relearning each one's props.
Browse them all in the live gallery: https://dash-loading-components.phylatech.com
pip install dash-loading-componentsimport dash_loading_components as dlc
dlc.loading_dev.Dual(size=48, color="#f97316", rate=1.5)
dlc.premium.OrbitRings(size=48, color="#f97316", ring_count=4, playing=False)
dlc.ldrs.Ring(size=48, color="#f97316", stroke=5)Every component takes the same four props with the same meaning:
| prop | meaning |
|---|---|
size |
pixels (react-loading-indicators takes its own "small" / "medium" / "large" tokens) |
color |
any CSS color |
rate |
relative tempo: 1.0 is this spinner's own tempo, 2.0 twice as fast. Unset keeps the upstream tempo |
playing |
False pauses the animation |
Each library's own props are there too, in snake_case, in the library's own
units: dlc.loading_dev.Arc(duration=1600, easing="stacked"),
dlc.spinners.ClipLoader(speed_multiplier=2). A native tempo or pause prop
passed alongside rate or playing wins.
Need a loading overlay? Use Dash's own:
dcc.Loading(children, custom_spinner=dlc.ldrs.Ring(size=48))Discover what is available at runtime:
dlc.list_libraries() # ['loading_dev', 'ldrs', 'spinners', ..., 'gridora', 'loadersz']
dlc.list_spinners("ldrs") # ['Ring', 'Helix', 'DotPulse', ...]
dlc.list_spinners("loadersz") # ['working', 'searching', 'connecting', ...]React ≥19 and Dash ≥4.5, both hard requirements. The loading_dev family
needs React 19, and this package will not claim Dash 4.4 / React 18
compatibility.
Dash 4.5 is not on PyPI as a stable release yet, so the dependency is pinned as
dash>=4.5.0rc0. That specifier names the pre-release, which is what lets pip
resolve it; it will accept the final 4.5 release once published.
| Namespace | Upstream | Components |
|---|---|---|
loading_dev |
loading-dev | 29 |
premium |
premium-react-loaders | 15 |
ldrs |
ldrs | 12 |
spinners |
react-spinners | 12 |
indicators |
react-loading-indicators | 11 |
loader_spinner |
react-loader-spinner | 10 |
spinners_react |
spinners-react | 9 |
epic |
react-epic-spinners | 8 |
m3 |
@alerix/m3-loading-indicator | 1 |
flicker |
flicker-dot | 61 |
gridora |
gridora | 133 |
loadersz |
loadersz | 160 |
flicker-dot is a player: it animates whatever 7x7 frames it is given. dlc.flicker
ships 61 original presets for it, drawn by PhylaTech and MIT licensed with this
package, in six sets: Field (the naturalist set: Mycelium, Diatom,
Cladogram, Chromatogram, MassSpec, TestTube, Jellyfish, ...), Data
(Scatter, Histogram, LineChart, PieChart, Heatmap, Database),
Everyday (Cart, Upload, Download, Mail, Wifi, Heart, Lock, ...),
Board (flip-dot panel classics), Geometry, and Solids (the five Platonic
solids). Each is the player with its frames filled in:
dlc.flicker.Mycelium(size=48, color="#f97316")
dlc.flicker.Helix(size=48, variant="5x5", off_color="#e5e5e5", off_opacity=1)To play frames of your own, give dlc.flicker.Spinner a list of frames. A frame
is 7 strings of 7 characters, # lit and . dark (or upstream's 49 booleans,
or 7 lists of 7):
blink = ["...#...", "..###..", ".#####.", "#######", ".#####.", "..###..", "...#..."]
dlc.flicker.Spinner(grids=[blink, [row.replace("#", ".") for row in blink]])dlc.flicker.PRESETS holds every preset's frames, a starting point for your own.
rotate (0, 90, 180 or 270, clockwise) and mirror orient any preset, so
dlc.flicker.Cladogram(rotate=90) grows left to right. variant picks the grid: "7x7", its inner "5x5" (bigger dots) or "9x9",
the 7x7 padded with a ring of unlit dots (smaller dots at the same size).
Lit dots are color at on_opacity (default 1). Unlit dots are off_color
(default: color) at off_opacity (default 0.16)
rather than upstream's fixed light grey, so one color reads on light and dark
pages; off_opacity=0 shows only the lit dots.
gridora ships 133 motion variants of grid-dot animations plus a bitmap-font text
component. dlc.gridora.GridLoader renders any motion variant; dlc.gridora.Text
renders text using gridora's built-in glyph font.
dlc.gridora.GridLoader(variant="orbit", color="#f97316", rate=1.5)
dlc.gridora.Text(text="LOAD", color="#f97316", effect="bounce")size is overall px, not cell size. Upstream size is an alias for cellSize
(the individual cell), which would make a 3×3 grid 12 px wide. When you set the
common size, dlc.gridora derives the cell size from the grid geometry. Pass
cell_size explicitly if you need direct control. The upstream speed prop is
cycle duration in seconds (higher = slower), not a multiplier; rate translates:
speed = 1 / rate.
loadersz ships 160 canvas loaders as a Web Component, from product states
(working, searching) to chart-shaped ones (heatmapping, sankeying,
treemapping). dlc.loadersz.Loader renders any of them; the state is a prop,
so there is no dlc.loadersz.working.
dlc.loadersz.Loader(state="heatmapping", size=96, color="#f97316")
dlc.loadersz.Loader(state="working", hue=200, density=1.5)color paints every particle itself and wins over hue and theme. Leave it
off to get loadersz's own palette (grey, or a loader's own tones), tinted by
hue (0 to 360) and drawn for a light or dark page by theme ("auto"
follows the operating system, not the page).
Every component is also exported prefixed at the top level
(dlc.LoadingDevArc, dlc.LdrsRing) for Dash callbacks that want a flat name.
Version pins, SPDX licenses, upstream URLs and React peers: docs/UPSTREAM-INVENTORY.md. Attribution: NOTICE.
rate is a rate, not a duration. 1.0 reproduces the tempo the upstream
component runs at on its own, and the value is translated per family in the
wrappers: a duration in milliseconds for loading-dev, epic and premium,
seconds per loop for ldrs, a multiplier for react-spinners, m3, flicker and
loadersz, a percentage for spinners-react, an offset for
react-loading-indicators, and seconds per cycle (inverted) for gridora.
That baseline is per spinner, not per family. loading_dev.Compass is normally
500ms and loading_dev.Slide is 2400ms, so rate=1.0 gives each of them its
own tempo and rate=2.0 halves both. react-loader-spinner has no tempo prop,
so rate does nothing there.
dash-loading-spinners is
a focused package built largely around react-spinners-era indicators. dlc is
not a rename or a silent re-export of it. The goals differ:
- A multi-upstream umbrella with stable namespaces,
dlc.<family>.<Component> - An explicit React 19 / Dash ≥4.5 floor, driven by loading-dev
- A license inventory and NOTICE covering every wrapped family
- Room to add families without flattening name collisions
Where the APIs overlap, credit upstream; this is honest coexistence, not a drop-in replacement.
Pull requests only, against main. Family wrappers land as focused follow-up
PRs, one family or a small batch per PR. This repo squash-merges, so the PR
title becomes the commit subject on main. Releases are cut by
release-please from that
subject, so the PR title must carry a
Conventional Commits type (feat:,
fix:, etc.). See CONTRIBUTING.md for the full rules.
Local setup:
pixi install # Python, Node, Dash, the gallery's deps, test tooling
npm install
pixi run build # JS bundle + generated Python wrappers
pixi run python usage.py # minimal demo app
pixi run gallery # the full gallery, http://127.0.0.1:8050/
pixi run testThe deployed gallery runs under gunicorn (gallery:server); pixi run serve
does the same locally.
pixi run build-js alone is enough when you have only changed React or CSS
under src/.
MIT, Copyright (c) 2026 Phyla Technologies. See LICENSE. Wrapped third-party libraries remain under their own licenses, listed in the inventory.