From bb3b4f064fb804510c3e38c565694478d16af322 Mon Sep 17 00:00:00 2001
From: Debbie Matthews
Date: Fri, 24 Oct 2025 17:09:15 -0700
Subject: [PATCH 1/3] Example apps for custom components v2
---
.../charts.video3/requirements.txt | 2 +-
.../components.custom_anchors.py | 44 +++++++++++++
.../components.interactive_svg.py | 51 ++++++++++++++
.../components.markdown_links.py | 33 ++++++++++
.../components.text_input.py | 66 +++++++++++++++++++
.../guides/requirements.txt | 2 +-
.../hello/requirements.txt | 2 +-
.../mpa-hello/requirements.txt | 2 +-
.../navigation.example_1/requirements.txt | 2 +-
.../navigation.example_2/requirements.txt | 2 +-
.../navigation.example_top/requirements.txt | 2 +-
python/api-examples-source/requirements.txt | 2 +-
.../requirements.txt | 2 +-
.../theming/requirements.txt | 2 +-
.../custom-navigation/requirements.txt | 2 +-
.../elements/charts/requirements.txt | 2 +-
.../elements/dataframes/requirements.txt | 2 +-
.../tutorials/requirements.txt | 2 +-
.../utilities.switch_page/requirements.txt | 2 +-
.../widget.page_link/requirements.txt | 2 +-
python/concept-source/requirements.txt | 2 +-
.../requirements.txt | 2 +-
.../theming-color-baseRadius/requirements.txt | 2 +-
.../requirements.txt | 2 +-
.../requirements.txt | 2 +-
.../theming-color-textColor/requirements.txt | 2 +-
.../requirements.txt | 2 +-
.../requirements.txt | 2 +-
.../llm-18-lines-of-code/requirements.txt | 2 +-
29 files changed, 219 insertions(+), 25 deletions(-)
create mode 100644 python/api-examples-source/components.custom_anchors.py
create mode 100644 python/api-examples-source/components.interactive_svg.py
create mode 100644 python/api-examples-source/components.markdown_links.py
create mode 100644 python/api-examples-source/components.text_input.py
diff --git a/python/api-examples-source/charts.video3/requirements.txt b/python/api-examples-source/charts.video3/requirements.txt
index d19c75d8b..a8ee71bca 100644
--- a/python/api-examples-source/charts.video3/requirements.txt
+++ b/python/api-examples-source/charts.video3/requirements.txt
@@ -1,2 +1,2 @@
-streamlit>=1.50.0
+streamlit-nightly
webvtt-py
\ No newline at end of file
diff --git a/python/api-examples-source/components.custom_anchors.py b/python/api-examples-source/components.custom_anchors.py
new file mode 100644
index 000000000..5c371ad01
--- /dev/null
+++ b/python/api-examples-source/components.custom_anchors.py
@@ -0,0 +1,44 @@
+import streamlit as st
+
+JS = """
+export default function(component) {
+ const { data, setTriggerValue, parentElement } = component;
+ parentElement.innerHTML = data;
+ const links = parentElement.querySelectorAll('a');
+
+ links.forEach((link) => {
+ link.onclick = (e) => {
+ setTriggerValue('clicked', link.getAttribute('data-link'));
+ };
+ });
+}
+"""
+
+CSS = """
+a {
+ color: var(--st-link-color);
+}
+"""
+
+my_component = st.components.v2.component(
+ "inline_links",
+ css=CSS,
+ js=JS,
+)
+
+paragraph_html = """
+This is an example paragraph with inline links. To see the response in
+Python, click on the first link or
+second link.
+"""
+
+
+def callback():
+ pass
+
+
+result = my_component(data=paragraph_html, on_clicked_change=callback)
+if result.clicked == "link_1":
+ st.write("You clicked the first link!")
+elif result.clicked == "link_2":
+ st.write("You clicked the second link!")
diff --git a/python/api-examples-source/components.interactive_svg.py b/python/api-examples-source/components.interactive_svg.py
new file mode 100644
index 000000000..bcda15420
--- /dev/null
+++ b/python/api-examples-source/components.interactive_svg.py
@@ -0,0 +1,51 @@
+import streamlit as st
+
+HTML = """
+Click on the triangle, square, or circle to interact with the shapes:
+
+
+"""
+
+JS = """
+export default function(component) {
+ const { setTriggerValue, parentElement } = component;
+ const shapes = parentElement.querySelectorAll('[data-shape]');
+
+ shapes.forEach((shape) => {
+ shape.onclick = (e) => {
+ setTriggerValue('clicked', shape.getAttribute('data-shape'));
+ };
+ });
+}
+"""
+
+CSS = """
+polygon, rect, circle {
+ stroke: var(--st-primary-color);
+ stroke-width: 2;
+ fill: transparent;
+ cursor: pointer;
+}
+polygon:hover, rect:hover, circle:hover {
+ fill: var(--st-secondary-background-color);
+}
+"""
+
+
+def callback():
+ pass
+
+
+my_component = st.components.v2.component(
+ "clickable_svg",
+ html=HTML,
+ css=CSS,
+ js=JS,
+)
+
+result = my_component(on_clicked_change=callback)
+result
diff --git a/python/api-examples-source/components.markdown_links.py b/python/api-examples-source/components.markdown_links.py
new file mode 100644
index 000000000..10a894ba6
--- /dev/null
+++ b/python/api-examples-source/components.markdown_links.py
@@ -0,0 +1,33 @@
+import streamlit as st
+
+JS = """
+export default function(component) {
+ const { setTriggerValue } = component;
+ const links = document.querySelectorAll('a[href="#"]');
+
+ links.forEach((link) => {
+ link.onclick = (e) => {
+ setTriggerValue('clicked', link.innerHTML);
+ };
+ });
+}
+"""
+
+my_component = st.components.v2.component(
+ "inline_links",
+ js=JS,
+)
+
+
+def callback():
+ pass
+
+
+result = my_component(on_clicked_change=callback)
+
+st.markdown(
+ "Components aren't [sandboxed](#), so you can write JS that [interacts](#) with the main [document](#)."
+)
+
+if result.clicked:
+ st.write(f"You clicked {result.clicked}!")
diff --git a/python/api-examples-source/components.text_input.py b/python/api-examples-source/components.text_input.py
new file mode 100644
index 000000000..b9e28b134
--- /dev/null
+++ b/python/api-examples-source/components.text_input.py
@@ -0,0 +1,66 @@
+import streamlit as st
+
+HTML = """
+
+
+"""
+
+JS = """
+ export default function(component) {
+ const { setStateValue, parentElement, data } = component;
+
+ const label = parentElement.querySelector('label');
+ label.innerText = data.label;
+
+ const input = parentElement.querySelector('input');
+ if (input.value !== data.value) {
+ input.value = data.value ?? '';
+ };
+
+ input.onkeydown = (e) => {
+ if (e.key === 'Enter') {
+ setStateValue('value', e.target.value);
+ }
+ };
+
+ input.onblur = (e) => {
+ setStateValue('value', e.target.value);
+ };
+ }
+"""
+
+my_component = st.components.v2.component(
+ "my_text_input",
+ html=HTML,
+ js=JS,
+)
+
+
+def callback():
+ pass
+
+
+def my_component_wrapper(label, *, default="", key=None, on_change=callback):
+ component_state = st.session_state.get(key, {})
+ value = component_state.get("value", default)
+ data = {"label": label, "value": value}
+ result = my_component(
+ data=data, default={"value": value}, key=key, on_value_change=on_change
+ )
+ return result
+
+
+st.title("My custom component")
+
+if st.button("Hello World"):
+ st.session_state["my_text_input_instance"]["value"] = "Hello World"
+if st.button("Clear text"):
+ st.session_state["my_text_input_instance"]["value"] = ""
+result = my_component_wrapper(
+ "Enter something",
+ default="I love Streamlit!",
+ key="my_text_input_instance",
+)
+
+st.write("Result:", result)
+st.write("Session state:", st.session_state)
diff --git a/python/api-examples-source/guides/requirements.txt b/python/api-examples-source/guides/requirements.txt
index e52b3a361..a4462d862 100644
--- a/python/api-examples-source/guides/requirements.txt
+++ b/python/api-examples-source/guides/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
+streamlit-nightly
diff --git a/python/api-examples-source/hello/requirements.txt b/python/api-examples-source/hello/requirements.txt
index b1f7e0b62..46748ba36 100644
--- a/python/api-examples-source/hello/requirements.txt
+++ b/python/api-examples-source/hello/requirements.txt
@@ -2,4 +2,4 @@ pandas==1.5.3
numpy==1.23.5
altair==4.2.0
pydeck==0.8.0
-streamlit>=1.50.0
+streamlit-nightly
diff --git a/python/api-examples-source/mpa-hello/requirements.txt b/python/api-examples-source/mpa-hello/requirements.txt
index c552d4cd3..aa4d6800c 100644
--- a/python/api-examples-source/mpa-hello/requirements.txt
+++ b/python/api-examples-source/mpa-hello/requirements.txt
@@ -3,4 +3,4 @@ numpy==1.23.5
altair==4.2.0
pydeck==0.8.0
opencv-python-headless==4.8.1.78
-streamlit>=1.50.0
+streamlit-nightly
diff --git a/python/api-examples-source/navigation.example_1/requirements.txt b/python/api-examples-source/navigation.example_1/requirements.txt
index e52b3a361..a4462d862 100644
--- a/python/api-examples-source/navigation.example_1/requirements.txt
+++ b/python/api-examples-source/navigation.example_1/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
+streamlit-nightly
diff --git a/python/api-examples-source/navigation.example_2/requirements.txt b/python/api-examples-source/navigation.example_2/requirements.txt
index ff1db4e58..13e4deab9 100644
--- a/python/api-examples-source/navigation.example_2/requirements.txt
+++ b/python/api-examples-source/navigation.example_2/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
\ No newline at end of file
+streamlit-nightly
\ No newline at end of file
diff --git a/python/api-examples-source/navigation.example_top/requirements.txt b/python/api-examples-source/navigation.example_top/requirements.txt
index ff1db4e58..13e4deab9 100644
--- a/python/api-examples-source/navigation.example_top/requirements.txt
+++ b/python/api-examples-source/navigation.example_top/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
\ No newline at end of file
+streamlit-nightly
\ No newline at end of file
diff --git a/python/api-examples-source/requirements.txt b/python/api-examples-source/requirements.txt
index 482098df7..1e2937ca5 100644
--- a/python/api-examples-source/requirements.txt
+++ b/python/api-examples-source/requirements.txt
@@ -11,4 +11,4 @@ pydeck
Faker
openai
vega_datasets
-streamlit>=1.50.0
+streamlit-nightly
diff --git a/python/api-examples-source/st-experimental-connection/1.22/st-experimental-connection/requirements.txt b/python/api-examples-source/st-experimental-connection/1.22/st-experimental-connection/requirements.txt
index 5bd269b6b..5cefc45c1 100644
--- a/python/api-examples-source/st-experimental-connection/1.22/st-experimental-connection/requirements.txt
+++ b/python/api-examples-source/st-experimental-connection/1.22/st-experimental-connection/requirements.txt
@@ -1,4 +1,4 @@
-streamlit>=1.50.0
+streamlit-nightly
toml
sqlalchemy==1.4
duckdb
diff --git a/python/api-examples-source/theming/requirements.txt b/python/api-examples-source/theming/requirements.txt
index 58baf9e6e..a93dd41a2 100644
--- a/python/api-examples-source/theming/requirements.txt
+++ b/python/api-examples-source/theming/requirements.txt
@@ -1,4 +1,4 @@
-streamlit>=1.50.0
+streamlit-nightly
vega_datasets
altair==4.2.0
plotly==5.13.0
\ No newline at end of file
diff --git a/python/api-examples-source/tutorials/custom-navigation/requirements.txt b/python/api-examples-source/tutorials/custom-navigation/requirements.txt
index e52b3a361..a4462d862 100644
--- a/python/api-examples-source/tutorials/custom-navigation/requirements.txt
+++ b/python/api-examples-source/tutorials/custom-navigation/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
+streamlit-nightly
diff --git a/python/api-examples-source/tutorials/elements/charts/requirements.txt b/python/api-examples-source/tutorials/elements/charts/requirements.txt
index f00dcff1a..f5966bf57 100644
--- a/python/api-examples-source/tutorials/elements/charts/requirements.txt
+++ b/python/api-examples-source/tutorials/elements/charts/requirements.txt
@@ -1,2 +1,2 @@
vega_datasets
-streamlit>=1.50.0
\ No newline at end of file
+streamlit-nightly
\ No newline at end of file
diff --git a/python/api-examples-source/tutorials/elements/dataframes/requirements.txt b/python/api-examples-source/tutorials/elements/dataframes/requirements.txt
index e99f2162f..7319bdccf 100644
--- a/python/api-examples-source/tutorials/elements/dataframes/requirements.txt
+++ b/python/api-examples-source/tutorials/elements/dataframes/requirements.txt
@@ -1,2 +1,2 @@
Faker
-streamlit>=1.50.0
\ No newline at end of file
+streamlit-nightly
\ No newline at end of file
diff --git a/python/api-examples-source/tutorials/requirements.txt b/python/api-examples-source/tutorials/requirements.txt
index e52b3a361..a4462d862 100644
--- a/python/api-examples-source/tutorials/requirements.txt
+++ b/python/api-examples-source/tutorials/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
+streamlit-nightly
diff --git a/python/api-examples-source/utilities.switch_page/requirements.txt b/python/api-examples-source/utilities.switch_page/requirements.txt
index e52b3a361..a4462d862 100644
--- a/python/api-examples-source/utilities.switch_page/requirements.txt
+++ b/python/api-examples-source/utilities.switch_page/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
+streamlit-nightly
diff --git a/python/api-examples-source/widget.page_link/requirements.txt b/python/api-examples-source/widget.page_link/requirements.txt
index e52b3a361..a4462d862 100644
--- a/python/api-examples-source/widget.page_link/requirements.txt
+++ b/python/api-examples-source/widget.page_link/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
+streamlit-nightly
diff --git a/python/concept-source/requirements.txt b/python/concept-source/requirements.txt
index ff1db4e58..13e4deab9 100644
--- a/python/concept-source/requirements.txt
+++ b/python/concept-source/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
\ No newline at end of file
+streamlit-nightly
\ No newline at end of file
diff --git a/python/concept-source/theming-color-backgroundColor/requirements.txt b/python/concept-source/theming-color-backgroundColor/requirements.txt
index ff1db4e58..13e4deab9 100644
--- a/python/concept-source/theming-color-backgroundColor/requirements.txt
+++ b/python/concept-source/theming-color-backgroundColor/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
\ No newline at end of file
+streamlit-nightly
\ No newline at end of file
diff --git a/python/concept-source/theming-color-baseRadius/requirements.txt b/python/concept-source/theming-color-baseRadius/requirements.txt
index ff1db4e58..13e4deab9 100644
--- a/python/concept-source/theming-color-baseRadius/requirements.txt
+++ b/python/concept-source/theming-color-baseRadius/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
\ No newline at end of file
+streamlit-nightly
\ No newline at end of file
diff --git a/python/concept-source/theming-color-borderColor/requirements.txt b/python/concept-source/theming-color-borderColor/requirements.txt
index ff1db4e58..13e4deab9 100644
--- a/python/concept-source/theming-color-borderColor/requirements.txt
+++ b/python/concept-source/theming-color-borderColor/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
\ No newline at end of file
+streamlit-nightly
\ No newline at end of file
diff --git a/python/concept-source/theming-color-primaryColor/requirements.txt b/python/concept-source/theming-color-primaryColor/requirements.txt
index ff1db4e58..13e4deab9 100644
--- a/python/concept-source/theming-color-primaryColor/requirements.txt
+++ b/python/concept-source/theming-color-primaryColor/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
\ No newline at end of file
+streamlit-nightly
\ No newline at end of file
diff --git a/python/concept-source/theming-color-textColor/requirements.txt b/python/concept-source/theming-color-textColor/requirements.txt
index ff1db4e58..13e4deab9 100644
--- a/python/concept-source/theming-color-textColor/requirements.txt
+++ b/python/concept-source/theming-color-textColor/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
\ No newline at end of file
+streamlit-nightly
\ No newline at end of file
diff --git a/python/concept-source/theming-overview-anthropic-light-inspried/requirements.txt b/python/concept-source/theming-overview-anthropic-light-inspried/requirements.txt
index ff1db4e58..13e4deab9 100644
--- a/python/concept-source/theming-overview-anthropic-light-inspried/requirements.txt
+++ b/python/concept-source/theming-overview-anthropic-light-inspried/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
\ No newline at end of file
+streamlit-nightly
\ No newline at end of file
diff --git a/python/concept-source/theming-overview-spotify-inspired/requirements.txt b/python/concept-source/theming-overview-spotify-inspired/requirements.txt
index ff1db4e58..13e4deab9 100644
--- a/python/concept-source/theming-overview-spotify-inspired/requirements.txt
+++ b/python/concept-source/theming-overview-spotify-inspired/requirements.txt
@@ -1 +1 @@
-streamlit>=1.50.0
\ No newline at end of file
+streamlit-nightly
\ No newline at end of file
diff --git a/python/tutorial-source/llm-18-lines-of-code/requirements.txt b/python/tutorial-source/llm-18-lines-of-code/requirements.txt
index 73f238f83..6f8defe93 100644
--- a/python/tutorial-source/llm-18-lines-of-code/requirements.txt
+++ b/python/tutorial-source/llm-18-lines-of-code/requirements.txt
@@ -1,2 +1,2 @@
-streamlit>=1.50.0
+streamlit-nightly
langchain-openai
From eb049ced4fc4737e371cffd68c342e30676e3824 Mon Sep 17 00:00:00 2001
From: Debbie Matthews
Date: Mon, 27 Oct 2025 14:02:05 -0700
Subject: [PATCH 2/3] Add Tailwind example
---
.../components.custom_anchors.py | 7 +----
.../components.interactive_svg.py | 7 +----
.../components.markdown_links.py | 7 +----
.../components.tailwind.py | 30 +++++++++++++++++++
.../components.text_input.py | 8 ++---
5 files changed, 36 insertions(+), 23 deletions(-)
create mode 100644 python/api-examples-source/components.tailwind.py
diff --git a/python/api-examples-source/components.custom_anchors.py b/python/api-examples-source/components.custom_anchors.py
index 5c371ad01..6a3de4131 100644
--- a/python/api-examples-source/components.custom_anchors.py
+++ b/python/api-examples-source/components.custom_anchors.py
@@ -32,12 +32,7 @@
second link.
"""
-
-def callback():
- pass
-
-
-result = my_component(data=paragraph_html, on_clicked_change=callback)
+result = my_component(data=paragraph_html, on_clicked_change=lambda: None)
if result.clicked == "link_1":
st.write("You clicked the first link!")
elif result.clicked == "link_2":
diff --git a/python/api-examples-source/components.interactive_svg.py b/python/api-examples-source/components.interactive_svg.py
index bcda15420..fae4c6550 100644
--- a/python/api-examples-source/components.interactive_svg.py
+++ b/python/api-examples-source/components.interactive_svg.py
@@ -35,11 +35,6 @@
}
"""
-
-def callback():
- pass
-
-
my_component = st.components.v2.component(
"clickable_svg",
html=HTML,
@@ -47,5 +42,5 @@ def callback():
js=JS,
)
-result = my_component(on_clicked_change=callback)
+result = my_component(on_clicked_change=lambda: None)
result
diff --git a/python/api-examples-source/components.markdown_links.py b/python/api-examples-source/components.markdown_links.py
index 10a894ba6..d7a19d524 100644
--- a/python/api-examples-source/components.markdown_links.py
+++ b/python/api-examples-source/components.markdown_links.py
@@ -18,12 +18,7 @@
js=JS,
)
-
-def callback():
- pass
-
-
-result = my_component(on_clicked_change=callback)
+result = my_component(on_clicked_change=lambda: None)
st.markdown(
"Components aren't [sandboxed](#), so you can write JS that [interacts](#) with the main [document](#)."
diff --git a/python/api-examples-source/components.tailwind.py b/python/api-examples-source/components.tailwind.py
new file mode 100644
index 000000000..9127f7cb8
--- /dev/null
+++ b/python/api-examples-source/components.tailwind.py
@@ -0,0 +1,30 @@
+import streamlit as st
+
+HTML = """
+
+"""
+JS = """
+ export default function(component) {
+ const { setTriggerValue, parentElement } = component;
+ const button = parentElement.querySelector('button');
+ button.onclick = () => {
+ setTriggerValue('clicked', true);
+ };
+ }
+"""
+my_component = st.components.v2.component(
+ "my_tailwind_button",
+ html=HTML,
+ js=JS,
+)
+result_1 = my_component(
+ isolate_styles=False, on_clicked_change=lambda: None, key="one"
+)
+result_1
+
+result_2 = my_component(
+ isolate_styles=False, on_clicked_change=lambda: None, key="two"
+)
+result_2
diff --git a/python/api-examples-source/components.text_input.py b/python/api-examples-source/components.text_input.py
index b9e28b134..dcd59a925 100644
--- a/python/api-examples-source/components.text_input.py
+++ b/python/api-examples-source/components.text_input.py
@@ -36,11 +36,9 @@
)
-def callback():
- pass
-
-
-def my_component_wrapper(label, *, default="", key=None, on_change=callback):
+def my_component_wrapper(
+ label, *, default="", key=None, on_change=lambda: None
+):
component_state = st.session_state.get(key, {})
value = component_state.get("value", default)
data = {"label": label, "value": value}
From 14ba674cb9606c443414f86454997a64ce59e2a9 Mon Sep 17 00:00:00 2001
From: Debbie Matthews
Date: Mon, 27 Oct 2025 14:27:48 -0700
Subject: [PATCH 3/3] Self-host Tailwind script
---
.../components.tailwind.py | 7 +-
.../api-examples-source/tailwind_example.js | 12216 ++++++++++++++++
2 files changed, 12221 insertions(+), 2 deletions(-)
create mode 100644 python/api-examples-source/tailwind_example.js
diff --git a/python/api-examples-source/components.tailwind.py b/python/api-examples-source/components.tailwind.py
index 9127f7cb8..258949145 100644
--- a/python/api-examples-source/components.tailwind.py
+++ b/python/api-examples-source/components.tailwind.py
@@ -1,11 +1,14 @@
import streamlit as st
+with open("python/api-examples-source/tailwind_example.js", "r") as f:
+ TAILWIND_SCRIPT = f.read()
+
HTML = """
-