CLI tool
ObservedObserver/streamlit-shadcn-ui avatar
ObservedObserver/streamlit-shadcn-ui

streamlit-shadcn-ui: overlays escape the Streamlit layout through the browser top layer

Using shadcn-ui components in streamlit

1,158 stars92 forksTypeScriptMIT

At a glance

What is it?
A shadcn component set for Streamlit built on Components V2, with no iframe, a documented migration from the 0.1.x line, and an explicit rule that the components must not be restyled to look like Streamlit. Six prototype scripts still sit in the repository root.
Who is it for?
streamlit-shadcn-ui fits a Streamlit team that wants a real component library instead of st.selectbox and st.button, and that has upgraded to Streamlit 1.60 or newer. It does not fit an application that still relies on the 0.1.x iframe implementation, because that path is not shipped in the 1.0 package.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 7 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • ObservedObserver/streamlit-shadcn-ui1,158 ★ · +12
Star history
Repository2026-08-012026-11-14
ObservedObserver/streamlit-shadcn-ui1.1k1.2k

12 Stars in the last 105 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Version 1.0 dropped the iframe and the compatibility namespace

The 1.0 release is a hard cut rather than a deprecation. The package root is the V2 API, the V1 iframe implementation is not shipped, and the compatibility namespace streamlit_shadcn_ui.v1 does not exist in the current package. Applications that cannot migrate yet are told to remain on the last 0.1.x release.

The listed source changes are small but structural. The button text argument becomes label. Grouped V1 checkboxes become ordinary composition of scalar checkboxes. The card context manager becomes Streamlit layout wrapped around a declarative Card. The low-level trigger and content helpers and the experimental element trees are removed outright. And component return values with callbacks replace anything that relied on raw session-state transport dictionaries.

That last one is the deepest of the six, because it changes how data leaves a component. The others are renames or restructuring that a search and replace will find; transport dictionaries are an architectural assumption, and code built on them will keep working right up until it does not.

Two documents carry the detail: a compatibility matrix for the full mapping and a V1 to 1.0 migration guide. An architecture decision record covers the V2 Python API for 1.0, and a production migration plan sits alongside it.

Overlays use the top layer instead of escaping the layout

Components render without an iframe, which is the technical decision everything else follows from. Version 1.0 uses checked-in shadcn source backed by Base UI, React 19 and Tailwind CSS 4, running inside Streamlit's isolated Shadow DOM runtime.

The problem that creates is positioning. Anything anchored or modal normally has to be placed inside its container, and in Streamlit that container clips it. So anchored and modal overlays stay in their own component ShadowRoot and use the browser top layer, which takes an element out of the normal stacking and clipping context entirely.

The components that depend on this are named: Select, Combobox, Dropdown Menu, Popover, Hover Card, Date Picker and Alert Dialog, plus Dialog and Tooltip when they are composed through Elements.

That list is worth reading as a capability claim rather than a component list. The two failure modes people hit when porting a web component library into another framework are the overlay being clipped and the overlay appearing behind something else, and this is the mechanism that addresses both.

The cost is that the library owns its own stacking behaviour inside a host application it does not control, which is a tradeoff rather than a free win.

Two rendering paths, and neither takes native Streamlit children

There are two ways to build a non-trivial interface, and the difference is where React state lives.

Ordinary helper calls are independently isolated V2 components. Each one is its own component with its own identity, and precomposed helpers such as Card, Popover and Collapsible accept documented text or data arguments. This is the path that behaves like normal Streamlit code, one call at a time.

ui.elements is the opt-in aggregate path. It builds one nested, stateful React tree from typed Card, Button Group, Dialog, Empty, Field, Tooltip, Spinner, layout, content, input, choice and action nodes, and the whole tree renders in a single V2 root:

python
with ui.elements(key="settings") as el:
    with el.card(key="account"):
        with el.card_header():
            el.heading("Account")
            el.text("Manage your preferences.", variant="muted")
        with el.card_content():
            email = el.input("Email", key="email")
        with el.card_footer():
            save = el.button("Save", key="save", stretch=True)

st.write(email.value, save.clicked)

The constraint underneath both paths is stated plainly: neither accepts native Streamlit elements as React children. So you cannot hand a st.markdown or a st.column into a shadcn Card and expect it to compose.

For most applications that is fine. For anyone whose existing Streamlit layout depends on interleaving native widgets inside a custom container, it is the line to check before starting.

Values come back from components, labels stay on screen

The data flow is explicit. Choice components return the original Python values rather than the display labels, so a select over a list of strings gives you back the string, not a number or an index. Presentation is customised separately with format_func, or with Choice and MenuItem objects, and those change what is drawn without changing what is returned.

That separation is what makes the elements example above readable. email.value is the value, save.clicked is a click flag, and both are read as attributes after the block closes rather than being fetched from a session dictionary by key.

Identity is the other half of the contract. The key argument is optional for ordinary calls, and the guidance is to add a stable key when components are created from a loop, when they can be reordered, or when they need identity that survives changes to their other arguments:

python
for project in projects:
    ui.checkbox(project.name, key=f"project_{project.id}")

The example derives the key from a project id rather than from the label or the position, which is the point. A key built from the label breaks when the label changes, and a key built from the loop index breaks when the list is reordered.

Charts arrived in 1.4.0 with a documented data limit

Version 1.4.0 adds six chart APIs: line, area, bar, pie, radar and radial. They accept records or a pandas DataFrame and render shadcn Cards around native shadcn tooltips and legends, with colourful light and dark palettes rather than a single chart colour scheme.

The documentation link for charts is not a formality. It is where the options and the data limits are described, which means the library draws a line between what it will attempt to plot and what it will refuse, and that line is documented rather than discovered at runtime.

The release rhythm around it is worth noting. Three releases in eight days: 1.2.2 on 2026-08-31, 1.3.0 on 2026-09-01 and 1.4.0 on 2026-09-08, with the 1.4.0 publication carrying the same timestamp as the last commit on main. That is a project iterating in public, and it argues for pinning a version rather than tracking the branch.

The component catalogue is broader than the charts. Choice and action covers select, combobox, dropdown menu, radio group, button, link button, breadcrumb and pagination. Inputs covers checkbox, input, input group, number input, textarea, one-time-code input, slider, switch, toggle and toggle group. Date and navigation covers calendar, date picker, tabs, accordion and collapsible. Display covers alert, avatar, badges, card, metric card, aspect ratio, progress, scroll area, separator, skeleton and table.

Six prototype scripts sit in the repository root

The root listing tells you how the project was built. Alongside Home.py, which the documentation app uses as its explicit router, there are six loose Python files: V2_P0.py, V2_POC.py, V2_WAVE2.py, V2_WAVE3.py, V2_WAVE4.py and V2_WAVE5.py. The names describe phases of the V2 effort rather than features, which makes them read as development scratch files that were committed rather than removed.

Packaging tells you what actually ships. The project declares include-package-data as false, then explicitly lists the package data as the nested pyproject file and everything under frontend_v2/dist. So the built frontend is included deliberately while the usual include-package-data behaviour is switched off, and a MANIFEST.in still exists at the root alongside that arrangement.

Runtime dependencies are a single package, streamlit at 1.60 or newer, with nothing else. The React 19, Tailwind CSS 4 and Base UI layers arrive as prebuilt assets rather than as Python dependencies, which is why an end user installs one package and gets a compiled frontend.

Development needs more: Node 22.20.0 and the pnpm version pinned in the frontend manifest, with scripts to watch the V2 frontend build, run the documentation app, and verify the release source, plus a pytest run over the v2 tests.

The styling boundary is a written rule, not a preference

The most opinionated sentence in the README concerns who owns appearance. shadcn owns the component palette, radius, typography, focus rings and interaction styling. Streamlit provides the surrounding light and dark colour scheme, language and direction. And the package does not restyle shadcn to look like Streamlit.

That is a deliberate design position rather than an omission, and the development section enforces it: changes to shadcn default styles, including overrides in adapters or in documentation CSS, require explicit user permission.

The four-layer architecture explains how the boundary is maintained. A Python API call goes to a Streamlit Components V2 adapter, which renders an owned generated shadcn component, which sits on a Base UI behaviour primitive. Each layer has one job, and restyling would mean editing the generated component, which is exactly what the permission rule guards.

It also means the visual result will not match the surrounding Streamlit app, and an application that has been carefully themed will show the difference at the boundary. That is the tradeoff being made explicitly rather than by accident.

Licensing is MIT with the LICENSE file referenced from the README, and the reference set is three links: Streamlit, shadcn/ui and Base UI.

Editorial conclusion

streamlit-shadcn-ui fits a Streamlit team that wants a real component library instead of st.selectbox and st.button, and that has upgraded to Streamlit 1.60 or newer. It does not fit an application that still relies on the 0.1.x iframe implementation, because that path is not shipped in the 1.0 package. Verify four things before you commit. Read the migration list rather than guessing, since grouped checkboxes, the card context manager and raw session-state transport dictionaries all changed shape. Decide whether you accept the styling boundary, because the project states that it does not restyle shadcn toward Streamlit and requires explicit permission to override shadcn defaults even in documentation CSS. Check your Streamlit version, since the floor is 1.60 and the package depends on nothing else at runtime. And check what is inside your wheel before deploying, since the React and Tailwind layers ship as built assets under frontend_v2 and the root still carries prototype scripts. Version 1.4.0 was released on 2026-09-08, the same day as the last commit on main, following 1.3.0 on 2026-09-01 and 1.2.2 on 2026-08-31.

Frequently asked questions

What does streamlit-shadcn-ui add to Streamlit?

It provides shadcn components implemented on Streamlit Components V2, including selects, comboboxes, dropdown menus, dialogs, popovers, date pickers, tabs, cards, badges and six chart types. Components render without an iframe, and anchored or modal overlays use the browser top layer so they are not clipped by the Streamlit layout.

What does streamlit-shadcn-ui require to install?

Python 3.10 or newer and Streamlit 1.60 or newer, installed with pip install streamlit-shadcn-ui. The package declares streamlit as its only runtime dependency, because the React 19, Tailwind CSS 4 and Base UI layers ship as prebuilt assets under frontend_v2 rather than as Python packages.

How do I upgrade from streamlit-shadcn-ui 0.1.x?

Version 1.0 is the V2 API only. The V1 iframe implementation and the streamlit_shadcn_ui.v1 namespace are not shipped, so applications that cannot migrate should stay on the last 0.1.x release. Changes include button(text=) becoming button(label=), grouped checkboxes becoming scalar composition, and component return values replacing raw session-state transport dictionaries.

Why does streamlit-shadcn-ui ask for a key argument?

Keys are optional for ordinary calls, but a component created inside a loop, one that can be reordered, or one that needs identity surviving changes to its other arguments needs a stable key. The documented example derives the key from a project id rather than from the label or the loop position.

Can streamlit-shadcn-ui components be restyled to match Streamlit?

The project states the opposite is intentional: shadcn owns the palette, radius, typography, focus rings and interaction styling while Streamlit supplies the surrounding colour scheme, language and direction, and the package does not restyle shadcn. Changes to shadcn default styles, including overrides in adapters or documentation CSS, require explicit user permission.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/observedobserver-streamlit-shadcn-ui.svg)](https://hysenlabs.com/projects/observedobserver-streamlit-shadcn-ui)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/observedobserver-streamlit-shadcn-ui.svg)](https://hysenlabs.com/projects/observedobserver-streamlit-shadcn-ui)

Was this useful?