Quickstart examples
Get started with custom components v2 through these practical examples. Each example introduces a new concept to progressively build your understanding. To highlight each concept, the code on this page shows either a portion of the component example or a simplified version of it. Follow the links below each example to see the complete code for that example, including explanations.
Two-step component process
Creating and using a custom component involves two distinct steps:
- Registration: Define your component's HTML, CSS, and JavaScript with
st.components.v2.component(). - Mounting: Mount a specific instance of your component to your app's frontend using the
ComponentRenderercreated during registration.
For detailed explanations, see Component registration and Component mounting.
Hello world
This is a minimal static component that displays "Hello, World!" using the app's primary theme color. This component introduces the following concepts:
- Component registration with HTML and CSS using
st.components.v2.component() - Theme integration using CSS custom properties
- Mounting a component by calling the
ComponentRenderer
import streamlit as st
hello_component = st.components.v2.component(
name="hello_world",
html="<h2>Hello, World!</h2>",
css="h2 { color: var(--st-primary-color); }",
)
hello_component()
Rich data
This is a component that receives various data types from Python. This component introduces the following concepts:
- Passing data from Python via the
dataparameter and accessing it in JavaScript - Automatic dataframe and JSON serialization
- Passing an image as a Base64-encoded string
- Using a placeholder in the component's HTML and dynamically updating it with received data
data_component = st.components.v2.component(
"data_display",
html="""<div id="data-container">Loading data...</div>""",
js="""
export default function({ data, parentElement }) {
const container = parentElement.querySelector("#data-container");
const df = data.df;
const userInfo = data.user_info;
const imgBase64 = data.image_base64;
container.innerHTML = `
<h4>Dataframe: ${df}</h4>
<h4>User Info: ${userInfo.name}</h4>
<img src="data:image/png;base64,${imgBase64}" style="width: 25%;" />
`;
}
""",
)
data_component(
data={
"df": df, # Arrow-serializable
"user_info": {"name": "Alice"}, # JSON-serializable
"image_base64": img_base64 # Base64 string
}
)
Simple button
This is an interactive button that sends events to Python. This component introduces the following concepts:
- Component registration with HTML, CSS, and JavaScript
- One-time trigger values sent from JavaScript with
setTriggerValue() - Callback functions using the
on_<trigger>_changenaming pattern - Accessing trigger values from the component's return object
import streamlit as st
def handle_button_click():
st.session_state.click_count += 1
st.session_state.setdefault("click_count", 0)
button_component = st.components.v2.component(
"simple_button",
html="""<button id="btn">Click me</button>""",
css="""button { background-color: var(--st-primary-color); }""",
js="""
export default function(component) {
const { setTriggerValue, parentElement } = component;
parentElement.querySelector("button").onclick = () => {
setTriggerValue("action", "button_clicked");
};
}
""",
)
result = button_component(on_action_change=handle_button_click)
st.write(result.action)
st.write(f"Total clicks: {st.session_state.click_count}")
Simple checkbox
This is a simple checkbox that reports a stateful value to Python. This component introduces the following concepts:
- Persistent state values sent from JavaScript with
setStateValue() - Callback functions with the
on_<state>_changenaming pattern - Initializing a stateful component with the
dataanddefaultparameters - Using font from the app's theme
- Accessing state values from the component's return object
import streamlit as st
checkbox_component = st.components.v2.component(
"simple_checkbox",
html="""
<label class="checkbox-container">
<input type="checkbox" id="checkbox" />
<span>Enable feature</span>
</label>
""",
css="""
.checkbox-container {
gap: 0.5rem;
font-family: var(--st-font);
color: var(--st-text-color);
}
""",
js="""
export default function({ parentElement, data, setStateValue }) {
const checkbox = parentElement.querySelector("#checkbox");
checkbox.checked = data?.checked ?? false;
checkbox.onchange = () => {
setStateValue("checked", checkbox.checked);
};
}
""",
)
initial_state = True
result = checkbox_component(
data={"checked": initial_state},
default={"checked": initial_state},
on_checked_change=lambda: None,
)
st.write(f"Current state: {'Enabled' if result.checked else 'Disabled'}")
Interactive counter
This is a counter with increment, decrement, and reset functionality. This component introduces the following concepts:
- Combining state and trigger values in one component
- Multiple event handlers
<div class="counter">
<h3>Count: <span id="display">0</span></h3>
<div class="buttons">
<button id="decrement">-1</button>
<button id="increment">+1</button>
<button id="reset">Reset</button>
</div>
</div>
export default function ({ parentElement, setStateValue, setTriggerValue }) {
const incrementBtn = parentElement.querySelector("#increment");
const decrementBtn = parentElement.querySelector("#decrement");
const resetBtn = parentElement.querySelector("#reset");
let count = 0;
decrementBtn.onclick = () => {
count--;
setStateValue("count", count); // Persistent state
};
incrementBtn.onclick = () => {
count++;
setStateValue("count", count); // Persistent state
};
resetBtn.onclick = () => {
count = 0;
setTriggerValue("reset", true); // One-time event
setStateValue("count", 0);
};
}
Text input
This is a text input component that demonstrates full bidirectional communication, including programmatic updates from Python. This component introduces the following concepts:
- Mounting a component with a key and reading component state from Session State
- Wrapping a component's raw mounting command to create a user-friendly mounting command
- Programmatic updates from Python via the
dataparameter - Syncing frontend state without interrupting user input
def textbox_component_wrapper(
label, *, default="", key=None, on_change=lambda: None
):
# Read current state from Session State
component_state = st.session_state.get(key, {})
value = component_state.get("value", default)
# Pass current value to component
data = {"label": label, "value": value}
result = textbox_component(
data=data,
default={"value": value},
key=key,
on_value_change=on_change,
)
return result
const input = parentElement.querySelector("input");
// Sync input value with data from Python
if (input.value !== data.value) {
input.value = data.value ?? "";
}
Danger button
This is a hold-to-confirm button with frontend validation and visual feedback. This component introduces the following concepts:
- Frontend validation before sending data to Python
- Timed interactions with
requestAnimationFrame() - Visual feedback with CSS animations and transitions
- Rate limiting with cooldown periods
- Touch events for mobile support
- Layout control using the
widthparameter - Cleanup functions for event listeners
function startHold() {
startTime = Date.now();
animationFrame = requestAnimationFrame(updateProgress);
}
function updateProgress() {
const progressPercent = Math.min(elapsed / HOLD_DURATION, 1);
if (progressPercent >= 1) {
setTriggerValue("confirmed", true); // Only after 2 seconds
} else {
animationFrame = requestAnimationFrame(updateProgress);
}
}
result = danger_button(
on_confirmed_change=on_delete_confirmed,
width="content" # Layout control
)
Radial menu
This is a circular selection menu demonstrating state values for persistent selections. This component introduces the following concepts:
- CSS custom properties for dynamic positioning (
--i,--total) - A fixed-position backdrop for click-outside behavior
- Complex animations with CSS transitions
result = radial_menu(
data={"options": options, "selection": "burger"},
default={"selection": "burger"}, # Avoids initial rerun
on_selection_change=lambda: None,
)
// Dynamic element creation
Object.entries(options).forEach(([value, icon], index) => {
const button = document.createElement("button");
button.style.setProperty("--i", index);
button.style.setProperty("--total", Object.keys(options).length);
// ...
});
What's next?
Now that you've seen these examples:
- Learn the fundamentals in Component registration and Component mounting.
- Understand State versus trigger values for advanced interactions.
- Explore Theming and styling to make beautiful components.
- Build complex projects with Package-based components.
Still have questions?
Our forums are full of helpful information and Streamlit experts.