2026, Jan 01 13:00
Fix disappearing inline SVG in Streamlit: render Jinja2 output with st.markdown (unsafe_allow_html) instead of st.html
Inline SVG vanishes in Streamlit because st.html sanitizes with DOMPurify Learn to render Jinja2 SVG via st.markdown with unsafe_allow_html, or use a component.
Rendering inline SVG through Jinja2 in Streamlit looks straightforward until the output silently disappears. The container shows up, but the SVG itself is gone. The reason isn’t your markup or CSS — it’s how Streamlit handles HTML sanitization.
Problem setup
The UI structure is a centered container with an SVG circle. The HTML and CSS render correctly in a browser, but when passed through Streamlit via st.html, the SVG vanishes.
<style>
html,body {
height: 100%;
background-color: #404040;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}
.shell {
display: flex;
width: 500px;
height: 500px;
border: 3px solid #EEEEEE;
justify-content: center;
align-items: center;
border-radius: 20px;
}
.stage {
position: relative;
width: 2000px;
height: 600px;
}
.svg-holder {
width: 100%;
height: 100%;
}
.ring {
fill: none;
stroke: grey;
stroke-width: 10;
}
</style><body>
<div class="shell">
<div class="stage">
<svg viewBox="0 0 500 500" class="svg-holder">
<circle class="ring" cx="250" cy="250" r="198"></circle>
</svg>
</div>
</div>
</body>The rendering pipeline uses Jinja2 to render a template string and then pushes it to Streamlit:
from jinja2 import Template
import streamlit as st
def render_markup(raw_markup):
tpl = Template(raw_markup)
html_out = tpl.render()
st.text(html_out)
st.html(html_out)
# Somewhere in the app
render_markup(svg_block)What’s actually going wrong
Streamlit’s st.html applies internal sanitizing with DOMPurify. In this case, DOMPurify strips your <svg> because the tag isn’t in the accepted list. That’s why the outer container shows up and the SVG is blank. The filtering happens inside st.html, so valid browser output doesn’t guarantee it will survive Streamlit’s sanitization step.
Working paths forward
There are a few ways to proceed, depending on whether you want something quick or reusable.
The direct fix is to bypass the DOMPurify filter used by st.html by sending the markup through st.markdown with unsafe_allow_html=True. This accepts your SVG and renders it in the page without wrapping it in an iframe, which also makes sizing and styling more convenient.
import streamlit as st
st.markdown(
"""
<svg viewBox="0 0 500 500" class="svg-holder">
<circle class="ring" cx="250" cy="250" r="198"></circle>
</svg>
""",
unsafe_allow_html=True
)If your markup is built with Jinja2, keep the templating step and only switch the output sink from st.html to st.markdown:
from jinja2 import Template
import streamlit as st
def render_markup(raw_markup):
tpl = Template(raw_markup)
html_out = tpl.render()
st.markdown(html_out, unsafe_allow_html=True)This change keeps your logic intact and avoids the sanitization that removes the <svg> tag. The resulting HTML is wrapped in Streamlit’s own container divs and not placed inside an iframe, which means you can apply CSS and the element resizes with the app layout. The container width is adjusted live by Streamlit’s JavaScript, so the SVG scales while preserving its ratio.
An alternative is to use a custom component. Building a Streamlit component is a clean, reusable approach when you want to package SVG handling or more complex front-end integrations. This is the route to take if you plan to share or reuse the feature across apps.
Another option sometimes suggested is st.components.v1.html(your_svg_code, height=...), which renders inside an iframe. This makes styling and dynamic sizing less convenient because you’ll need to set a fixed height and you won’t be able to style the content from the parent page.
Why you should care
Understanding where sanitization happens saves time when embedding custom HTML in Streamlit. Inline SVG is common for dashboards and visualization widgets, yet it can be silently stripped if you feed it through st.html. Knowing that st.markdown with unsafe_allow_html=True renders the markup as-is, and that a component is the long-term solution, helps you decide quickly between a pragmatic fix and a maintainable pattern.
Takeaways
If inline SVG disappears in Streamlit, it’s likely being removed by DOMPurify inside st.html. Render the markup with st.markdown(..., unsafe_allow_html=True) to display your SVG without an iframe. If you need something reusable or more complex, build a Streamlit component. This way you get predictable rendering, easier styling, and a clear path to scale your UI.