Background Colors in Grafana HTML Panels: What Actually Works
Grafana's HTML panel lets you drop custom HTML and CSS directly into a dashboard. That means you can style a background color however you want. It sounds trivial, but people run into the same walls repeatedly. I'm going to walk through how to do it properly, where it falls apart, and what to use instead when this approach doesn't cut it. HTML panels accept raw HTML. You put a div with a style attribute, you set the background color, and it renders. Here's the simplest form: <div style="background-color: #1a1a2e; color: #e0e0e0; padding: 20px; border-radius: 8px;">Your content here</div>
That's it for the basic case. Dark backgrounds with light text. Light backgrounds with dark text. Pick a palette. Apply it. The more useful approach pulls the background from Grafana's own theme variables so your panel doesn't look completely out of place when someone switches from dark to light mode. Grafana exposes CSS custom properties you can reference: <div style="background-color: var(--pf-ui-bg-color); color: var(--pf-ui-text-color); padding: 20px;">Content</div>
I used to hardcode colors in every panel. After a few months of maintaining dashboards across teams, I switched everything to theme variables. It took me about three days to refactor a dashboard with forty-plus panels. Worth it. If you need conditional colors — red background when an alert is firing, green when it's clear — you can't do that with plain static HTML. You'd need to pull data in and react to it. That's where templating and JavaScript come in. You can use Handlebars syntax inside the HTML panel. Grafana processes those before rendering. So you could build something like this:
Get the Full Details
![[Tutorial] Custom colors based on labels - Gauge Panel - Grafana Labs ...](https://us1.discourse-cdn.com/grafana/optimized/3X/5/1/511b5e0315fc1c4fb2ab6494fdf2c7d89dfa1e2d_2_580x500.png)
<div style="background-color: {{{statusColor}}}; padding: 15px;">Status: {{{status}}}</div> Then in your panel options, you set the variable statusColor using a template expression or a custom variable. This gets messy fast if you don't keep it simple. For more complex logic, I wrap the HTML panel in a script tag and manipulate the DOM directly. Grafana runs the script on panel render:
<script>var el = document.getElementById('status-box');if (currentValue > threshold) { el.style.backgroundColor = '#d32f2f'; } else { el.style.backgroundColor = '#388e3c'; }</script><div id="status-box">Loading...</div> This works. But there's a catch. The script runs before your data query finishes unless you hook into Grafana's lifecycle events. I learned that the hard way. One dashboard I was building had a panel that checked CPU utilization and changed the background from green to orange to red. I wired up the script exactly as above. The panel always showed green, even when CPU was at 94%. The issue was timing. Grafana renders the HTML panel before the data arrives, so currentValue was undefined at script execution time. The fix was to use Grafana's getBackendSrv and getDataSourceSrv to fetch data async, then update the DOM once the response came back. Or simpler: use the Panel context that Grafana injects into the scope.
Here's what that looks like in practice: <script>(function() { var el = document.getElementById('cpu-bg'); var ctx = panel || {}; var data = ctx.data; if (data && data.series) { var value = data.series[0].points[0].value; if (value > 80) { el.style.backgroundColor = '#d32f2f'; } else if (value > 50) { el.style.backgroundColor = '#ffa000'; } else { el.style.backgroundColor = '#388e3c'; } } })();</script> That pattern has saved me more times than I can count. But it only works reliably in Grafana 8 and above. Older versions handle panel context differently, and the data shape changes between versions.
![[Tutorial] Custom colors based on labels - Gauge Panel - Grafana Labs ...](https://us1.discourse-cdn.com/grafana/original/3X/e/9/e9528a9ebe568a7cf6491ef615d2fe6eb95db1cc.png)
Another thing people miss: the HTML panel doesn't respect Grafana's global CSS overrides by default. If you've added custom CSS to your instance through the Grafana.ini config or a public plugin, it won't automatically apply inside the sandboxed HTML panel. You need to inline everything or use a plugin that injects styles. I've seen dashboards that looked perfect in edit mode and completely broken in view mode because someone relied on a global class that didn't exist inside the panel's iframe-like context. Performance is also a real concern. Every HTML panel with inline JavaScript and repeated DOM manipulation adds up. A dashboard with twenty HTML panels, each running their own script on every refresh, will make your browser chew through CPU. I ran into this on a production dashboard that had to refresh every ten seconds. After about two weeks, users reported slowness. Profiled it down to the HTML panels. Replaced half of them with native Grafana panels. The remaining half got their scripts optimized to only update on data change rather than on every render tick. The dashboard went from noticeable lag to unnoticeable. If you're building something substantial — complex cards, KPI displays, custom alert banners — the HTML panel is fine for prototypes and small dashboards. But for anything that needs to scale across a fleet of dashboards or gets maintained by multiple people, I'd recommend looking at the Canvas panel instead. It's Grafana's native visual builder. You get the same kind of background color control without the JavaScript overhead, without the sandboxing gotchas, and without the maintenance burden of hand-rolled HTML.
Canvas panels also support conditional formatting natively. Set a rule: if metric exceeds X, change fill color to Y. No scripts. No timing issues. It's not as flexible as raw HTML for layout, but for background colors and visual highlighting, it does what you actually need without the downsides. There's also the Simple Gradient Background plugin if you just want gradient backgrounds without writing anything. It's a Grafana plugin you install and configure through the UI. Good middle ground if you want something prettier than flat colors but don't want to maintain custom HTML. My advice: start with theme variables. Hardcode colors only when you have a specific reason. Use scripts sparingly and always account for async data loading. Test your dashboard in view mode, not edit mode. And when the complexity grows beyond a few panels, switch to Canvas before you build yourself a maintenance headache.
The HTML panel is a tool, not a framework. It does what you tell it to do, and it will do it wrong in ways you didn't expect if you're not careful. That's just how it is.
