Biohacking dashboards are ugly by default
I track too many things. HRV, sleep, kettlebell sessions, batting drills, glucose on and off, sometimes reaction time. Every tool ships with its own opinionated UI and zero interest in playing nicely with others.
I hate context switching visually. Dark mode here, clinical white there, neon gradients in the next tab. It fragments my focus. If I am looking at my nervous system, I want one visual language. Not six.
So I stopped waiting for integrations and themes that never ship. I started forcing every biohacking tool into the same look using one boring but powerful feature: CSS variables.
My constraint: one theme, many weird tools
Here is the practical setup I am working with:
- Garmin and Oura exports piped into a custom dashboard.
- A small web app I built for tracking baseball drills and swing volume.
- A Notion-like notes tool where I keep protocols and experiments.
- Random vendor dashboards in iframes. Because life is pain.
I wanted all of them to feel like one system. Same colors. Same typography. Same sense of hierarchy. If I glance at HRV or at kettlebell volume, my brain should not need a full layout parse.
CSS variables are the glue. I define the design system once, then keep bending UIs around it until they submit.
The core: a tiny design token layer in CSS
I treat CSS variables as the public API of my theme. Colors, spacing, fonts, radii, shadows. If it affects how the interface feels, it becomes a variable.
This is the baseline I use across my biohacking tools:
:root {
/* core palette */
--color-bg: #04060a;
--color-bg-elevated: #0b1018;
--color-surface: #121927;
--color-accent: #4fd1c5; /* "recovered" / good */
--color-accent-soft: #234e52;
--color-danger: #f56565; /* "overreached" / bad */
--color-warning: #ecc94b;
--color-text: #f7fafc;
--color-text-muted: #a0aec0;
--color-border: #2d3748;
/* typography */
--font-sans: system-ui, -apple-system, BlinkMacSystemFont,
"SF Pro Text", "Segoe UI", sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
/* spacing scale */
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 24px;
--space-6: 32px;
/* radii */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 16px;
/* shadows */
--shadow-soft: 0 10px 30px rgba(0, 0, 0, 0.45);
}
This file is theme.css. Every project imports it. I do not care if it is a Next app, a static dashboard, or something injected via a browser extension.
The exact values are opinionated. Dark, slightly cinematic, a hint of lab equipment. The details matter, because that mood is the mental context for my biohacking work.
Mapping physiology to color and motion
I hate random color choices. If you stare at these dashboards daily, your brain starts building associations whether you like it or not. I would rather decide those intentionally.
For my biohacking stack, I use a very simple mapping:
- Cool greens / teals for recovered, parasympathetic leaning states.
- Ambers for “pay attention” but not panic.
- Warm reds for “do less or you will regret it.”
The CSS side of that looks like this:
:root {
--state-good: var(--color-accent);
--state-good-soft: var(--color-accent-soft);
--state-warning: var(--color-warning);
--state-bad: var(--color-danger);
}
.badge-good {
color: var(--state-good);
background: color-mix(in srgb, var(--state-good-soft) 70%, transparent);
}
.badge-warning {
color: var(--state-warning);
}
.badge-bad {
color: var(--state-bad);
}
Now when I wire up a new panel for HRV, sleep debt, or training load, I do not think about exact hex values. I just choose the state variable.
HRV green, sleep debt amber, too many heavy swings deep red. Same palette everywhere. My brain learns it fast, which is the entire point.
A theme object that frameworks can understand
Variables in CSS are nice. Variables in JS are sometimes unavoidable. I want both to agree.
My compromise is to define the tokens in CSS and then mirror them into a tiny JS theme object. I do not maintain two separate systems. I read from CSS:
const cssVar = (name) =>
getComputedStyle(document.documentElement)
.getPropertyValue(name)
.trim();
export const theme = {
bg: cssVar("--color-bg"),
accent: cssVar("--color-accent"),
danger: cssVar("--color-danger"),
};
Now, if I feed values into Vega-Lite charts or some random canvas visualization, I am still using the same colors as the CSS buttons and cards.
I think this is underrated. Most health dashboards look like ten designers disagreed over a period of three years. Keeping everything tied to the CSS variables makes it feel like one tool instead of a playlist of vendor branding.
Switching day and night without a mess
Biohacking is inherently tied to light. Circadian rhythm, blue blockers, light intensity. So my UI respects that. I do not want a retina-burning chart at 23:00.
I only run two themes: dark and night. Yes, both are dark. One is just darker and softer. I do not maintain a light theme. I think that is pointless for this context.
The CSS is simple:
:root[data-theme="dark"] {
--color-bg: #04060a;
--color-bg-elevated: #0b1018;
--color-surface: #121927;
--color-text: #f7fafc;
--color-text-muted: #a0aec0;
}
:root[data-theme="night"] {
--color-bg: #010208;
--color-bg-elevated: #05060b;
--color-surface: #0a0c14;
--color-text: #e2e8f0;
--color-text-muted: #718096;
}
Theme switching is just an attribute toggle:
const setTheme = (mode) => {
document.documentElement.setAttribute("data-theme", mode);
localStorage.setItem("theme", mode);
};
All the charts, tables, inputs, and cards update automatically because they never used literal colors. Only variables.
This is the core benefit. I can keep adjusting how “night mode” feels without touching a single component. I just massage the variables until my eyes are happy.
Hijacking external dashboards with a browser extension
So far I have only talked about stuff I own. Custom apps, small dashboards, things I deploy myself. The more interesting bit is external tools that I cannot control.
Here is how I force them to behave.
Step 1: Global theme injector
I run a user-style extension in the browser (Stylus works fine). It injects a very small stylesheet into matching domains. That stylesheet defines my variables and overrides just enough of the UI to make it feel like my system.
Example for a hypothetical HRV platform:
@-moz-document domain("hrv-vendor.example") {
:root {
--color-bg: #04060a !important;
--color-surface: #121927 !important;
--color-text: #f7fafc !important;
--color-accent: #4fd1c5 !important;
}
body {
background: var(--color-bg) !important;
color: var(--color-text) !important;
font-family: var(--font-sans, system-ui) !important;
}
.btn-primary,
button.primary {
background: var(--color-accent) !important;
border-radius: var(--radius-md, 8px) !important;
border: none !important;
}
}
Is it a bit brute force? Yes. Does it work? Also yes.
The idea is not to re-skin their product fully. I only care about aligning the background, text, and accent colors with my variables. That is enough for my brain to treat it as part of the same ecosystem.
Step 2: Dealing with iframes
Iframes are annoying. The host page can be perfectly themed while the embedded analytics widget still screams corporate teal on white.
If the iframe is on the same origin, it is easy. I just inject the same theme.css file into the iframe document. Done.
If it is cross origin, I have two options:
- Live with it and only control the chrome around it.
- Proxy it through my own domain and strip or override its CSS.
For personal tooling, I sometimes go for the proxy. A tiny Next or Cloudflare Worker that fetches the vendor page, rewrites the CSS to variables, and serves it under my domain.
It is hacky and fragile, but for one or two critical dashboards, the trade-off is worth it. My rule is simple. If I look at it daily, it deserves to speak my visual language.
Making charts obey the theme
Charts are where most dashboards fall apart visually. They come pre-styled with awful contrasts and gradients that feel like a marketing deck.
I standardize chart theming around the same variables and keep the configuration close to the CSS.
For example, in a simple D3 or Chart.js setup:
const getTheme = () => ({
bg: cssVar("--color-bg"),
surface: cssVar("--color-surface"),
text: cssVar("--color-text"),
muted: cssVar("--color-text-muted"),
good: cssVar("--state-good"),
warning: cssVar("--state-warning"),
bad: cssVar("--state-bad"),
});
const t = getTheme();
const hrvSeriesColor = (value) => {
if (value >= 80) return t.good;
if (value >= 60) return t.warning;
return t.bad;
};
Now if I decide that “good” should be less neon and more muted, I touch one variable and the charts, tags, and state badges all shift together.
This is the main reason I like CSS variables for biohacking tools. I change my mind often. One night of bad sleep and I think the entire palette feels wrong. With variables, that is a 10 minute fix, not a refactor.
Connecting UI intensity to physiological load
One thing I started playing with recently: UI intensity that matches how stressed my system is. Not just colors but spacing and density.
A calm body gets a calm UI. A stressed body gets a louder one.
I expose a variable for “tension” and hook it into subtle bits of the interface:
:root {
--tension: 0; /* 0 = calm, 1 = high tension */
}
.card {
box-shadow:
0 0 0 calc(1px + var(--tension) * 1px) rgba(255, 255, 255, 0.03),
0 12px 30px rgba(0, 0, 0, 0.4 + var(--tension) * 0.2);
}
.hrv-line {
stroke-width: calc(1.5px + var(--tension) * 0.5px);
}
Then in JS I do something naive first:
const tensionFromHrv = (rmssd) => {
if (!rmssd) return 0.5;
if (rmssd >= 80) return 0.1;
if (rmssd <= 40) return 1;
return 0.5;
};
const applyTension = (value) => {
document.documentElement.style.setProperty(
"--tension",
String(value)
);
};
This is subtle, but I feel it. On days where HRV is low or load is high, the dashboard feels slightly “sharper.” More contrast. Slightly thicker lines. It nudges my behavior.
Is this overkill? Maybe. But this is where CSS variables start feeling like a nervous system for the UI. You hook the state of the body into the state of the interface without touching every component manually.
Practical advice if you want to copy this
If you want your own unified biohacking UI, here is the order I would actually do it in.
First, write down three words for how you want the interface to feel. Mine: calm, clinical, focused. That will drive your color and spacing choices more than any palette generator.
Second, build one small theme.css file. Backgrounds, text, accents, spacing, radii, shadows. Nothing fancy. Ship it.
Third, refactor one tool at a time to only use variables. No hex codes in components. No stray rgba inlined in chart configs.
Fourth, add a browser extension stylesheet for the external dashboards you use most often. Fix just the background, text, and primary buttons. Do not try to be their design team.
Finally, once you live with it for a week, start experimenting with state-connected variables like --tension or separate dark and night modes.
The tooling is trivial. The value comes from using CSS variables as a contract. Your body state changes. Your tools change. The contract stays the same.
That is how you keep a messy stack of biohacking apps feeling like one instrument instead of a drawer of mismatched gadgets.
Subscribe to my newsletter to get the latest updates and news
Member discussion