Wei works in data at a logistics company. His title says data analyst. The actual work runs from writing SQL to cleaning customs declarations to pulling the monthly numbers for finance. He knows Python. His front end experience stopped in college, when he edited an HTML template somebody else had written.
Last October the operations director pulled him into a meeting room. Customers were asking for freight quotes, and sales had fallen into the habit of messaging Wei on WeChat. He would run a script by hand, twenty-odd times a day. The director wanted a web page. Sales would type in weight, volume and destination, the page would return a price, and ideally it would export a quote sheet the salesperson could forward to the customer.
Wei went home and searched for a night. The phrase “build a web app with pure Python” took him to the Reflex site (https://reflex.dev). The homepage was persuasive. Front end and back end both in Python, no JavaScript required. He ran pip install reflex, then reflex init, and stopped there. Initialization pulls front end dependencies. The company network did not get along with the npm registry, and the first attempt ran for twenty minutes before exiting with an error. The next day he switched to a mirror and got it running. A default page appeared at localhost:3000. He thought he was finished.
The real problem arrived on day three. He wanted a dropdown on the page, with its options read from a database. The docs told him to put the data in a State class, use a computed var, and pay attention to yield and async. Copying the example worked. Changing one field broke it, and the error was buried in the front end compile log. An afternoon went by and he still could not explain why a dropdown needed that much code.
That evening he stopped and asked himself a more basic question. What am I actually building?
A page, or something that keeps running
These four frameworks get dropped into the same comparison table all the time. They do not understand the phrase “web page” the same way.
For Streamlit, you have a script. It gives you a web shell, and every time someone touches something, it reruns your script from the top. For Gradio, you have a function. It gives you a set of input boxes and an output box, and wraps them. For NiceGUI, you build a tree of interface objects on the server, and the browser is a display for that tree. For Reflex, you write components in Python, it compiles them into a real front end application, and pairs that with a Python backend.
Those four readings decide almost everything downstream, including the parts you only notice in month three.
The table below is not sorted by performance or feature count. Neither matters much when the thing you are building is an internal tool. It is sorted by the five questions that actually stop people: whether the license lets you use it commercially, whether you have to run the deployment yourself, how work is split between front end and back end, where it is least painful, and how long the first version takes.
| Streamlit | Gradio | NiceGUI | Reflex | |
|---|---|---|---|---|
| License | Apache 2.0 | Apache 2.0 | MIT | Framework Apache 2.0, cloud service billed separately |
| Deployment | streamlit run, or hosted on Community Cloud |
python app.py; add share=True for a temporary public URL; or Hugging Face Spaces |
python main.py, Docker if you want it, or as a desktop window |
reflex run locally, reflex deploy to the official cloud, or package it yourself |
| Front end and back end | Server reruns the whole script and returns the page | Front end is a set of ready components, events call your function directly | Backend holds the interface objects, events travel back over a WebSocket | Python component tree compiles to a React front end, state lives in Python classes |
| Best fit | Reports, dashboards, parameter pages | Model demos, single function demos, chat interfaces | Always-on consoles, instrument panels, tools that refresh in real time | Multi-page products with login that need to last |
| Learning cost | Half a day | A demo in an hour | One to three days | A week and up, depending on how much front end vocabulary you need |
Read down a column and it becomes clear these are not four candidates for one job. Streamlit and Gradio sell speed to a visible result. NiceGUI sells control. Reflex sells a path to something that behaves like a product. The expensive mistake is not failing to build the thing. It is building it, shipping it, and discovering three months later that you cannot move it.
Streamlit: a script with a page around it, at the cost of rerunning
A typical Streamlit app is a daily number someone in operations checks before nine. The person who built it worked in merchandising. Thirty lines or so: read a table, show it with st.dataframe, pick a date range with st.slider, draw a chart with st.bar_chart. She had never learned web development. She typed streamlit run, a browser opened by itself, and her manager was using it that afternoon.
The model is simple to the point of being blunt. The script is one function, and any interaction executes it from the top. In read-only territory this costs nothing. You do not need to understand state, or rendering. Change a line, save the file, the page reloads by itself. The README calls it live editing.
The trouble comes from the same place. Rerunning the script means every click walks the code above it again. When Wei built his first quote tool in Streamlit, the price table was read from disk on every interaction, so he added @st.cache_data. Later he wanted a button that generated a quote number. He clicked it, the page reran, and the counter went back to zero. The number had to live in st.session_state. All of this is documented. Each instance is a small reminder that you are negotiating with a model that has no real state of its own.
Streamlit is Apache 2.0, and the project offers free hosting on Community Cloud (https://streamlit.io/cloud). It has tens of thousands of stars on GitHub (https://github.com/streamlit/streamlit). One detail is easy to miss. The README states that the team has paused accepting pull requests from outside the maintainers. That does not stop you from using it. It does tell you where the pace comes from: the core team. When you hit a deep bug, your options are usually to wait or to route around it.
If your page looks like a report with a few filters bolted on, Streamlit is the shortest of these four roads. The moment the page grows a sequence, where one action depends on the last, it starts working against you.
Gradio: built for “let people try it”
Gradio was never aimed at internal systems. It was aimed at demonstrations.
An engineer working on image segmentation needed to show a client what the model did. The client did not believe the slides and wanted to try it. He wrote twenty lines: gr.Interface(fn=predict, inputs=gr.Image(), outputs=gr.Image()), then demo.launch(share=True). Seconds later the terminal printed an address ending in *.gradio.live. He sent it over. The client uploaded a photo in a browser and saw a result. The computation was still running on the engineer’s laptop.
That share=True is the sharpest thing in the project. It compresses a deployment problem into one argument. The docs list a full set of built-in components covering text, images, audio, video and dataframes, which is most of what a model needs on both ends (https://www.gradio.app/docs). When the layout gets complicated, gr.Blocks takes over. For conversation there is gr.ChatInterface. There is also gradio_client, which lets other people call your app from code and treat it as an endpoint.
Hosting has two ready-made routes: the temporary link, or Hugging Face Spaces, which runs it for free (https://huggingface.co/spaces). For a researcher this is close to ideal. No server to buy, no domain to configure, no HTTPS certificate to renew.
The cost is baked into the interface DNA. Gradio components are designed for input goes in, result comes out. Ask it for an admin backend with a sidebar, several pages and user permissions and you will fight it the whole way. It can be done. The code will read like something that was meant for a demo. The license is Apache 2.0, so nothing there blocks commercial use.
If the requirement contains the sentence “let people outside try it,” Gradio is the answer and you can stop deliberating. If the requirement is “our own people will use this every day,” it is probably the wrong tool.
NiceGUI: an interface for a machine that never shuts down
NiceGUI gets less discussion than the others, but its positioning is clear enough that one look tells you who it serves.
People in custom automation run into the same scene. A labeling machine ships to a food factory with a fifteen inch touchscreen bolted next to it. The operator changes speed, changes spacing, checks today’s count. The old interface was written in PyQt by somebody who has since left. Changing one parameter meant rebuilding a package and installing it on the industrial PC. After the switch to NiceGUI, the interface is a single Python file, the process runs on that PC, and the screen opens it in a browser or as a NiceGUI desktop window. To adjust a parameter, you connect remotely, edit two lines, restart the process. Two minutes.
That is the use case the project describes for itself: micro web apps, dashboards, robotics projects, smart home controls (https://nicegui.io). The stack is FastAPI in the back, Vue and Quasar in the front, and a long-lived socket.io connection in between. The README spells the arrangement out. The ui.button(...) and ui.label(...) you write are real objects on the server. The DOM in the browser is a projection of them.
Two capabilities fall out of that model and are hard to find elsewhere. The first is real time. Timers are built in, and the project says the shortest interval is ten milliseconds, which is enough for serial ports, PLCs and sensors that keep changing values. The second is the desktop window. No browser needed, which matters on a factory floor without network access.
The author wrote a note in the README about motivation: they liked Streamlit but found its approach to state had too much magic in it. That sentence explains the whole trade. What you get are explicit objects and explicit event binding. A button click is a Python function. Whatever object the function changes, that part of the page changes. No full page rerun, and therefore none of the “why did my variable reset” conversations.
The cost sits in community size. NiceGUI is maintained by a German company called Zauberzeug under the MIT license (https://github.com/zauberzeug/nicegui). Its community is smaller than the ones around Streamlit or Gradio. When you hit something obscure, you have the issue tracker and the bundled examples. The engineering basics are covered: pytest support, a Docker image, a conda package. The plugin ecosystem is nowhere near as busy as Streamlit’s.
If the page stays open all day, the data keeps moving, and the person using it is standing next to the equipment, NiceGUI is the closest match here. It is not trying to build you a website. It is building you a control panel.
Reflex: aimed at something you would call a product
Wei came back to Reflex, because by then he knew what he wanted was not a report.
His tool ended up like this. Sales logs in, picks a customer, enters the parameters, sees previous quotes, confirms and generates a PDF. His manager sees the approval status of every quote. That is not a script. It is a small system. Streamlit can host it, but every page forces the same question about where state lives. Gradio does not fit at all. NiceGUI can do it, except login, routing and multiple pages are yours to assemble.
Reflex sits exactly there. It compiles your Python component tree into a real front end. You write rx.vstack, rx.input, rx.button, and what runs in the browser is an ordinary front end, with routing, static assets and page titles. On the server, state is a subclass of rx.State, its fields are normal Python type annotations, and changes propagate to the client.
The ceiling that comes with this design is high. You are not limited by rerunning a script, the way Streamlit is, and you are not constrained by the shape of a component the way Gradio is. Twenty pages, permissions, custom styling: it can carry all of it.
The cost is concentrated in the first three days. reflex init installs a front end toolchain, which is the wall Wei hit on his first attempt. Conceptually there is more to learn too: State, event handlers, computed vars, component compilation, var operations. You have to accept that you are writing Python bindings against a front end framework, not writing a script.
Commercially the line is drawn clearly. The framework is Apache 2.0 (https://github.com/reflex-dev/reflex), and self-hosting costs nothing. The official cloud starts with a free plan at $0, limited usage and one published app. Pro starts at $25 a month, and the monthly allowance is shared between AI generation and app hosting (https://reflex.dev/pricing). For an individual that is not cheap. For a company paying an internal tool’s bill, $25 a month is usually less than two more weeks of Wei’s time.
When the thing needs accounts, several pages, and a handoff to other people who will keep using it, Reflex’s extra complexity earns its price. For a report, it is a sledgehammer, and one that takes a while to sharpen.
Choose by the shape of the interaction, not the star count
By this point the choice is less a decision than a description. You only have to answer one question: once a user opens the page, how many actions do they take, and do those actions depend on each other?
One action, fill in and read the result. That is Gradio. gr.Interface was built for that shape.
Many actions, each one a filter followed by a look, independent of the others. Streamlit. Its rerun model costs nothing in read-only territory, and what you save is the entire state layer.
Actions that affect each other, a page that stays open, data that moves on its own. NiceGUI. What you need is objects and events, not a script.
Actions that form a sequence, with roles, permissions and separate pages. Reflex. What you need is an application framework that happens to be written in Python.
Star counts are close to useless in this judgment. Streamlit has the most because its floor is the lowest. Gradio has plenty because the population of people building models is large. Stars record how many people tried something. They say nothing about how many stayed, and less about whether it fits your case.
There is another variable people skip: how long the thing has to live. A one-off analysis page written in Streamlit over two days and thrown away afterward is the optimal outcome. An internal system that has to survive two years will repay a slower first version by month six. Wei shipped version one in Streamlit in a week, and finance was happy. Three months later the requirement changed to “customers log in and check their own quotes,” and he rewrote it in Reflex. Those three months were not wasted, because the business logic was plain Python functions and only the interface layer moved.
That is the kindness all four projects share. Your business code is ordinary Python, and the interface layer is replaceable. What locks you in is not the framework. It is putting business logic inside interface callbacks.
What Wei handed over
He handed over two things.
For finance, a Streamlit page of forty-seven lines that reads a table synced overnight, lets you pick a month, prints a summary, and downloads an Excel file. It went live the same afternoon. Finance sent a thumbs up in the group chat.
For sales, a Reflex application: login, customer selection, price calculation, a PDF, and a record of who changed what. It took three weeks, two days of which went to the npm mirror. After launch, sales stopped shouting at him on WeChat and started filing requests in the system. When something went wrong, he could see who changed what, and at which step.
His summary afterward was that he thought he was choosing a framework, and by the end he was choosing whose job the thing would be. The Streamlit page belongs to finance. The Reflex app belongs to him.
That is probably the most practical standard in this whole comparison. If you are willing to sit with it until ten at night, pick the one that can grow into something large. If you need to hand it over tomorrow morning, pick the one that runs in half an hour.
Wei is a composite character, assembled from situations that come up often enough to be recognizable. Licensing, hosting and pricing details come from the project repositories and sites: https://github.com/reflex-dev/reflex, https://github.com/streamlit/streamlit, https://github.com/zauberzeug/nicegui, https://github.com/gradio-app/gradio, https://reflex.dev/pricing, https://www.gradio.app/docs, https://nicegui.io, https://streamlit.io/cloud, https://huggingface.co/spaces.
[//]: # (seo-title: Beyond Reflex: Streamlit, NiceGUI and Gradio for Python Web Apps | seo-desc: Compare Streamlit, Gradio, NiceGUI and Reflex on licensing, deployment, state model and learning cost, then pick one for an internal Python app. | seo-kw: Reflex alternatives, Streamlit, NiceGUI, Gradio, Python web apps, pure Python frontend | slug: reflex-alternatives-streamlit-nicegui-gradio-2026-en)



