
Customizing Apache Superset Dashboards with CSS — Additional Tips and Tricks
Our last post on customizing dashboards in Apache Superset has proven to be one of our most-referenced blog posts ever. There’s certainly more to cover in this area, so I wanted to take some time today to provide additional things you might want to consider when undertaking such efforts, to make things safer, easier, and better supported as the Superset project shifts further toward the long-standing effort of comprehensive theming.
Regarding full themability, we’ve been making a lot of progress toward that goal since our last post. The SIP remains in progress, and is looking promising. While that effort may indeed affect our CSS templates as the DOM changes due to various component/style refactoring, it will not cover the long tail of all the nitty-gritty things people like to do with CSS. It’s as relevant as ever! For today, let’s go deeper on CSS, to reduce the level of maintenance effort and improve flexibility.
Avoid leveraging generated class names
The Superset codebase uses the Emotion library for its React components. This is super handy for developers, and leads to generated class names throughout our compiled codebase, such as .css-13133f7
. These are subject to change with any build or release! Whenever possible, avoid these by building a CSS selector that uses elements above and/or below that element in the DOM tree.
There’s more than one way to select a chart
If you look at the DOM around a given chart in a Dashboard, you’ll see that there are a few selectors you can use in your CSS:
ByChart ID Class
.dashboard-chart-id-5549 {
...super awesome chart styles here
}
data-test
Attributes
These are used by various testing frameworks to make sure Superset remains stable. In fact, they may not even exist in production builds, depending on your configuration. Although it’s not their purpose in life, you can leverage them when needed.
[data-test-chart-id="5535"] {
...super awesome chart styles here
}
Chart Class
Often, you’ll want to select ALL instances of a chart type in a dashboard. That’s why there’s a class name (in this case the newer pivot table) right next to the chart ID. Note that if/when the legacy pivot table is removed, this might lose its “v_2” suffix… just another example of how things could shift, and a friendly reminder that the DOM is not a contract and is not supported by semantic versioning of Superset for now.
<div id="chart-id-2590" class="pivot_table_v_2">
Reduce redundant style definitions
I’ve seen many instances of developers who need a block of styles for several charts, and they repeat it in their stylesheets like so:
...
[data-test-chart-id="5535"] > div:first-child {
display: none;
}
[data-test-chart-id="5557"] > div:first-child {
display: none;
}
[data-test-chart-id="5546"] > div:first-child {
display: none;
}
[data-test-chart-id="5552"] > div:first-child {
display: none;
}
[data-test-chart-id="5555"] > div:first-child {
display: none;
}
[data-test-chart-id="5558"] > div:first-child {
display: none;
}
[data-test-chart-id="5560"] > div:first-child {
display: none;
}
[data-test-chart-id="5569"] > div:first-child {
display: none;
}
[data-test-chart-id="5539"] > div:first-child {
display: none;
}
...
If you want to add ten more lines of styles, this gets very painful very fast. Instead, you can just serialize your CSS selectors, and define the actual styles only once, making it faster and less redundant to edit any shared styles:
[data-test-chart-id="5535"] > div:first-child,
[data-test-chart-id="5557"] > div:first-child,
[data-test-chart-id="5546"] > div:first-child,
[data-test-chart-id="5552"] > div:first-child,
[data-test-chart-id="5555"] > div:first-child,
[data-test-chart-id="5558"] > div:first-child,
[data-test-chart-id="5560"] > div:first-child,
[data-test-chart-id="5569"] > div:first-child,
[data-test-chart-id="5539"] > div:first-child {
display: none;
}
Let CSS cascade
Don’t forget that “C” in “CSS”. If you want to do something like that to setting the background of a bunch of charts, and then outlining the border of one or two of them, you should take advantage of the fact that subsequent and/or higher-specificity CSS selectors can override/cascade over previously defined styles. You can select a chart type by class, or a handful of charts (like in the prior example) and then add a single override. For example:
.dashboard-chart-id-5547, .dashboard-chart-id-5548, .dashboard-chart-id-5549 {
background: PapayaWhip;
}
.dashboard-chart-id-5547 {
border: PeachPuff;
}
or
.big_number {
background: DodgerBlue
}
.dashboard-chart-id-5547 {
border: RebeccaPurple;
}
… and yes, those are real HTML colors.
Don’t forget row backgrounds
Don’t forget row backgrounds! in the dash layout editor, you can go clear/white. This can save on CSS code, and/or conflict with your styling. To see it in action, click the gear icon next to a row in edit mode, and you can pick transparent or white.
If you DO select the “white” option for your row, you’ll find the dashboard now contains background-white
class. The trick is, you need to provide a selector with a high enough specificity that matches or exceeds the existing style, so you don’t have to rely on
.dashboard-grid .grid-row.background--white {
background-color: red; /* I know, it's ugly */
}
You can see the ugly result (it could be prettied up!) and the selector specificity that makes it work highlighted here:
!important
is often NOT important.
It bears repeating: we want to write more specific selectors when needed to make overrides, and avoid using !important
whenever possible. People tend to overuse this “nuclear option” in CSS styling to force an override. It’s only a matter of time before you need to override an “important” style with some other “important” style.
Very often, a better selector is what you need… use your CSS inspector to find the class that’s adding the style you want to override (like a background color, or whatever) and add a class with equal or higher specificity.
You may spot in your browser’s CSS inspector a nice deep selector that looks like this:
.some-layout-thing .some-wrapper .some-component + someSiblingElement .icon{
background-color: PeachPuff;
}
It’s tempting to just add this in your custom CSS template:
.icon{
background-color: CornflowerBlue !important; /* The boss in Fight Club actually knows his HTML colors! */
}
The !important
will make the icon blue. The risk, however, is that there might be a reason the selector is so long. You might use .icon
all over your app, and your new override will affect all of them. You probably want to affect this specific instance for good reason, just like the original author of the CSS you’re overriding did. So just do this and you’ll be fine:
.some-layout-thing .some-wrapper .some-component .icon{
background-color: CornflowerBlue;
}
Some more random examples!
It just wouldn’t be a CSS post without some more random examples, right? These are just a few things people have asked me about lately.
Centering number on a BigNumber chart
Someone asked for this, and they were trying text-align: center
on the number in the Big Number chart. The number itself, however, happens to be within a flexbox column layout. New to flex box? This is a gold mine. But anyway...
The HTML of a “Big Number” chart looks like so:
<div id="chart-id-232" class="big_number_total">
<div class="superset-legacy-chart-big-number css-9elo0 no-trendline" style="height: 331px;">
<div class="header-line" style="display: flex; align-items: center; font-size: 66px; height: auto; color: black;">
$10,032,628.85
</div>
</div>
</div>
The HTML of a “Big Number With Trendline” chart looks like so:
<div id="chart-id-3280" class="big_number">
<div class="superset-legacy-chart-big-number css-9elo0 ">
<div class="text-container" style="height: 232px;">
<div class="header-line" style="display: flex; align-items: center; font-size: 77px; height: auto; color: black;">
$ 78,918
</div>
</div>
<div height="99" width="544" class="css-5m6j4a" style="user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);" _echarts_instance_="ec_1741126055930" aria-label="Big number visualization ">
<div style="position: relative; width: 544px; height: 99px; padding: 0px; margin: 0px; border-width: 0px; cursor: default;">
<canvas data-zr-dom-id="zr_0" width="1088" height="198" style="position: absolute; left: 0px; top: 0px; width: 544px; height: 99px; user-select: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); padding: 0px; margin: 0px; border-width: 0px;"></canvas>
<div style="position: absolute !important; visibility: hidden !important; padding: 0px !important; margin: 0px !important; border-width: 0px !important; user-select: none !important; width: 0px !important; height: 0px !important; inset: 0px auto auto 0px !important;"></div>
<div style="position: absolute !important; visibility: hidden !important; padding: 0px !important; margin: 0px !important; border-width: 0px !important; user-select: none !important; width: 0px !important; height: 0px !important; inset: 0px 0px auto auto !important;"></div>
<div style="position: absolute !important; visibility: hidden !important; padding: 0px !important; margin: 0px !important; border-width: 0px !important; user-select: none !important; width: 0px !important; height: 0px !important; inset: auto auto 0px 0px !important;"></div>
<div style="position: absolute !important; visibility: hidden !important; padding: 0px !important; margin: 0px !important; border-width: 0px !important; user-select: none !important; width: 0px !important; height: 0px !important; inset: auto 0px 0px auto !important;"></div>
</div>
</div>
</div>
</div>
While there’s a bunch more “stuff” for the trendline in there, you’ll notice that you can specify which chart type you want with a .big_number_total
or .big_number
selector to get the outer layer. Interestingly, the next layer down is .superset-legacy-chart-big-number
for both chart types, since they’re built from the same stuff under the hood! That means to get both types in one shot, you can simply do this:
.superset-legacy-chart-big-number {
align-items: center;
}
If you want to get just one or the other, you can do so with a more specific selector:
.big_number .superset-legacy-chart-big-number {
align-items: center;
}
.big_number_total superset-legacy-chart-big-number {
align-items: center;
}
Changing colors of chart titles.
Let’s say you want to make your chart titles a nice obvious/hideous red color. This case is a little funny, since the CSS classes for the header are actually different depending on whether your dashboard is in display mode or edit mode. For that reason, we need to use both .editable-title
and .editable-title--editable
classes.
You could write a style block like so:
.editable-title a, .editable-title--editable {
color: red;
}
However, that makes all editable titles (including tabs and chart titles) red:
Want to narrow that down a bit? You can add the .dashboard-component-chart-holder
to have it apply only to charts, like so:
.dashboard-component-chart-holder .editable-title a, .dashboard-component-chart-holder .editable-title--editable {
color: red;
}
Or you can have it apply only to the tabs, like so:
div[role="tab"] .editable-title, div[role="tab"] .editable-title--editable {
color: red;
}
Note that I used the “role” attribute selector here. I’m not using any ant
class names for the time being because we’re transitioning from AntD v4 to v5 (again, that’s another story entirely). Their classnames will be unstable during this transition. As always, it’s worth noting that while the DOM is accessible, it’s not contractual.
Center chart rows on dashboards:
Some people want their grid to feel a little more “centered” on the dashboard layout, so charts don’t sit relative to the left edge. For this, you can simply use:
.grid-row {
justify-content: center;
}
This should center each row (which we can narrow down if needed). It affects the display mode of dashboards, but does not affect “Edit mode,” where it stays left-aligned so you can build your dashboards as normal.
Adding new (and permanent!) convenience classes.
Sometimes things might just be too darned hard… like when you need different classes in different states (like a dashboard’s viewing and edit modes). Or you want to easily access a seemingly common layout item (like something in the filters bar) and you don’t want to fill your template with lengthy selectors. Or better yet, you DO want the DOM to be a bit more contractual/stable. In this case, we’re certainly open to adding CSS selectors to the codebase that are specifically for custom CSS templates.
The current proposal I’ll lay out now is that we use the class namespace/prefix of custom-styling-XXXXX
. You can then use these “custom styling” classes in your CSS/templates, but the authors of Superset can attempt to avoid them in internal/component development.
If you want to open a PR to add such a class, we simply want to know the use case to weigh the before/after benefits. We know these can be useful, but we also don’t want to completely litter the UI with them.
If the intent os to make the DOM more contractual, then clearly removing such a class be considered a breaking change. It’ll require consensus on the dev@ mailing list (subscribe by sending an email here), and holding off from merging until a major version release of Superset. Let’s cross that bridge when we come to it.
What’s next? You tell me!
I truly love tweaking CSS, and would love to continue this series. Hit me up with all your burning CSS questions and use cases. You can email me at evan@preset.io or DM me on Slack, and together we can start building up to Part 3! Together, we can make sure CSS in Superset no longer needs to feel like this: