---
title: Brand and design a dashboard
description: Retint the runtime to your brand colors, embed your real logo and font, and elevate the chrome without breaking scheduled refresh.
updated: 2026-08-04
tier: free
---

A dashboard published straight from the authoring loop is correct but plain:
default blue on white, every figure the same weight, a text-only header. This
page turns that into something that looks like your company built it on purpose.

Ask your AI to use the `dashies-design` skill, which is bundled with the plugin,
and it follows exactly this procedure.

## What you control, and what the runtime draws

A dashboard is one self-contained HTML file. Knowing who owns what is the whole
job.

| Part | Who controls it |
|---|---|
| Header, cards, layout grid, section titles, backgrounds, spacing, typography | **You.** Plain HTML and your own `<style>`. The runtime only writes numbers into the slots you mark. |
| Charts, tables, filter controls, the default figure card | The runtime draws them, but every colour and font is a CSS custom property you can redefine. |

So the procedure is: **retint the runtime to your brand, then build good chrome
around it.** Doing only the second is the single most recognisable "default
template with a custom title" tell.

## 1. Decide the brand system first

Four decisions, written down before any CSS:

1. **Accent.** One brand colour, used sparingly.
2. **Neutral temperature.** Warm grey, cool slate, or near-black. This carries
   most of the design. Match it to the accent: a warm accent wants warm neutrals,
   a cool one wants slate. A mismatched temperature is a quiet ugliness.
3. **Type.** Identify the real typeface, then decide whether you can embed it.
4. **Light or dark, and the mark.**

## 2. Retint the runtime

Drop this into your `<style>` and swap the values. The example is an
indigo and near-black feel.

```html
<style>
:root {
  /* accent: chart series, line/bar/area, dots, focus */
  --drt-blue:        #635bff !important;
  --drt-blue-700:    #4b45d6 !important;
  --drt-blue-soft:   #f0efff !important;
  /* text ramp: keep the greys descending and legible */
  --drt-ink:         #0a1636 !important;
  --drt-muted:       #45507a !important;
  --drt-subtle:      #6b769c !important;
  --drt-faint:       #9aa2be !important;
  /* surfaces and hairlines */
  --drt-surface:     #ffffff !important;
  --drt-sunken:      #f7f8fc !important;
  --drt-line:        #e7e9f3 !important;
  --drt-line-strong: #ced2e6 !important;
  --drt-grid:        #eef0f8 !important;
  /* type */
  --drt-sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif !important;
  --drt-mono: ui-monospace, 'SF Mono', 'JetBrains Mono', Menlo, monospace !important;
}
/* the two colours that are not tokenized */
.drt-area { fill: color-mix(in srgb, var(--drt-blue) 12%, transparent) !important; }
.drt-select:focus-visible {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--drt-blue) 22%, transparent) !important;
}
</style>
```

:::warning{title="The !important is load-bearing"}
The runtime injects its own `<style>` last, at boot, so a plain
`:root { --drt-blue: ... }` in your head **loses** the cascade to the runtime's
own `:root`. Marking your declarations `!important` wins regardless of order.

Two colours are hardcoded rather than tokenized, the area-chart fill and the
select focus ring, which is why the two class overrides are there. That is the
entire surface; there is nothing else to chase.
:::

Reuse the same variables in your own chrome CSS, so the header and the charts
share one palette by construction. Tint neutrals a few percent toward the accent
rather than using pure greys: a canvas of
`color-mix(in srgb, var(--drt-blue) 4%, white)` reads as designed, `#f5f5f5`
reads as template.

## 3. Embed the real logo and font

The dashboard is served under a strict sandbox with no network access, so
external fonts, CDN scripts, and remote images do not load. The full boundary is
in
[what a viewer's browser can and cannot do](/concepts/dashboard-file#what-a-viewers-browser-can-and-cannot-do). **Inline SVG and
`data:` URIs do**, which means you can embed the real assets rather than
approximate them.

**The logo:** paste the company's official SVG straight into the header markup
and size it to roughly 20 to 36 pixels. A single-colour mark takes
`fill: currentColor` or the brand colour.

**The font:** embed it as base64 so it needs no network.

```html
<style>
@font-face {
  font-family: 'Brand';
  font-weight: 400;
  src: url(data:font/woff2;base64,d09GMgABAAAAAA...) format('woff2');
}
:root { --drt-sans: 'Brand', ui-sans-serif, system-ui, sans-serif !important; }
</style>
```

One `@font-face` per weight, and keep it to two or three weights: a Latin subset
is roughly 20 to 40 KB of base64 each. Setting the family on `--drt-sans` makes
the chrome and the runtime widgets share it.

:::note{title="Only embed a font you are licensed to embed"}
Open-licence fonts, Google Fonts, or a file you have the rights to. Most flagship
brand typefaces are proprietary and not freely obtainable, so use the closest
free match instead.
:::

## 4. Elevate the chrome

Four moves, in order. Each is small and changes a lot.

- **One thing is biggest.** The default makes every figure identical, so the eye
  has nowhere to land. Pick the metric that matters most and make it dominant,
  roughly three times the size of its label, with a short supporting line. Demote
  the rest to a quieter row.
- **Give the header an identity.** A mark, one brand moment such as a thin accent
  rule or a subtle tinted band, and a tidy meta line with the title, a real
  subtitle, and the freshness stamp.
- **Commit to one surface treatment.** Either flat cards with a hairline border on
  a lightly tinted canvas, or lifted cards with one soft shadow and no border.
  Mixing borders and shadows at random is the classic tell. One radius value
  everywhere.
- **Use a real type scale.** Big jumps and weight contrast, not 14/16/18. Numbers
  in a monospace with tabular figures so columns align. Space on a consistent
  8-pixel rhythm.

Spend the accent on the primary chart series, the hero figure, links, focus
rings, and the header mark. Not on every border, label, and card background.

## Two rules that keep refresh working

:::danger{title="Do not touch the data island or replace the charts"}
- **Never edit, wrap, or move `<script id="dashies-data">`.** The refresh cron
  rewrites that element by id, which is why your design survives every refresh.
  See [The data island](/concepts/data-island). Styling belongs in `<head>` and in
  your own markup.
- **Keep charts runtime-rendered.** Retint them; do not replace them with
  hand-drawn SVG. A static chart cannot be updated by the refresh, so the page
  would show a stale picture beside fresh numbers.
- **Never script-replace `<body>`.** That clobbers the badge injection.
:::

Styling never changes the datasets, the compiled island, or how the dashboard
refreshes, so you can restyle a live dashboard freely as long as you respect
those three.

## The quick path: the spec's theme field

For a light touch without writing CSS, the spec carries a `theme` block with
`accent`, `font`, `density`, and `mode`. It is the right tool for making a
dashboard the correct colour. It is not enough to make one look designed, which
is what the rest of this page is for.

## What to avoid

These are the tells that make a dashboard read as generic:

- Shipping the runtime's default blue on the charts.
- Every figure the same size.
- A text-only header with no mark.
- Borders and shadows mixed at random across cards.
- Rainbow or multi-colour categorical bars. Dashies charts are single-accent by
  design; keep them so.
- Gradients everywhere, glassmorphism, neon glow, heavy drop shadows, radii above
  20 pixels, emoji in the header, decorative icons carrying no meaning.
- Low-contrast grey-on-grey text.
- A coloured left-border stripe on every card "for variety".
- Card inside card inside pill. Two levels of surface nesting is the limit.
- Filler copy such as "Welcome" or "Get started".
- Deltas, trend arrows, or metrics the data does not actually contain.

## Check it worked

1. **Open the published URL and look at a chart.** If the series is still the
   default blue, the retint did not land, which almost always means a missing
   `!important`.
2. **Open the browser console.** There should be no content-security-policy
   violations. One means something is still trying to reach the network, usually
   a font or image URL that needs embedding.
3. **Trigger a refresh, then reload.** The numbers change and your styling is
   untouched. If the styling disappeared, something was written into the data
   island.
4. **Check contrast.** Text on your accent should clear WCAG AA, and
   `--drt-ink` should be genuinely dark.
