Build responsive apps based on different screen features

Perhaps you have been in this situation. Busy building your streamlit app on you 17inch wide laptop. Everything looks fly:

Then you realise users might access your app from their tablet or phone. So you open up your developer tools not expecting much of a change only to see:

Perhaps if I had a way to tell what the screen size is and when it changes to show different types of an app. You could use st_javascript to get the screen width size at the start of your app using some javascript or streamlit_js_eval which seems much simpler. In addition to these options, I created a library that pulls some important screen data based on dynamic changes to the screen that looks like:
image

You can use these to conditionally render different formats/version of your app.

The demo app is here, the github repo and the pypi.

Happy streamliting.

13 Likes

nicely done @Rushmore it will surely be useful in my next upcoming project

1 Like

Hi @Rushmore,

I tried your library today (after pip installing the recommended 3 packages). Just wanted to let you know a few points:

The code I used was:

import streamlit as st
from st_screen_stats import ScreenData     # pip install streamlit-screen-stats

screenD = ScreenData(setTimeout=1000)
screen_stats = screenD.st_screen_data_window_top()
st.info(f"Scn Width: {screen_stats['screen']['width']}")

Running the above script, it gave the following error on the 1st pass:

And immediately thereafter, the error disappeared and gave me the desired result (on the 2nd pass)

It happens very quickly and can easily be bypassed with a try-except enclosure. Thought, I’d mention this to you, in case you want to try and investigate the source of the error (which only happens on the 1st pass).

Other than that, this is super useful as a streamlit app (with multiple st.columns) display weirdly on a mobile.

Another suggestion: besides the screen stats, there are other end user parameters which will be very helpful to collect: IP address / location / local time / etc., for the purpose of user / country / local time / etc. identification, so that suitable code can be written for those user categories. Maybe, your library can cover an entire end user profile, instead of just end user screen stats.

Thank you for this library. Will start using it henceforth. :slight_smile:

Cheers

Hey Shawn, glad you liked the component.

Regarding your points:

  1. Yeah, so this part of the component waits 1 second before delivering the data. The setTimeout param at 1000 is telling the component to wait 1 second before delivering data to the component. The best thing to do is to set a time.sleep for 2 seconds or like you said do a try and except block. I use a while loop, to wait for data from the component before running the rest of the app albeit I use the other class from the component WindowQuerySize which delivers data only when the window size changes. It works kinda like CSS’ @media screen queries.
  2. Thanks for the recommendations, will work on these other attributes and update the package.

Thanks for the feedback, its always appreciated :smiley:

Hey @Shawn_Pereira

Finally got round to doing some debugging.

  1. Placed a timeout at the python level to stop the error at the 1st pass/run of the component. It adds 1 second to the setTimeout parameter a user adds.
  2. Created browser-detection-engine here to get more details from user browser. Will add more data as I too need it for my app.
1 Like

More updates***

  • Have added a callback for on_change.
    Reason:
    • The component works such that on first mount/app load it sends the value to streamlit.
    • Subsequent app reloads will most likely return None or the value selected for the default parameter unless there has been a change in the size of the screen.
    • To make sure you only get a value when there has been a legitimate change in the screen width/size, utilise the on_change parameter to set the results on change of the screen width to a session_state value.
from st_screen_stats import ScreenData, WindowQuerySize, WindowQueryHelper

helper_screen_stats = WindowQueryHelper()

def onScreenSizeChange(updated_screen, component_function_):

    st.session_state[updated_screen] = st.session_state[component_function_]

if "large_screen_size_" not in st.session_state:
    st.session_state["large_screen_size_"] = helper_screen_stats.window_range_width(min_width=1000, max_width=1100, key="lg_screen")
else:
    helper_screen_stats.window_range_width(min_width=1000, max_width=1100, on_change=onScreenSizeChange, args=("large_screen_size_", "lg_screen_post_first_mount",) key="lg_screen_post_first_mount")

if st.session_state["large_screen_size_"]["status"]:
    st.write("rest of code here")

# Also works for `ScreenData` and `WindowQuerySize` classes

1 Like

Okay, fixed a few things to make component more reliable.

  • I noticed whilst using the component that I would regularly get None/null arguments because I was using the window.top to get the width/screen dimensions. I have since changed this to window.parent which has proved to be more reliable.

Latest update 0.0.69.

2 Likes

Latest update: 0.0.75

  • no need for pause or time.sleep. Automatically waits for result from browser before rendering result into your app.
1 Like

import streamlit as st
from st_screen_stats import ScreenData, WindowQueryHelper
import streamlit_highcharts as hchart

Get screen data

screenD = ScreenData(setTimeout=1000)
screen_d = screenD.st_screen_data()

Store screen width in session state

screen_width = screen_d.get(“width”, 1200) # Default to 1200px if not available

Helper to check screen width range (unique keys assigned)

helper_screen_stats = WindowQueryHelper()
is_mobile = helper_screen_stats.maximum_window_size(max_width=480, key=“max_width_480”)[“status”]
is_tablet = helper_screen_stats.window_range_width(min_width=481, max_width=768, key=“range_width_481_768”)[“status”]
is_laptop = helper_screen_stats.window_range_width(min_width=769, max_width=1024, key=“range_width_769_1024”)[“status”]
is_large_screen = helper_screen_stats.minimum_window_size(min_width=1025, key=“min_width_1025”)[“status”]

Define column layout based on screen size

if is_mobile:
kpi_columns = 1
chart_columns = 1
elif is_tablet:
kpi_columns = 3
chart_columns = 2
elif is_laptop:
kpi_columns = 4
chart_columns = 2
else:
kpi_columns = 6
chart_columns = 4

Dummy KPI data

kpi_data = [
(“Revenue”, “$120K”), (“Orders”, “450”), (“Customers”, “300”),
(“Profit”, “$30K”), (“Growth”, “15%”), (“Retention”, “80%”)
]

Dummy Chart Data

chart_options = {
“chart”: {“type”: “column”},
“title”: {“text”: “Sales Overview”},
“xAxis”: {“categories”: [“Jan”, “Feb”, “Mar”, “Apr”]},
“series”: [{“name”: “Sales”, “data”: [100, 200, 150, 300]}]
}

UI Styling

st.markdown(“”"

* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { background-color: #0f0f0f !important; color: #00ffaa; font-family: ‘Poppins’, sans-serif; }
.title { font-size: 25px; font-weight: bold; text-align: center; border-bottom: 2px solid #00ffaa; padding: 10px; }
.row { display: flex; flex-wrap: wrap; justify-content: space-between; padding: 10px; }
.card { background: #0f0f0f; border: 2px solid #00ffaa; box-shadow: 0 0 10px #00ffaa; padding: 10px; border-radius: 8px; text-align: center; flex: 1; margin: 5px; }

“”", unsafe_allow_html=True)

Title

st.markdown(‘

Dashboard
’, unsafe_allow_html=True)

KPI Container (Row 1)

st.markdown(‘

’, unsafe_allow_html=True)
cols = st.columns(kpi_columns)
for i in range(kpi_columns):
with cols[i]:
if i < len(kpi_data):
name, value = kpi_data[i]
st.markdown(f’
{name}
{value}
‘, unsafe_allow_html=True)
st.markdown(’
', unsafe_allow_html=True)

Row 2 - Charts

st.markdown(‘

’, unsafe_allow_html=True)
cols = st.columns(chart_columns)
for i in range(chart_columns):
with cols[i]:
hchart.streamlit_highcharts(chart_options, key=f"chart_row2_{i}")
st.markdown(‘
’, unsafe_allow_html=True)

Row 3 - Charts

st.markdown(‘

’, unsafe_allow_html=True)
cols = st.columns(chart_columns)
for i in range(chart_columns):
with cols[i]:
hchart.streamlit_highcharts(chart_options, key=f"chart_row3_{i}")
st.markdown(‘
’, unsafe_allow_html=True)

I used it so that the media query gets applied to my streamlit code. But the problem is that I have row1 containing 6 columns. I want my dashboard to have a responsive grid like format so that if I change the screen size the columns inside my row1 get shifted to the next row. For example if it is a laptop then6 column in one row, if tablet then 3 in one horizontal row the rest3 gets to the next row , if mobile then one column in one row only.

how can we achieve using this?

1 Like

@Bugzzz4u

I just recently went through this with my app. I have re-written you code below and its should work out as intended.

import streamlit as st 
import streamlit as st
from st_screen_stats import WindowQueryHelper
import streamlit_highcharts as hchart

st.set_page_config(
    layout="wide"
)



with st.container(height=1, border=False):
    helper_screen_stats = WindowQueryHelper()
    is_mobile = helper_screen_stats.maximum_window_size(max_width=480, key="max_width_480")["status"]
    is_tablet = helper_screen_stats.window_range_width(min_width=481, max_width=768, key="range_width_481_768")["status"]
    is_laptop = helper_screen_stats.window_range_width(min_width=769, max_width=1024, key="range_width_769_1024")["status"]
    is_large_screen = helper_screen_stats.minimum_window_size(min_width=1025, key="min_width_1025")["status"] 

if is_mobile:
    kpi_columns = 1
    number_of_kpi_per_row = 1
    chart_columns = 1
    number_of_charts_per_row = 1
    total_number_of_charts = 4
    
elif is_tablet:
    kpi_columns = 3
    number_of_kpi_per_row = 2
    chart_columns = 2 
    number_of_charts_per_row = 2
    total_number_of_charts = 4
elif is_laptop:
    kpi_columns = 4
    number_of_kpi_per_row = 2
    chart_columns = 2
    number_of_charts_per_row = 2
    total_number_of_charts = 4
else:
    kpi_columns = 6
    number_of_kpi_per_row = 6
    chart_columns = 4 
    number_of_charts_per_row = 4
    total_number_of_charts = 4

kpi_data = [
    ("Revenue", "$120K"), ("Orders", "450"), ("Customers", "300"),
    ("Profit", "$30K"), ("Growth", "15%"), ("Retention", "80%")
]

chart_options = {
    "chart": {"type": "column"},
    "title": {"text": "Sales Overview"},
    "xAxis": {"categories": ["Jan", "Feb", "Mar", "Apr"]},
    "series": [{"name": "Sales", "data": [100, 200, 150, 300]}]
}

st.markdown('''
    Dashboard
''', unsafe_allow_html=True)


st.write("")


cols = st.columns(kpi_columns)
for i in range(len(kpi_data)):
    index = i % number_of_kpi_per_row 
    with cols[index]:
        if i < len(kpi_data):
            name, value = kpi_data[i]
            st.metric(label=name, value=value) 
        

st.write("")
cols = st.columns(chart_columns)
for i in range(total_number_of_charts):
    index = i % number_of_charts_per_row
    with cols[index]:
        hchart.streamlit_highcharts(chart_options, key=f"chart_row2_{i}")