πŸš€ Streamlit Configurator – A Declarative Framework for Streamlit Apps πŸŽ›οΈ

Hello Streamlit community! :wave: I’m excited to introduce Streamlit Configurator – a declarative & modular way to build Streamlit apps. :art: Instead of manually defining every component, you can structure pages effortlessly, while enjoying powerful state management and reusable layouts.

:sparkles: Key Features

:white_check_mark: Declarative Layouts – Use ComponentConfig & PageConfig to structure pages without repetitive code.
:floppy_disk: Robust State Management – Preserve values across page refreshes with PlaceholderValue.
:recycle: Reusable Components – Define UI once and reuse it across multiple pages.
:arrows_counterclockwise: Seamless Integration – Works alongside native Streamlit calls, so you can still use custom functions.


:video_game: Try It Out

:globe_with_meridians: Live Demo: :link: st-configurator.streamlit.app

:computer: Run Locally:

git clone https://github.com/FrunkyLiu/Streamlit-Configurator.git
cd Streamlit-Configurator
streamlit run example/main.py

:hammer_and_wrench: GitHub & Feedback

:pushpin: GitHub: :link: Streamlit Configurator
:e-mail: Contact: :email: Email

I’d love to hear your thoughts! :bulb: Feel free to open an issue or submit a PR if you find a bug, have feature suggestions, or want to contribute. :raised_hands:

Thanks for checking it out! :rocket: Hope Streamlit Configurator makes your development faster & easier! :blue_heart:

3 Likes

@Frunky Looks pretty useful, and will help separate UI config and rendering from business logic. It appears that it eliminates those nasty callbacks.

Can you clarify, if I had multiple page containers, say main_left and main_right, that I can use PageRenderer.render_layout to render the provided config into those page containers? E.g. :

with main_left:
    # Assume we have a list of component configurations.
    PageRenderer().render_layout(main_left_configs)

Thanks!

@asehmi, thanks a lot for your question!

Currently, I don’t recommend directly using PageRenderer().render_layout() to render multiple independent page containers (such as your examples main_left and main_right). Unlike render_page, the render_layout method doesn’t contain page-specific configurations, and directly using it might lead to keyword generation errors.

Instead, I recommend structuring your configurations using the following approach:

main_left_container_config = ComponentConfig(
    component=st.container,
    children=main_left_configs  # main_left_configs: List[ComponentConfig]
)

...

page_config = PageConfig(
    ...
    body=[main_left_container_config, main_right_container_config]
)

Additionally, based on your naming, I suspect you might be looking for the functionality of st.columns . If that’s the case, you could also structure it like this:

main_columns_config = ComponentConfig(
    component=st.columns,
    args=(2,),
    kwargs={"border": True},
    children=[main_left_configs, main_right_configs]  # main_xxx_configs: List[ComponentConfig]
)

To be completely honest, I feel there is still considerable room for improvement in the design of PageRenderer. If you have any specific usage scenarios or suggestions about areas you find particularly awkward, please share them. Your feedback would greatly assist me in refining and improving the design!

Thanks again for your valuable input!

Quick update: I’ve just realized there’s a bug with the suggested approach I provided earlier. Currently, the length of main_left_configs must exactly match the length of main_right_configs, or it will raise an error. :sweat_smile:
I’ll address this issue ASAP.

Hi @Frunky, great project, thanks!
I want to know if you have some suggestion regarding the use the state management for the data_editor component, giving that it outputs something different than the edited dataframe itself.

For example, I mannaged to build this page. When I edit the dataframe while in-page it shows the right edits below. I can even import the edited dataframe from another page and it comes right.
But if I switch to another page and come back, it restart to initial df value.
This won’t happen with other components like st.text_input (like in your app docs), where it stays the same cross-page.

Any hints on how to preserve the edited state?
Thanks in advance

import streamlit as st
import pandas as pd

from st_configurator import ComponentConfig, PageConfig, PageRenderer
from st_configurator.placeholder import Placeholder, PlaceholderValue

df = pd.DataFrame({
        'Name': ['John', 'Mary'],
        'Age': [30, 25]
    })

class MyState(Placeholder):
    EDITED_DF = PlaceholderValue()

def edit_df(df, **kwargs):
    edited_df = st.data_editor(
        df,
        **kwargs
    )
    return edited_df

editor_config = ComponentConfig(
    component=edit_df,
    args=(df,),
    kwargs={
        'num_rows': 'dynamic'
    },
    result_key=MyState.EDITED_DF
)

show_edited_df = ComponentConfig(
    component=st.write,
    args=(MyState.EDITED_DF,)
)

page_config = PageConfig(
    page_tag="My Streamlit App",
    body=[
        editor_config,
        show_edited_df
    ],
)

# Render page
PageRenderer().render_page(page_config)

Hi @Juan_Victoriano , thanks for your question!

Unfortunately, I’m encountering the same challenge you’ve described. Initially, the design of st_configurator assumed that the value saved by a Streamlit component using a key would be identical to the value returned by that component. However, the behavior of st.data_editor clearly deviates from this assumptionβ€”the returned value differs from what Streamlit stores internally with its key mechanism.

Since the demonstrated approach relies heavily on this assumption, it becomes challenging to achieve consistent cross-page state management with st.data_editor in the current design.

I’ll try to explore this further and see if I can find an alternative solution or provide improvements in the design to better support such scenarios. Meanwhile, if you have additional insights or suggestions based on your experience, I’d appreciate hearing them!

Thanks again for bringing this issue up!