Plotly error bands: shaded confidence interval (tested)

Plotly has no error-band option: add the upper bound as an invisible line, then the lower bound with fill="tonexty" (it fills to the trace right before it), then the mean line. Or draw one closed polygon with fill="toself".

1. Upper line, lower line with fill="tonexty", then the mean

b1.py:

import numpy as np, pandas as pd, plotly.graph_objects as go
x = np.arange(5)
mean = np.array([2.0, 3.0, 2.5, 4.0, 3.5])
sd = np.array([0.4, 0.5, 0.3, 0.6, 0.5])

fig = go.Figure([
    go.Scatter(x=x, y=mean + sd, mode="lines", line=dict(width=0), showlegend=False),
    go.Scatter(x=x, y=mean - sd, mode="lines", line=dict(width=0), fill="tonexty",
               fillcolor="rgba(31,119,180,0.2)", name="±1 sd"),
    go.Scatter(x=x, y=mean, mode="lines", line=dict(color="rgb(31,119,180)"), name="mean"),
])
for t in fig.data:
    print(t.name, "| fill:", t.fill, "| y:", np.round(t.y, 2).tolist())
fig.write_html("band.html")

# Wrong order: fill="tonexty" on the FIRST trace fills down to y=0 instead
bad = go.Figure([go.Scatter(x=x, y=mean + sd, fill="tonexty"),
                 go.Scatter(x=x, y=mean - sd)])
print("bad first trace fill:", bad.data[0].fill, "(no trace before it)")

# Single trace alternative: closed polygon with fill="toself"
poly = go.Scatter(x=np.concatenate([x, x[::-1]]),
                  y=np.concatenate([mean + sd, (mean - sd)[::-1]]),
                  fill="toself", line=dict(width=0), name="band")
print("toself x:", poly.x.tolist())
print("toself y:", np.round(poly.y, 2).tolist())

Output (plotly 7.1.0, numpy 2.5.4, Python 3.13.5):

None | fill: None | y: [2.4, 3.5, 2.8, 4.6, 4.0]
±1 sd | fill: tonexty | y: [1.6, 2.5, 2.2, 3.4, 3.0]
mean | fill: None | y: [2.0, 3.0, 2.5, 4.0, 3.5]
bad first trace fill: tonexty (no trace before it)
toself x: [0, 1, 2, 3, 4, 4, 3, 2, 1, 0]
toself y: [2.4, 3.5, 2.8, 4.6, 4.0, 3.0, 3.4, 2.2, 2.5, 1.6]

2. Which spread to shade

Frequently asked questions

How do I add a shaded error band in plotly?

Add an invisible upper-bound trace, then a lower-bound trace with fill="tonexty" and an rgba fillcolor, then the mean line.

Why does my plotly band fill down to zero?

fill="tonexty" is on the first trace, or the traces are in the wrong order; with no trace before it, tonexty behaves like tozeroy.

Can I draw the band as a single trace?

Yes: x forward then reversed, upper values then lower values reversed, with fill="toself".

Written with AI assistance. Measured on 2026-10-23 with plotly 7.1.0, numpy 2.5.4 and Python 3.13.5 on Linux. Scripts ran verbatim and the output was pasted unedited. PlainTables is our extension; Plotly is free.