Shiny for Python and SCSS: How to Style Your Dashboards with SASS
There's a more powerful design kid on the block with the name of SASS (Syntactically Awesome Style Sheets), and it simplifies how app styles are written and maintained. Today you'll learn all about it in the context of Shiny for Python - also a new arrival to the web development scene.
To be more precise, we'll write a Shiny for Python SCSS dashboard that displays earthquake data, both in table and chart form. The user can filter out quakes below a certain magnitude and can rename the title of the chart. It's simple but will be enough to demonstrate how SASS and Shiny for Python work.
<blockquote>Want to dive deeper into Shiny for Python capabilities? <a href="https://appsilon.com/pyshiny-demo/" target="_blank" rel="noopener">Here's an entire demo written by an R Shiny professional</a>.</blockquote>
Table of contents:
<ul><li><a href="#dashboard">Let's Write a Basic Shiny for Python Dashboard</a></li><li><a href="#assets">How to Add a Static Asset Directory to Shiny</a></li><li><a href="#styles">How to Compile SCSS in Shiny for Python</a></li><li><a href="#summary">Summary of Shiny for Python SCSS</a></li></ul>
<hr />
<h2 id="dashboard">Let's Write a Basic Shiny for Python Dashboard</h2>
To explore SCSS stylings, we have to write the dashboard first. Download the <a href="https://www.kaggle.com/datasets/mathurinache/quakes" target="_blank" rel="nofollow noopener">Quakes dataset</a> from Kaggle and create an <code>app.py</code> file.
Regarding the UI user inputs, the app will allow users to filter out the quakes based on the minimum magnitude and change the chart title. The app will display a table of 10 sample rows from the dataset and a histogram showing the distribution of the quake magnitudes.
We won't dive deep into the code in this article, as the point is to discuss SCSS. Here's the entire snippet:
<pre><code class="language-python">import pandas as pd
import matplotlib.pyplot as plt
from IPython import display
from shiny import App, ui, render, reactive
display.set_matplotlib_formats("svg")
plt.rcParams["axes.spines.top"] = False
plt.rcParams["axes.spines.right"] = False
<br>
df = pd.read_csv("quake.csv")
<br>
app_ui = ui.page_fluid(
ui.tags.h3("Earthquakes dataset visualizer", class_="app-heading"),
ui.tags.div(class_="input_container", children=[
ui.input_slider(
id="slider_min_magnitude",
label="Minimum magnitude:",
min=df["richter"].min(),
max=df["richter"].max(),
value=df["richter"].min(),
step=0.1
),
ui.input_text(
id="in_histogram_title",
label="Histogram title",
value="Distribution of the Richter value"
)
]),
# Table
ui.tags.div(class_="card", children=[
ui.tags.p("Sample of 10 earthquakes", class_="card_title"),
ui.output_table(id="quake_table", class_="quake_table")
]),
# Histogram
ui.tags.div(class_="card", children=[
ui.output_text(id="out_histogram_title"),
ui.output_plot(id="quake_histogram")
])
)
<br>
def server(input, output, session):
@reactive.Calc
def data():
return df[df["richter"] >= input.slider_min_magnitude()]
<br> @output
@render.table
def quake_table():
return data().sample(10, replace=True)
<br> @output
@render.text
def out_histogram_title():
return input.in_histogram_title()
<br> @output
@render.plot
def quake_histogram():
fig, ax = plt.subplots()
ax.hist(data()["richter"], ec="#000000", color="#0099F8")
return fig
<br>
app = App(ui=app_ui, server=server)</code></pre>
You can launch the app by running the following shell command:
<pre><code class="language-shell">shiny run --reload app.py</code></pre>
Here's what it looks like:
<img class="size-full wp-image-15608" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d2b075b180b12ad38de1_2e92464f_1-1.webp" alt="Image 1 - Quakes Shiny for Python dashboard" width="4336" height="2656" /> Image 1 - Quakes Shiny for Python dashboard
You can play around with the input controls - here's an example:
<img class="size-full wp-image-15610" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d2b15591af1433a8610d_4f6088ec_2-1.webp" alt="Image 2 - Quakes Shiny for Python dashboard (2)" width="4336" height="2656" /> Image 2 - Quakes Shiny for Python dashboard (2)
And that's almost all we need to start styling the dashboard. The only prerequisite left is creating and linking a static assets directory. Let's cover that next.
<h2 id="assets">How to Add a Static Asset Directory to Shiny</h2>
The static files directory is used to host your images, scripts, styles, and everything else related to the app. A common practice is to create a <code>www</code> folder and put everything inside it. Create one yourself and make sure it's in the same folder as <code>app.py</code>.
To connect Shiny for Python dashboard with the <code>www</code> folder, you'll have to do the following:
<ul><li>Import the <code>Path</code> class from the <code>pathlib</code> library.</li><li>Create a variable <code>www_dir</code> that will store an absolute path to the <code>www</code> directory.</li><li>Inside <code>App()</code>, specify an additional parameter <code>static_assets</code> and set it to <code>www_dir</code>.</li></ul>
Here's what your <code>app.py</code> file should look like after implementing the changes:
<pre><code class="language-python">import pandas as pd
import matplotlib.pyplot as plt
from pathlib import Path
from IPython import display
from shiny import App, ui, render, reactive
display.set_matplotlib_formats("svg")
plt.rcParams["axes.spines.top"] = False
plt.rcParams["axes.spines.right"] = False
<br>
df = pd.read_csv("quake.csv")
<br>
app_ui = ui.page_fluid(
ui.tags.head(
ui.tags.link(rel="stylesheet", href="styles.css")
),
ui.tags.h3("Earthquakes dataset visualizer", class_="app-heading"),
ui.tags.div(class_="input_container", children=[
ui.input_slider( ... ),
ui.input_text( ... )
]),
# Table
ui.tags.div(class_="card", children=[ ... ]),
# Histogram
ui.tags.div(class_="card", children=[ ... ])
)
<br>
def server(input, output, session):
...
<br>
www_dir = Path(__file__).parent / "www"
app = App(ui=app_ui, server=server, static_assets=www_dir)</code></pre>
The dots <code>...</code> are here just to make the snippet shorter - don't actually replace the code with them.
Now we have everything we need to start writing some SCSS code.
<h2 id="styles">How to Compile SCSS in Shiny for Python</h2>
At the same level where your <code>app.py</code> is, create a new directory <code>styles</code> and two files inside it: <code>_variables.scss</code> and <code>styles.scss</code>.
One of the neat things about SCSS is that you can separate the stylings into fragments (multiple files), and then include them in one main file. That's just what we did with the variables.
This file will contain links to our fonts and declarations for different colors, sizes, and similar - everything you want to reuse in multiple places:
<pre><code class="language-css">@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap');
<br>$font-stack: Poppins, sans-serif;
$bg-color: #F4F4F4;
$card-color: #FFFFFF;
$border-color: #EEEEEE;
$text-color: #333333;
$text-color-light: #505050;
$card-border: 1px solid $border-color;
$card-border-radius: 5px;</code></pre>
The <code>styles.scss</code> will import the variables fragment and declare styles for the entire app. Since the app is simple, the resulting file is quite short:
<pre><code class="language-css">@use 'variables';
<br>body {
font-family: variables.$font-stack;
background-color: variables.$bg-color;
color: variables.$text-color;
box-sizing: border-box;
margin: 0;
padding: 0;
}
<br>.container-fluid {
max-width: 1280px;
margin: 0 auto;
}
<br>.card {
background-color: variables.$card-color;
padding: 1rem 1.25rem;
margin: 1rem auto;
border: variables.$card-border;
border-radius: variables.$card-border-radius;
box-shadow: 5px 5px 15px 0px rgba(0,0,0,0.2);
transition: all 0.2s;
<br> .card_title, #out_histogram_title {
color: variables.$text-color-light;
font-weight: bold;
text-transform: uppercase;
}
}
<br>.card:hover {
box-shadow: 5px 5px 15px 0px rgba(0,0,0,0.4);
}
<br>.app-heading {
text-transform: uppercase;
font-weight: bold;
margin-bottom: 1rem;
}
<br>.input_container {
display: flex;
flex-direction: row;
background-color: variables.$card-color;
padding: 1rem 1.25rem;
margin: 1rem auto;
<br> .shiny-input-container:first-child {
margin-right: 3rem;
}
}
<br>table.shiny-table {
width: 100% !important;
text-align: center;
<br> thead {
text-align: center !important;
<br> tr {
text-align: center !important;
}
}
}</code></pre>
That's SCSS taken care of, but what do you actually do with it? The <code>styles.css</code> is linked in the dashboard, and we don't have that file in the <code>www</code> directory.
Well, assuming you have <a href="https://sass-lang.com/install" target="_blank" rel="nofolow noopener">SASS installed</a>, the compilation boils down to a single shell command:
<pre><code class="language-shell">sass <path-to-scss-file> <path-to-css-file></code></pre>
Here's what it looks like in our case:
<img class="size-full wp-image-15618" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d2b24d0cd7b298100cc4_309b285a_3-1.webp" alt="Image 3 - Compiling SCSS into CSS" width="2092" height="1096" /> Image 3 - Compiling SCSS into CSS
And that's it! The <code>styles.css</code> file is now present in the <code>www</code> folder:
<img class="size-full wp-image-15614" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d2b406181de50dbf9d20_2fb0bd45_4-1.webp" alt="Image 4 - Compiled CSS file" width="4336" height="2656" /> Image 4 - Compiled CSS file
You can now refresh the app and see the styles in action:
<img class="size-full wp-image-15616" src="https://webflow-prod-assets.s3.amazonaws.com/6525256482c9e9a06c7a9d3c%2F65b7d2b4f1dfa1404cc85648_2e1e78d9_5-1.webp" alt="Image 5 - Shiny for Python dashboard with SCSS styles" width="4336" height="2656" /> Image 5 - Shiny for Python dashboard with SCSS styles
And that's how you can work with SASS/SCSS in Shiny for Python. Let's wrap things up next.
Want to see an earthquake dashboard design in R Shiny? <a href="https://connect.appsilon.com/quakes_explorer/" target="_blank" rel="noopener">Explore our Quakes Explorer</a> built with <a href="https://appsilon.com/professional-shiny-app-ui/" target="_blank" rel="noopener">shiny.fluent and imola</a>.
<hr />
<h2 id="summary">Summary of Shiny for Python SCSS</h2>
There's no one stopping you from using vanilla CSS in your Shiny for Python projects, but it's always a good idea to keep up with the trends and best practices in the industry. SASS has been one for years, and it doesn't seem like it's going anywhere. It's an excellent skill to learn and it will definitely make your job easier.
<blockquote>Curious about <a href="https://appsilon.com/shiny-application-layouts/" target="_blank" rel="noopener">where Shiny application layouts are trending</a>? We've got you covered.</blockquote>
<i>What are your thoughts on Shiny for Python and SCSS integration? Do you use some alternative method for compiling SCSS?</i> Please let us know in the comment section below. Also, feel free to continue the discussion on Twitter - <a href="https://twitter.com/appsilon" target="_blank" rel="nofollow noopener">@appsilon</a> - We'd love to hear from you.
<blockquote>Need to make some reports quickly? <a href="https://appsilon.com/quarto-and-jupyter-notebooks/" rel="noopener">Try Jupyter Notebook and Quarto integration.</a></blockquote>