Building a Themeable Nuxt Portfolio
How I built a portfolio that ships five completely different visual identities from a single set of CSS variables — and why the token layer matters more than the components.

Why five themes
Most portfolios pick one aesthetic and commit. I wanted the site itself to be the demo — terminal, blueprint, cyberpunk, editorial, and bento, each a genuinely different personality, all served from the same components.
The constraint that made it work: components never hardcode a color.
The token layer
Every theme is a block of CSS custom properties. Components reference semantic names, never raw values.
[data-theme='terminal'] {
--t-base: #0b0f0d;
--t-text-1: #d6e5dc;
--t-accent: #4ade80;
--t-accent-2: #22d3ee;
--t-line: #1c2b23;
}
Tailwind v4 makes this cheap — no tailwind.config.js, just @theme in the
main stylesheet mapping utilities onto the variables:
@theme {
--color-accent: var(--t-accent);
--color-t1: var(--t-text-1);
}
Now text-t1 and bg-accent mean whatever the active theme says they mean.
What actually needed per-theme branching
Three things resisted pure tokenization:
- Voice — the terminal theme says
$ ls ~/projects, the blueprint theme saysFIG. Work. Copy is identity, so a handful ofv-if="theme === '...'"blocks survive. - Card chrome — a shared
ThemeCardcomponent switches its frame per theme. - Home layout — bento and editorial restructure the page enough that they get their own root components rather than a re-skin.
Everything else — every page, every button, every tag pill — is theme-agnostic.
The lesson
The temptation is to build theme variants at the component level. Don't. Push the variance down into tokens and let components stay boring. When I added the fifth theme it took under an hour, because there was nothing to change but a variable block and a few strings.