How to Create Nested Columns with Streamlit

Introduction
Since 2022, Streamlit has been one of my favourite tools for creating dynamic dashboards. I like its simplicity and effectiveness in creating interactive dashboards in a short time. However, despite its many brilliant features, Streamlit has some limitations in the layout arrangement.
Streamlit offers different layout options, including columns, expanders, containers, the sidebar and tabs. Columns are the most common and ideal way to create dynamic layouts which will hold different visualizations. With columns, you can divide your screen into a table or grid-like format, where each column holds a status element, widget or chart. The column feature far outperforms the other layout components regarding screen flexibility and arrangement.
However, what hinders the Streamlit column feature from reaching its full potential is the limitation Streamlit has on nested columns. Streamlit columns are painstakingly one-dimensional and one-directional. Columns go only one way at a time. This could be vertically or horizontally.

The inclusion of nested columns in Streamlit comes with an embargo. Nested columns are only allowed up to one level and from what I gather on the forum, this is to prevent users from creating weird layouts. I don’t know what ‘weird layouts’ means to Streamlit, but there are many ways they could go about offering more screen flexibility while maintaining an intuitive interface — they could copy from CSS and give us something like the CSS grid or the flexbox grid.
This problem has also been the subject of many discussions on Streamlit forums as more mid to advanced-level data visualisations require a nested grid format where columns can be as wide as possible, with other columns — to an nth level — nested in an outer one and having as many rows as needed. Users may also need to sectionalize different parts of the dashboard to have data or metrics that are similar on one side while everything else is on the other side.
In this article, I’ll show you how to overcome this problem and create nested columns within the Streamlit framework.
NB: If you are a beginner, before you continue, it would be a good idea to brush up on your knowledge of Streamlit columns.
Streamlit nested layout
To solve the nested column issues, developers have tried different techniques. Some use AgGrid, a component built by a Streamlit user with Javascript. Others try to call the columns in a loop to multiply them in a 4 by 9 matrix which will result in a 4 by 9 grid.
The method I’ll show you for creating nested columns is very straightforward and simple. You’ll only need to import a library named streamlit_nested_layout. This library was created by a streamlit user as a patch. The Github page of the project gives a simple background of how to use the library and the different levels of nesting that can be done. I’ll build a complete multi-level nested-column dashboard as I go along with this tutorial.
What we are creating
In this tutorial, we will be creating a multi-level nested column that has an outer column wrapper, two columns that hold other columns, a column for metrics, different charts, images, text and a map. Before we create our page layout, it is important to map out what the visualization will look like and what each section will hold.
I took the liberty of creating an outline of what our page layout should look like when we are done visualizing the data. All charts, elements and text will be placed as it appears in this image. I also added descriptions to the columns so they are easily recognized in the code.
NB: The code in green is the method for creating the aforementioned columns.

What We Need
Dataset
The dataset I’ll be using for this tutorial is a modified sales data for a US retail store in Excel format. You can get it here.
Importing Libraries
Then we need to install and import libraries. I will be creating charts and map visualizations in Plotly and Matplotlib, adding images to the dashboard and analysing data with Pandas. We will need to import the libraries for all that; including the Streamlit library.
But first, we need to install Streamlit nested layout with pip (I focused on this since it is the major library for this tutorial).
pip install streamlit_nested_layout
Then import it in addition to all the other libraries
import streamlit as st
import streamlit_nested_layout
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
from PIL import Image
import plotly.express as pxNext, we configure our page and write our header.
st.set_page_config(page_title = "Sales Dashboard", layout = "wide")
st.header("US Retail Superstore Sales Analysis")Then import the dataset with Excel Pandas reader.
# read in the excel dataset with pandas
df = pd.read_excel("retail superstore sales.xlsx")Analysing the data
From the image above, the first section of column 1 will hold the sum of sales, profit and shipping cost as metric 1, metric 2, and metric 3. Before we create the layout, I’d prefer to calculate that using the sum() function.
# using the sum() function to get the sum of sales, profit and shipping cost
sum = df.sum()
# to the nearest round figure
# sales is 2.3M
# profit is 286.4K
# shipping cost is 262.4KCreating the layout
This is the point where we use streamlit_nested_layout. We have an outer column which acts as a wrapper for every other column or element. Then there are two columns, labelled Column 1 and Column 2, each having different elements and charts.
We start by defining the outer, wrapper column and indicating the number of columns it should have.
#To create the outer column which has column 1 and column 2
outer_cols = st.columns([1,1], gap = 'large')st.columns([1,1]) tells the number of columns we need to create, which in this case is two. Writing ([1,1,1]) will tell the program you need three columns. And you can continue that way depending on the number of columns you want. Gap is a parameter to adjust adjacent columns with ‘small’ or ‘large’ spaces in between.
To add text or charts to any of the columns, define it using the with function, then make sure everything that needs to be displayed in it is properly aligned inside it. Think of this as writing a function where the code is clearly written inside the function block.
Indentation matters a lot here! Remember that Python is a block-level language. The outer_cols is the main block and everything is indented inside it. The two columns are indented inside the outer main column. Elements and charts for each of the columns are then indented inside their block.
The columns are populated separately. Column 1 is identified as [0], column 2 [1], and so on. We work with a column before we move to the next.
So, to add the metric to the first column:
# outer column 1 layout
with outer_cols[0]:
sales, profit, shipping = st.columns([1,1,1])
sales.metric(
label = "Total Sales",
value = "2.3M"
)
profit.metric(
label = "Total Profit",
value = "286.4K"
)
shipping.metric(
label = "Total Shipping",
value = "262.4K"
)
white_space = st.columns([1]) # create white space between the figures and c chartThe metrics here are the values of the sum we calculated earlier.
To make sure there is adequate space between the metrics and the next chart below it, I added a white space with a blank column.
Now we add another element inside column 1. This element is a bar chart that will show the sales and profit for the four regions on our dataset. To achieve this, we use Groupby to manipulate the needed columns and display them with Matplotlib.
# using groupby to extract the columns we need from the dataframe
plt.style.use("fivethirtyeight")
region_sales_profit = df.groupby('Region')[('Sales', 'Profit')].mean()
region_sales_profit = region_sales_profit.reset_index()
sales = region_sales_profit['Sales']
profit = region_sales_profit['Profit']
region = region_sales_profit['Region']
# creating a stacked bar chart
fig = plt.figure(figsize = (9,6))
plt.bar(region, sales, color = 'purple', label = 'Sales', width = 0.3)
plt.bar(region, profit, bottom = sales, color = 'orange', label = 'Profit', width = 0.3)
plt.xlabel("Region")
plt.ylabel("Sales and Profit")
plt.title("Average Sales and Profits by Region")
plt.legend()
plt.tight_layout()
st.pyplot(fig)
white_space = st.columns([1]) #another white spaceStill, in Column 1, we then create an interactive data frame that shows the top-performing states by sales. For this, I added a Streamlit sidebar component so viewers can add or remove the states they want. This column section goes directly under the bar chart.
`# add sidebar filter
with st.sidebar:
dataframe_sidebar_selection = st.multiselect('Choose State:',
state_unique, default = ['California', 'New York', 'Texas', 'Washington', 'Pennsylvania', 'Florida', 'Illinois', 'Ohio', 'Michigan', 'Virginia'])
state_dataframe = (df['State'].isin(dataframe_sidebar_selection))
#use groupby to group data into sales and profit by state
state_sales_profit = df[state_dataframe].groupby('State')[('Sales', 'Profit')].sum().sort_values(by = ['Sales'], ascending = False)
state_sales_profit = state_sales_profit.reset_index()
st.dataframe(state_sales_profit, use_container_width = True)
# THE END OF FIRST COLUMNNow, we have successfully added a chart, data frame and metric for the first column, which is column 1. We will move to populate the 2nd column, which is labelled column 2. Column 2 has a header, images, a pie chart, and a map.
To start working on column 2, we will use the ‘with’ function to define the second outer column — with outer_cols[1]— then align everything else under its block. The first element inside this block is the text headline that reads ‘Regional Managers’.
# This is the second outer column
with outer_cols[1]:
st.write("""##### Regional Managers """)After the headline, we need four images lined horizontally, representing each of the regional managers. This will require us to use the default function for calling streamlit columns to get four new nested columns inside Column 2.
img1, img2, img3, img4 = st.columns(4)
with img1:
nick_img = Image.open('Images/nick-modified.png', )
st.image(nick_img, width = 80)
st.write(""" Nick """)
with img2:
susan_img = Image.open('Images/susan-modified.png', )
st.image(susan_img, width = 80)
st.write(""" Susan """)
with img3:
george_img = Image.open('Images/george-modified.png', )
st.image(george_img, width = 80)
st.write(""" George """)
with img4:
esther_img = Image.open('Images/esther-modified.png', )
st.image(esther_img, width = 80)
st.write(""" Esther """)
white_space = st.columns([1]) # white spaceThe next section after this is the pie chart headline displayed with st.write() function. Then the pie chart figure. This pie chart will show the profit percentage distribution in each region. The chart will be plotted with Matplotlib.
# pie chart title
st.write("""##### Pie chart to show the percentage profit by each manager in their respective regions """)
# to calculate the pie chart figures
# first get a table of the regional manager and the sum of sales
r_manager = df.groupby('Regional Manager')[('Sales')].sum().reset_index()
# pie chart with matplotlib
` #fig2, ax1 = plt.subplots()
fig2 = plt.figure(figsize = (6,6))
labels = ["Esther (Central)", "George (South)", "Nick (East)", "Susan (West)"]
# labels = r_manager['Regional Manager']
value = r_manager['Sales']
colors = ['#66b3ff', '#99ff99', '#ffcc99', '#ff9999']
#explosion
explode = (0,0.1,0,0.1)
#plot
plt.pie(value, colors = colors, labels = labels, autopct = '%1.1f%%', startangle = 90, pctdistance = 0.70, explode = explode)
#draw circle
centre_circle = plt.Circle((0,0), 0.60, fc = 'white')
fig = plt.gcf()
fig.gca().add_artist(centre_circle)
#axis
#equal aspect ratio ensures the pie is drawn as a circle
plt.axis('equal')
plt.tight_layout()
st.pyplot(fig2)The last chart to be plotted is a map showing the total sales of each state in America. This will be displayed with Plotly and go directly under the pie chart.
#third nested column. A map
fig3 = px.scatter_mapbox(df, lat="Latitude", lon="Longtitude", hover_name="City", hover_data=["State", "Sales"],
color_discrete_sequence=["fuchsia"], zoom=3, height=300)
fig3.update_layout(mapbox_style="open-street-map")
fig3.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
st.plotly_chart(fig3, use_container_width = True)The final output should look like this.

CSS Styling
You may have noticed that there are colours in my text and the font is different. This is because I styled the text using CSS. You can check out this article on how to style Streamlit components with CSS.
But on a quick note, add the following to the streamlit code:
# STYLING FOR THE METRIC AND TEXT
with open('style.css')as f:
st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html = True)Then simply create a new page and name it `style.css`. Add the following code to it.
/* style text markdowns */
span.css-10trblm.e16nr0p30{
color: green;
font-weight: bold;
}
div.css-50ug3q.e16fv1kl3{
padding: 10px;
font-weight: 900;
}
div.css-1v0mbdj.etr89bj1{
border-radius: 12px;
}
div.css-1offfwp.e16nr0p33 >p{
font-weight: bold;
}Make sure the CSS file is saved in the folder as the Streamlit Python file. Run it. Now your visualization looks exactly like mine.
Conclusion
By following this tutorial you should be able to adequately nested columns within other columns inside Streamlit to display complex visualization. You should also be able to add styles to your metrics and text.
You can find the full code here on Github
Resources
- https://github.com/barrisam/US-Retail-Store-Sales-Analysis-with-Pandas-and-Streamlit
- https://readmedium.com/how-to-style-streamlit-metrics-in-custom-css-9a0f02b150da
- https://github.com/streamlit/streamlit/issues/5284
- https://docs.streamlit.io/library/api-reference/layout/st.columns
- https://earthly.dev/blog/streamlit-python-dashboard/
- https://discuss.streamlit.io/t/create-nested-columns/18807
- https://github.com/joy13975/streamlit-nested-layout
- https://github.com/PablocFonseca/streamlit-aggrid
- https://blog.streamlit.io/introducing-new-layout-options-for-streamlit/
Subscribe to my email
More content at PlainEnglish.io.
Sign up for our free weekly newsletter. Follow us on Twitter, LinkedIn, YouTube, and Discord.



