Create a draggable and resizable dashboard in Streamlit, featuring Material UI widgets, Monaco editor (Visual Studio Code), Nivo charts, and more!
Streamlit Elements is a component that gives you the tools to compose beautiful applications with Material UI widgets, Monaco, Nivo charts, and more. It also includes a feature to create draggable and resizable dashboards.
pip install streamlit-elements==0.1.*Caution: It is recommended to pin the version to 0.1.*. Future versions might introduce breaking API changes.
Here is a list of elements and objects you can import in your app:
| Element | Description |
|---|---|
| elements | Create a frame where elements will be displayed. |
| dashboard | Build a draggable and resizable dashboard. |
| mui | Material UI (MUI) widgets and icons. |
| html | HTML objects. |
| editor | Monaco code and diff editor that powers Visual Studio Code. |
| nivo | Nivo chart library. |
| media | Media player. |
| sync | Callback to synchronize Streamlit's session state with elements events data. |
| lazy | Defer a callback call until another non-lazy callback is called. |
- A few Material UI widgets may not work as expected (ie. modal dialogs and snackbars).
- Using many element frames can significantly impact your app's performance. Try to gather elements in few frames at most.
# First, import the elements you needfromstreamlit_elementsimportelements, mui, html# Create a frame where Elements widgets will be displayed.## Elements widgets will not render outside of this frame.# Native Streamlit widgets will not render inside this frame.## elements() takes a key as parameter.# This key can't be reused by another frame or Streamlit widget.withelements("new_element"):
# Let's create a Typography element with "Hello world" as children.# The first step is to check Typography's documentation on MUI:# https://mui.com/components/typography/## Here is how you would write it in React JSX:## <Typography># Hello world# </Typography>mui.Typography("Hello world")- MUI Typography: https://mui.com/components/typography/
withelements("multiple_children"):
# You have access to Material UI icons using: mui.icon.IconNameHere## Multiple children can be added in a single element.## <Button># <EmojiPeople /># <DoubleArrow /># Hello world# </Button>mui.Button(
mui.icon.EmojiPeople,
mui.icon.DoubleArrow,
"Button with multiple children"
)
# You can also add children to an element using a 'with' statement.## <Button># <EmojiPeople /># <DoubleArrow /># <Typography># Hello world# </Typography># </Button>withmui.Button:
mui.icon.EmojiPeople()
mui.icon.DoubleArrow()
mui.Typography("Button with multiple children")- MUI button: https://mui.com/components/buttons/
- MUI icons: https://mui.com/components/material-icons/
withelements("nested_children"):
# You can nest children using multiple 'with' statements.## <Paper># <Typography># <p>Hello world</p># <p>Goodbye world</p># </Typography># </Paper>withmui.Paper:
withmui.Typography:
html.p("Hello world")
html.p("Goodbye world")- MUI paper: https://mui.com/components/paper/
withelements("properties"):
# You can add properties to elements with named parameters.## To find all available parameters for a given element, you can# refer to its related documentation on mui.com for MUI widgets,# on https://microsoft.github.io/monaco-editor/ for Monaco editor,# and so on.## <Paper elevation={3} variant="outlined" square># <TextField label="My text input" defaultValue="Type here" variant="outlined" /># </Paper>withmui.Paper(elevation=3, variant="outlined", square=True):
mui.TextField(
label="My text input",
defaultValue="Type here",
variant="outlined",
)
# If you must pass a parameter which is also a Python keyword, you can append an# underscore to avoid a syntax error.## <Collapse in />mui.Collapse(in_=True)
# mui.collapse(in=True)# > Syntax error: 'in' is a Python keyword:- MUI text field: https://mui.com/components/text-fields/
withelements("style_mui_sx"):
# For Material UI elements, use the 'sx' property.## <Box# sx={{# bgcolor: 'background.paper',# boxShadow: 1,# borderRadius: 2,# p: 2,# minWidth: 300,# }}# ># Some text in a styled box# </Box>mui.Box(
"Some text in a styled box",
sx={
"bgcolor": "background.paper",
"boxShadow": 1,
"borderRadius": 2,
"p": 2,
"minWidth": 300,
}
)- MUI's sx property: https://mui.com/system/the-sx-prop/
withelements("style_elements_css"):
# For any other element, use the 'css' property.## <div# css={{# backgroundColor: 'hotpink',# '&:hover': {# color: 'lightgreen'# }# }}# ># This has a hotpink background# </div>html.div(
"This has a hotpink background",
css={
"backgroundColor": "hotpink",
"&:hover": {
"color": "lightgreen"
}
}
)- Emotion's css property: https://emotion.sh/docs/css-prop#object-styles
importstreamlitasstwithelements("callbacks_retrieve_data"):
# Some element allows executing a callback on specific event.## const [name, setName] = React.useState("")# const handleChange = (event) => {# // You can see here that a text field value# // is stored in event.target.value# setName(event.target.value)# }## <TextField# label="Input some text here"# onChange={handleChange}# /># Initialize a new item in session state called "my_text"if"my_text"notinst.session_state:
st.session_state.my_text=""# When text field changes, this function will be called.# To know which parameters are passed to the callback,# you can refer to the element's documentation.defhandle_change(event):
st.session_state.my_text=event.target.value# Here we display what we have typed in our text fieldmui.Typography(st.session_state.my_text)
# And here we give our 'handle_change' callback to the 'onChange'# property of the text field.mui.TextField(label="Input some text here", onChange=handle_change)- MUI text field event: https://mui.com/components/text-fields/#uncontrolled-vs-controlled
- MUI text field API: https://mui.com/api/text-field/
withelements("callbacks_sync"):
# If you just want to store callback parameters into Streamlit's session state# like above, you can also use the special function sync().## When an onChange event occurs, the callback is called with an event data object# as argument. In the example below, we are synchronizing that event data object with# the session state item 'my_event'.## If an event passes more than one parameter, you can synchronize as many session state item# as needed like so:# >>> sync("my_first_param", "my_second_param")## If you want to ignore the first parameter of an event but keep synchronizing the second,# pass None to sync:# >>> sync(None, "second_parameter_to_keep")fromstreamlit_elementsimportsyncif"my_event"notinst.session_state:
st.session_state.my_event=Noneifst.session_state.my_eventisnotNone:
text=st.session_state.my_event.target.valueelse:
text=""mui.Typography(text)
mui.TextField(label="Input some text here", onChange=sync("my_event"))withelements("callbacks_lazy"):
# With the two first examples, each time you input a letter into the text field,# the callback is invoked but the whole app is reloaded as well.## To avoid reloading the whole app on every input, you can wrap your callback with# lazy(). This will defer the callback invocation until another non-lazy callback# is invoked. This can be useful to implement forms.fromstreamlit_elementsimportlazyif"first_name"notinst.session_state:
st.session_state.first_name=Nonest.session_state.last_name=Noneifst.session_state.first_nameisnotNone:
first_name=st.session_state.first_name.target.valueelse:
first_name="John"ifst.session_state.last_nameisnotNone:
last_name=st.session_state.last_name.target.valueelse:
last_name="Doe"defset_last_name(event):
st.session_state.last_name=event# Display first name and last namemui.Typography("Your first name: ", first_name)
mui.Typography("Your last name: ", last_name)
# Lazily synchronize onChange with first_name and last_name state.# Inputting some text won't synchronize the value yet.mui.TextField(label="First name", onChange=lazy(sync("first_name")))
# You can also pass regular python functions to lazy().mui.TextField(label="Last name", onChange=lazy(set_last_name))
# Here we give a non-lazy callback to onClick using sync().# We are not interested in getting onClick event data object,# so we call sync() with no argument.## You can use either sync() or a regular python function.# As long as the callback is not wrapped with lazy(), its invocation will# also trigger every other defered callbacks.mui.Button("Update first namd and last name", onClick=sync())withelements("callbacks_hotkey"):
# Invoke a callback when a specific hotkey sequence is pressed.## For more information regarding sequences syntax and supported keys,# go to Mousetrap's project page linked below.## /!\ Hotkeys work if you don't have focus on Streamlit Elements's frame /!\# /!\ As with other callbacks, this reruns the whole app /!\fromstreamlit_elementsimporteventdefhotkey_pressed():
print("Hotkey pressed")
event.Hotkey("g", hotkey_pressed)
# If you want your hotkey to work even in text fields, set bind_inputs to True.event.Hotkey("h", hotkey_pressed, bindInputs=True)
mui.TextField(label="Try pressing 'h' while typing some text here.")
# If you want to override default hotkeys (ie. ctrl+f to search in page),# set overrideDefault to True.event.Hotkey("ctrl+f", hotkey_pressed, overrideDefault=True)- Mousetrap: https://craig.is/killing/mice
- Github page: https://github.com/ccampbell/mousetrap
withelements("callbacks_interval"):
# Invoke a callback every n seconds.## /!\ As with other callbacks, this reruns the whole app /!\defcall_every_second():
print("Hello world")
event.Interval(1, call_every_second)withelements("dashboard"):
# You can create a draggable and resizable dashboard using# any element available in Streamlit Elements.fromstreamlit_elementsimportdashboard# First, build a default layout for every element you want to include in your dashboardlayout= [
# Parameters: element_identifier, x_pos, y_pos, width, height, [item properties...]dashboard.Item("first_item", 0, 0, 2, 2),
dashboard.Item("second_item", 2, 0, 2, 2, isDraggable=False, moved=False),
dashboard.Item("third_item", 0, 2, 1, 1, isResizable=False),
]
# Next, create a dashboard layout using the 'with' syntax. It takes the layout# as first parameter, plus additional properties you can find in the GitHub links below.withdashboard.Grid(layout):
mui.Paper("First item", key="first_item")
mui.Paper("Second item (cannot drag)", key="second_item")
mui.Paper("Third item (cannot resize)", key="third_item")
# If you want to retrieve updated layout values as the user move or resize dashboard items,# you can pass a callback to the onLayoutChange event parameter.defhandle_layout_change(updated_layout):
# You can save the layout in a file, or do anything you want with it.# You can pass it back to dashboard.Grid() if you want to restore a saved layout.print(updated_layout)
withdashboard.Grid(layout, onLayoutChange=handle_layout_change):
mui.Paper("First item", key="first_item")
mui.Paper("Second item (cannot drag)", key="second_item")
mui.Paper("Third item (cannot resize)", key="third_item")- Dashboard item properties: https://github.com/react-grid-layout/react-grid-layout#grid-item-props
- Dashboard grid properties (Streamlit Elements uses the Responsive grid layout):
withelements("monaco_editors"):
# Streamlit Elements embeds Monaco code and diff editor that powers Visual Studio Code.# You can configure editor's behavior and features with the 'options' parameter.## Streamlit Elements uses an unofficial React implementation (GitHub links below for# documentation).fromstreamlit_elementsimporteditorif"content"notinst.session_state:
st.session_state.content="Default value"mui.Typography("Content: ", st.session_state.content)
defupdate_content(value):
st.session_state.content=valueeditor.Monaco(
height=300,
defaultValue=st.session_state.content,
onChange=lazy(update_content)
)
mui.Button("Update content", onClick=sync())
editor.MonacoDiff(
original="Happy Streamlit-ing!",
modified="Happy Streamlit-in' with Elements!",
height=300,
)- Monaco examples and properties: https://github.com/suren-atoyan/monaco-react
- Code editor options: https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneEditorConstructionOptions.html
- Diff editor options: https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IStandaloneDiffEditorConstructionOptions.html
- Monaco project page: https://microsoft.github.io/monaco-editor/
withelements("nivo_charts"):
# Streamlit Elements includes 45 dataviz components powered by Nivo.fromstreamlit_elementsimportnivoDATA= [
{ "taste": "fruity", "chardonay": 93, "carmenere": 61, "syrah": 114 },
{ "taste": "bitter", "chardonay": 91, "carmenere": 37, "syrah": 72 },
{ "taste": "heavy", "chardonay": 56, "carmenere": 95, "syrah": 99 },
{ "taste": "strong", "chardonay": 64, "carmenere": 90, "syrah": 30 },
{ "taste": "sunny", "chardonay": 119, "carmenere": 94, "syrah": 103 },
]
withmui.Box(sx={"height": 500}):
nivo.Radar(
data=DATA,
keys=[ "chardonay", "carmenere", "syrah" ],
indexBy="taste",
valueFormat=">-.2f",
margin={ "top": 70, "right": 80, "bottom": 40, "left": 80 },
borderColor={ "from": "color" },
gridLabelOffset=36,
dotSize=10,
dotColor={ "theme": "background" },
dotBorderWidth=2,
motionConfig="wobbly",
legends=[
{
"anchor": "top-left",
"direction": "column",
"translateX": -50,
"translateY": -40,
"itemWidth": 80,
"itemHeight": 20,
"itemTextColor": "#999",
"symbolSize": 12,
"symbolShape": "circle",
"effects": [
{
"on": "hover",
"style": {
"itemTextColor": "#000"
}
}
]
}
],
theme={
"background": "#FFFFFF",
"textColor": "#31333F",
"tooltip": {
"container": {
"background": "#FFFFFF",
"color": "#31333F",
}
}
}
)- Nivo charts: https://nivo.rocks/
- Github page: https://github.com/plouc/nivo
withelements("media_player"):
# Play video from many third-party sources: YouTube, Facebook, Twitch,# SoundCloud, Streamable, Vimeo, Wistia, Mixcloud, DailyMotion and Kaltura.## This element is powered by ReactPlayer (GitHub link below).fromstreamlit_elementsimportmediamedia.Player(url="https://www.youtube.com/watch?v=iik25wqIuFo", controls=True)- ReactPlayer properties: https://github.com/cookpete/react-player#props
