The Component Workshop: Reflex's 100 Library Components
·
Course overview
Layout
·
Box
·
Flex
·
Stack
·
Grid
·
Container
·
Section
·
Center
·
Spacer
·
Separator
·
Card
·
Inset
·
Aspect Ratio
·
Fragment
Typography
·
Text
·
Heading
·
Link
·
Code
·
Markdown
·
Blockquote
·
Quote
·
Strong
·
Em
·
Kbd
Forms
·
Button
·
Input
·
Text Area
·
Checkbox
·
Switch
·
Radio Group
·
Select
·
Slider
·
Form
·
Upload
Data display
·
Badge
·
Avatar
·
Icon
·
Callout
·
List
·
Data List
·
Code Block
·
Progress
·
Spinner
·
Scroll Area
·
Moment
Disclosure
·
Tabs
·
Accordion
·
Segmented Control
Overlay
·
Tooltip
·
Popover
·
Hover Card
·
Dialog
·
Alert Dialog
·
Drawer
·
Dropdown Menu
·
Context Menu
·
Toast
Dynamic rendering
·
Cond
·
Match
·
Foreach
·
Auto Scroll
Media
·
Image
·
Audio
·
Video
Tables and data grids
·
Table
·
Data Table
·
Data Editor
Charts
·
Area Chart
·
Bar Chart
·
Line Chart
·
Composed Chart
·
Pie Chart
·
Radar Chart
·
Radial Bar Chart
·
Scatter Chart
·
Funnel Chart
·
Treemap
·
Sankey Chart
·
Error Bar
Chart parts
·
Axis
·
Cartesian Grid
·
Chart Tooltip
·
Legend
·
Label
·
Reference
·
Brush
Other chart libraries
·
Plotly
·
Pyplot
HTML elements
·
HTML
·
HTML Text
·
HTML Layout
·
HTML Forms
·
HTML Media
·
HTML Tables
·
SVG
Other tools
·
Theme
·
Skeleton
·
Clipboard
·
Html Embed
·
Script
·
Memo
Workshop order
Please show where a month's income comes from and how much of it flows to rent, food and savings, using the thickness of the flows.
rx.recharts.sankey_chart is a Sankey diagram: it shows nodes and the amounts flowing between them as bands whose thickness matches the amount. Use it to look at "flows", such as which pages visitors pass through on the way to a purchase, or where money comes from and where it goes.
data is not a list but a dict shaped like {"nodes": [...], "links": [...]}. A link's source and target are positions (from 0) in the nodes list, and value is the size of that flow. The tooltip goes in as a child.
sankeychart_data = {
"nodes": [
{"name": "Website"},
{"name": "Landing"},
{"name": "Product"},
{"name": "Checkout"},
{"name": "Purchase"},
],
"links": [
{"source": 0, "target": 1, "value": 1200},
{"source": 1, "target": 2, "value": 900},
{"source": 2, "target": 3, "value": 420},
{"source": 3, "target": 4, "value": 260},
],
}
rx.recharts.sankey_chart(
rx.recharts.graphing_tooltip(),
data=sankeychart_data,
node_padding=24,
node_width=12,
link_curvature=0.55,
width="100%",
height=300,
)A dict for node or link applies the same SVG styles to every node or band. Write the dict keys exactly as SVG attribute names (in camelCase, like strokeOpacity). link_curvature is how much the bands bend (0 is straight).
Try the props
node_color
link_opacity
0.35
link_curvature
0.5
sort
rx.recharts.sankey_chart(
rx.recharts.graphing_tooltip(),
data={
"nodes": [{"name": "Ads"}, {"name": "Search"}, {"name": "Signup"}, {"name": "Leave"}, {"name": "Paid"}],
"links": [
{"source": 0, "target": 2, "value": 300},
{"source": 0, "target": 3, "value": 500},
{"source": 1, "target": 2, "value": 600},
{"source": 1, "target": 3, "value": 200},
{"source": 2, "target": 4, "value": 350},
],
},
node={"fill": rx.color("indigo", 9), "stroke": "none"},
link={"stroke": rx.color("gray", 9), "strokeOpacity": 0.35},
link_curvature=0.5,
sort=True,
node_padding=20,
node_width=12,
width="100%",
height=300,
)| Prop | Values | Default | Use |
|---|---|---|---|
data | {"nodes": [...], "links": [...]} | none | Nodes and flows. Links connect nodes by position |
name_key | string | "name" | Key of the node name |
data_key | string | "value" | Key of the flow amount |
node | dict or a @sankey_chart.node function | Node style, or a function that draws nodes yourself | |
link | dict or a @sankey_chart.link function | Band style, or a function that draws bands yourself | |
node_width | number | 10 | Thickness of the node bars |
node_padding | number | 10 | Gap between nodes in the same column |
link_curvature | 0–1 | 0.5 | How much the bands bend |
sort | True / False | True | Whether to sort nodes vertically |
iterations | number | 32 | How many times the layout calculation repeats |
margin, width, height | dict / number or percentage | 5 each / "100%" | Margin and size. Widen margin if names on the right get cut off |
| Events | on_click on_mouse_enter on_mouse_leave on_mouse_move | Mouse events on the chart area |
data is connected to a dict in State. Press the button and the links get new values, so the band thickness and node heights are recalculated. Here node and link get dicts for color, border and opacity.
import random
from typing import Any
class SankeyChartLiveState(rx.State):
data: dict[str, Any] = {
"nodes": [
{"name": "Marketing"},
{"name": "Trial"},
{"name": "Sales"},
{"name": "Support"},
{"name": "Retained"},
],
"links": [
{"source": 0, "target": 1, "value": 600},
{"source": 1, "target": 2, "value": 320},
{"source": 2, "target": 4, "value": 210},
{"source": 1, "target": 3, "value": 180},
{"source": 3, "target": 4, "value": 130},
],
}
@rx.event
def shuffle(self):
self.data = {
"nodes": self.data["nodes"],
"links": [{**link, "value": random.randint(80, 700)} for link in self.data["links"]],
}
rx.vstack(
rx.recharts.sankey_chart(
rx.recharts.graphing_tooltip(),
data=SankeyChartLiveState.data,
node={
"fill": rx.color("accent", 7),
"stroke": rx.color("accent", 10),
"strokeWidth": 2,
},
link={
"stroke": rx.color("gray", 7),
"strokeOpacity": 0.35,
},
node_padding=18,
node_width=14,
width="100%",
height=300,
),
rx.button("Randomize flows", on_click=SankeyChartLiveState.shuffle),
width="100%",
)When a dict isn't enough, write functions that draw the nodes and bands. A function decorated with @rx.recharts.sankey_chart.node receives one node's information as a Var (SankeyNodeProps: position x and y, size width and height, and the original data payload) and returns an SVG component. @rx.recharts.sankey_chart.link receives one band's information (SankeyLinkProps: start and end coordinates, thickness linkWidth, and payload).
rx.recharts.use_chart_width() is the chart's actual width. It is used to put the names of the rightmost nodes on their left."fill") are read by converting to a dict, as in payload.to(dict)["fill"].rx.vars.use_id() creates a unique gradient id for each band. Each band blends from the source node's color to the target node's color.Define the two functions once at module level before using them in the UI.
sankeychart_styled = {
"nodes": [
{"name": "Sources", "fill": rx.color("blue", 8)},
{"name": "Direct", "fill": rx.color("green", 8)},
{"name": "Search", "fill": rx.color("grass", 8)},
{"name": "Paid", "fill": rx.color("amber", 8)},
{"name": "Revenue", "fill": rx.color("purple", 8)},
],
"links": [
{"source": 0, "target": 1, "value": 350},
{"source": 0, "target": 2, "value": 500},
{"source": 0, "target": 3, "value": 220},
{"source": 1, "target": 4, "value": 190},
{"source": 2, "target": 4, "value": 260},
{"source": 3, "target": 4, "value": 150},
],
}
@rx.recharts.sankey_chart.node
def sankeychart_node(node: rx.Var[rx.recharts.SankeyNodeProps]) -> rx.Component:
# put the name on the left for the rightmost nodes
is_out = node.x + node.width + 6 > rx.recharts.use_chart_width()
return rx.fragment(
rx.el.svg.text(
node.payload.name,
x=rx.cond(is_out, node.x - 6, node.x + node.width + 6).to(int),
y=(node.y + node.height / 2).to(int),
text_anchor=rx.cond(is_out, "end", "start"),
fill=rx.color("gray", 12),
font_size=10,
),
rx.el.svg.rect(
x=node.x.to(int),
y=node.y.to(int),
width=node.width.to(int),
height=node.height.to(int),
# read the payload's custom key (fill) after converting it to a dict
fill=node.payload.to(dict)["fill"],
stroke=rx.color("gray", 12),
stroke_width=1,
),
)
@rx.recharts.sankey_chart.link
def sankeychart_link(link: rx.Var[rx.recharts.SankeyLinkProps]) -> rx.Component:
link_id = rx.vars.use_id()
source = link.payload.source.to(dict)
target = link.payload.target.to(dict)
return rx.fragment(
rx.el.svg.linear_gradient(
rx.el.svg.stop(offset="0%", stop_color=source["fill"]),
rx.el.svg.stop(offset="100%", stop_color=target["fill"]),
id=link_id,
),
rx.el.svg.path(
d=(
f"M{link.sourceX},{link.sourceY} "
f"C{link.sourceControlX},{link.sourceY} "
f"{link.targetControlX},{link.targetY} "
f"{link.targetX},{link.targetY}"
),
fill="none",
stroke=f"url(#{link_id})",
stroke_opacity=0.35,
stroke_width=link.linkWidth,
),
rx.el.svg.text(
link.payload.value,
x=((link.sourceX + link.targetX) / 2).to(int),
y=((link.sourceY + link.targetY) / 2).to(int),
text_anchor="middle",
fill=rx.color("gray", 12),
font_size=10,
),
)
rx.recharts.sankey_chart(
data=sankeychart_styled,
node=sankeychart_node,
link=sankeychart_link,
width="100%",
height=320,
)Two income sources merge into a "Budget" node and then split three ways. Nodes are painted in a strong color and bands in a lighter shade of the same color, so the flows seem to pour out of the nodes, and a wider right margin keeps the names in the last column from being cut off.
rx.recharts.sankey_chart(
rx.recharts.graphing_tooltip(),
data={
"nodes": [
{"name": "Salary"},
{"name": "Side job"},
{"name": "Budget"},
{"name": "Rent"},
{"name": "Food"},
{"name": "Savings"},
],
"links": [
{"source": 0, "target": 2, "value": 3000},
{"source": 1, "target": 2, "value": 600},
{"source": 2, "target": 3, "value": 1400},
{"source": 2, "target": 4, "value": 900},
{"source": 2, "target": 5, "value": 1300},
],
},
node={"fill": rx.color("accent", 9), "stroke": "none"},
link={"stroke": rx.color("accent", 6), "strokeOpacity": 0.5},
node_padding=28,
node_width=10,
margin={"top": 10, "right": 80, "bottom": 10, "left": 10},
width="100%",
height=300,
)Workshop notes
data is {"nodes": [...], "links": [...]}. A link's source and target are node positions.
A dict for node or link styles them all (SVG attribute names in camelCase).
Tune the layout with node_width, node_padding, link_curvature and sort.
For more freedom, return SVG from @rx.recharts.sankey_chart.node / .link functions.
Connect data to a dict in State and the flows are recalculated when it changes.
Get news about new lessons
We'll email you when this course gets new lessons or a follow-up course opens.
R
ReflexKR
Learn Python and Reflex by doing. Every example was run and verified on Reflex 0.10.0.
ReflexKR is free, with no banner ads. If it helped you, you can support it with a coffee. ☕ Buy us a coffee
Affiliate disclosure© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
Questions & answers
Ask anything about this lesson. Questions from every language version of this page appear together.