Skip to content

Make Your Own Theme

bluefox-components ships functional Jinja2 macros styled by Pico CSS custom properties. The default Bluefox theme (bluefox.css) is just one set of overrides — you can replace every visual detail by writing a single CSS file. Different fonts, sharp or rounded corners, bold or muted colors, dense or spacious layouts. Same markup, completely different look.


How theming works

Pico CSS defaults
  └── bluefox.css (Bluefox brand overrides)
        └── YOUR theme CSS (your overrides win)

Every visual property — colors, fonts, spacing, border radius — is controlled by a --pico-* CSS custom property. Your theme file overrides these variables. No !important, no class hacks, no build step.

Applying a theme

Option A — attribute selector (recommended for multiple themes):

Create a CSS file that targets [data-bfx-theme="your-theme"]:

/* static/themes/neon.css */
[data-bfx-theme="neon"] {
  --pico-font-family: "Space Grotesk", system-ui, sans-serif;
  --pico-primary: #00FF88;
  --pico-border-radius: 0;
  /* ... all your overrides ... */
}

Then set the attribute in your template:

<html lang="en" data-bfx-theme="neon">

Or in Jinja2 via the base template:

{% extends "bfx/base.html" %}
{# Pass theme= to activate your theme #}
# In your route handler:
return templates.TemplateResponse("page.html", {"theme": "neon"})

Option B — simple :root override (single theme):

/* my-theme.css — loaded after bluefox.css */
:root {
  --pico-primary: #8B5CF6;
  --pico-font-family: "Inter", sans-serif;
  --pico-border-radius: 0;
}
{% block head %}
<link rel="stylesheet" href="/static/my-theme.css">
{% endblock %}

Complete variable reference

These are all the Pico CSS variables that bluefox-components themes override. Every one of these is available for you to customize.

Typography

Variable What it controls Example values
--pico-font-family Body text font "Inter", sans-serif / "IBM Plex Sans", sans-serif
--pico-font-family-monospace Code font "JetBrains Mono", monospace / "IBM Plex Mono", monospace
--pico-font-size Base font size (on :root/html) 100% (16px) / 106.25% (17px) / 93.75% (15px)
--pico-line-height Body line height 1.5 / 1.55 / 1.6
--pico-font-weight Default body font weight 400 / 300

Brand colors

Variable What it controls Example values
--pico-primary Buttons, links, focus rings #D4652A / #7C3AED / #1B3A5C
--pico-primary-hover Primary on hover Slightly darker than primary
--pico-primary-focus Focus ring color (use rgba) rgba(212, 101, 42, 0.25)
--pico-primary-inverse Text on primary background #fff (usually)
--pico-primary-background Filled button background Same as primary (usually)
--pico-primary-border Filled button border Same as primary
--pico-primary-hover-background Filled button hover bg Same as primary-hover
--pico-primary-hover-border Filled button hover border Same as primary-hover

Secondary accent

Variable What it controls
--pico-secondary Secondary buttons, accents
--pico-secondary-hover Secondary hover state
--pico-secondary-focus Secondary focus ring
--pico-secondary-inverse Text on secondary background

Backgrounds & surfaces

Variable What it controls Example values
--pico-background-color Page background #FAFAF8 / #F6F1EB / #fff
--pico-card-background-color Card/article background #fff / #FFFDF9
--pico-card-sectioning-background-color Card header/footer bg #F5F4F0

Text colors

Variable What it controls
--pico-color Primary body text
--pico-muted-color Secondary/muted text
--pico-h1-color through --pico-h6-color Heading colors (each level independently)

Borders & radius

Variable What it controls Example values
--pico-border-radius Global border radius 0 (sharp) / 0.25rem (tight) / 0.5rem (soft) / 0.75rem (round)
--pico-muted-border-color Default borders #E4E3DF
--pico-card-border-color Card borders #E4E3DF
--pico-table-border-color Table borders #E4E3DF

Form elements

Variable What it controls
--pico-form-element-background-color Input/select background
--pico-form-element-border-color Input/select border
--pico-form-element-active-border-color Input border on focus
--pico-form-element-focus-color Input focus ring

Code blocks

Variable What it controls
--pico-code-background-color <pre><code> background
--pico-code-color Code text color

Beyond variables: structural overrides

CSS variables control colors, fonts, and radius — but you can go further. Use regular CSS selectors to change structural properties like button shapes, letter spacing, shadows, and text transforms:

[data-bfx-theme="corporate"] {
  --pico-font-family: "Inter", sans-serif;
  --pico-primary: #1B3A5C;
  --pico-border-radius: 0.25rem;
}

/* Uppercase buttons with tight letter spacing */
[data-bfx-theme="corporate"] button,
[data-bfx-theme="corporate"] [role="button"] {
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 600;
}

/* Serif headings for authority */
[data-bfx-theme="corporate"] h1,
[data-bfx-theme="corporate"] h2,
[data-bfx-theme="corporate"] h3 {
  font-family: "DM Serif Display", Georgia, serif;
}

What you can override structurally

Property Effect Example
font-family on headings Different heading vs body fonts Serif headings + sans body
text-transform on buttons UPPERCASE buttons text-transform: uppercase
letter-spacing on buttons Tighter or wider tracking letter-spacing: 0.05em
border-radius on specific elements Pill buttons, sharp cards button { border-radius: 999px }
box-shadow on buttons/cards Depth and elevation box-shadow: 0 2px 8px rgba(...)
font-size on html Scale everything up or down html { font-size: 15px }
line-height on body Dense or airy layout body { line-height: 1.4 }

Step-by-step: create a theme from scratch

1. Pick your design direction

Before writing CSS, decide on your theme's personality:

Decision Options
Mood Corporate, playful, minimal, warm, brutalist, luxury
Corners Sharp (0), tight (0.25rem), soft (0.5rem), pill (999px on buttons)
Typography Geometric sans, humanist sans, serif, monospace
Density Compact (15px base, 1.4 line-height) or spacious (17px, 1.6)
Color temperature Cool (navy, slate, blue-gray) or warm (terracotta, beige, olive)

2. Create your CSS file

/* static/bfx/themes/my-brand.css */

[data-bfx-theme="my-brand"] {
  /* ── Typography ──────────────────────────── */
  --pico-font-family: "YOUR FONT", system-ui, sans-serif;
  --pico-font-size: 100%;
  --pico-line-height: 1.55;

  /* ── Brand colors ────────────────────────── */
  --pico-primary: #YOUR_COLOR;
  --pico-primary-hover: /* 10% darker */;
  --pico-primary-focus: rgba(YOUR_RGB, 0.2);
  --pico-primary-inverse: #fff;
  --pico-primary-background: #YOUR_COLOR;
  --pico-primary-border: #YOUR_COLOR;
  --pico-primary-hover-background: /* same as hover */;
  --pico-primary-hover-border: /* same as hover */;

  /* ── Backgrounds ─────────────────────────── */
  --pico-background-color: #YOUR_BG;
  --pico-card-background-color: #YOUR_SURFACE;
  --pico-card-sectioning-background-color: #YOUR_SURFACE_ALT;

  /* ── Text ────────────────────────────────── */
  --pico-color: #YOUR_TEXT;
  --pico-muted-color: #YOUR_TEXT_SECONDARY;
  --pico-h1-color: #YOUR_HEADING;
  --pico-h2-color: #YOUR_HEADING;
  --pico-h3-color: #YOUR_HEADING;
  --pico-h4-color: #YOUR_TEXT;
  --pico-h5-color: #YOUR_TEXT_SECONDARY;
  --pico-h6-color: #YOUR_TEXT_SECONDARY;

  /* ── Borders ─────────────────────────────── */
  --pico-border-radius: 0.375rem;
  --pico-muted-border-color: #YOUR_BORDER;
  --pico-card-border-color: #YOUR_BORDER;
  --pico-table-border-color: #YOUR_BORDER;

  /* ── Code ─────────────────────────────────── */
  --pico-code-background-color: #YOUR_CODE_BG;
  --pico-code-color: #YOUR_CODE_TEXT;

  /* ── Forms ───────────────────────────────── */
  --pico-form-element-background-color: #YOUR_SURFACE;
  --pico-form-element-border-color: #YOUR_BORDER;
  --pico-form-element-active-border-color: #YOUR_COLOR;
  --pico-form-element-focus-color: rgba(YOUR_RGB, 0.2);

  /* ── Secondary accent ────────────────────── */
  --pico-secondary: #YOUR_ACCENT2;
  --pico-secondary-hover: /* darker */;
  --pico-secondary-focus: rgba(YOUR_ACCENT2_RGB, 0.2);
  --pico-secondary-inverse: #fff;
}

3. Load your font

Add the Google Fonts link in the head block:

{% block head %}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=YOUR+FONT:wght@400;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/bfx/themes/my-brand.css">
{% endblock %}

4. Activate the theme

@app.get("/")
async def homepage(request: Request):
    return templates.TemplateResponse("home.html", {
        "request": request,
        "theme": "my-brand",
    })

5. Add structural overrides (optional)

/* Pill buttons */
[data-bfx-theme="my-brand"] button,
[data-bfx-theme="my-brand"] [role="button"] {
  border-radius: 999px;
  font-weight: 700;
}

/* Serif headings */
[data-bfx-theme="my-brand"] h1,
[data-bfx-theme="my-brand"] h2,
[data-bfx-theme="my-brand"] h3 {
  font-family: "Playfair Display", Georgia, serif;
}

Extreme examples

These demonstrate how far you can push the same bluefox-components markup to look completely different.

Example 1: Playful consumer app

Think cadana-days.com — vibrant, bouncy, rounded everything.

[data-bfx-theme="playful"] {
  --pico-font-family: "Nunito", system-ui, sans-serif;
  --pico-font-size: 106.25%;  /* 17px — generous */
  --pico-line-height: 1.6;

  --pico-primary: #7C3AED;          /* vibrant purple */
  --pico-primary-hover: #6D28D9;
  --pico-primary-focus: rgba(124, 58, 237, 0.2);
  --pico-primary-inverse: #fff;
  --pico-primary-background: #7C3AED;
  --pico-primary-border: #7C3AED;
  --pico-primary-hover-background: #6D28D9;
  --pico-primary-hover-border: #6D28D9;

  --pico-background-color: #FAF5FF;
  --pico-card-background-color: #fff;
  --pico-card-sectioning-background-color: #F3E8FF;

  --pico-color: #1E1B2E;
  --pico-muted-color: #6B7280;

  --pico-border-radius: 0.75rem;    /* very rounded */

  --pico-secondary: #EC4899;         /* hot pink accent */
  --pico-secondary-hover: #DB2777;
  --pico-secondary-focus: rgba(236, 72, 153, 0.2);
  --pico-secondary-inverse: #fff;
}

/* Pill-shaped buttons with shadow */
[data-bfx-theme="playful"] button,
[data-bfx-theme="playful"] [role="button"] {
  border-radius: 999px;
  font-weight: 700;
  box-shadow: 0 2px 8px rgba(124, 58, 237, 0.2);
}

/* Bouncy headings */
[data-bfx-theme="playful"] h1,
[data-bfx-theme="playful"] h2 {
  font-family: "Nunito", sans-serif;
  font-weight: 800;
}

Result: Purple pill buttons with drop shadows, 12px rounded cards, pink accents, lavender backgrounds, chunky bold headings.

Example 2: Corporate finance dashboard

Think Bloomberg terminal meets modern banking.

[data-bfx-theme="financial"] {
  --pico-font-family: "Inter", system-ui, sans-serif;
  --pico-font-size: 100%;           /* 16px — tight */
  --pico-line-height: 1.55;

  --pico-primary: #1B3A5C;          /* deep navy */
  --pico-primary-hover: #152E4A;
  --pico-primary-focus: rgba(27, 58, 92, 0.2);
  --pico-primary-inverse: #fff;
  --pico-primary-background: #1B3A5C;
  --pico-primary-border: #1B3A5C;
  --pico-primary-hover-background: #152E4A;
  --pico-primary-hover-border: #152E4A;

  --pico-background-color: #F8F9FA;
  --pico-card-background-color: #fff;

  --pico-color: #212529;
  --pico-muted-color: #6C757D;

  --pico-border-radius: 0.25rem;    /* 4px — tight and professional */

  --pico-secondary: #8B7355;         /* muted gold */
  --pico-secondary-hover: #756040;
  --pico-secondary-focus: rgba(139, 115, 85, 0.2);
  --pico-secondary-inverse: #fff;
}

/* Uppercase buttons, tight tracking */
[data-bfx-theme="financial"] button,
[data-bfx-theme="financial"] [role="button"] {
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-weight: 600;
  border-radius: 4px;
}

/* Serif headings for authority */
[data-bfx-theme="financial"] h1,
[data-bfx-theme="financial"] h2,
[data-bfx-theme="financial"] h3 {
  font-family: "DM Serif Display", Georgia, serif;
}

Result: Navy buttons with uppercase labels, tight 4px corners, serif headings, gold accents. Feels like a premium banking app.

Example 3: Brutalist minimal

Think justhenext.com — monochrome, razor-sharp edges, nothing extra.

[data-bfx-theme="minimal"] {
  --pico-font-family: "IBM Plex Sans", system-ui, sans-serif;
  --pico-font-family-monospace: "IBM Plex Mono", monospace;
  --pico-font-size: 93.75%;         /* 15px — compact */
  --pico-line-height: 1.5;

  --pico-primary: #111;             /* pure black */
  --pico-primary-hover: #333;
  --pico-primary-focus: rgba(0, 0, 0, 0.15);
  --pico-primary-inverse: #fff;
  --pico-primary-background: #111;
  --pico-primary-border: #111;
  --pico-primary-hover-background: #333;
  --pico-primary-hover-border: #333;

  --pico-background-color: #fff;
  --pico-card-background-color: #fff;
  --pico-card-sectioning-background-color: #F8F8F8;

  --pico-color: #111;
  --pico-muted-color: #666;

  --pico-border-radius: 0;          /* zero — razor sharp */

  --pico-secondary: #666;
  --pico-secondary-hover: #444;
  --pico-secondary-focus: rgba(0, 0, 0, 0.1);
  --pico-secondary-inverse: #fff;
}

/* Sharp everything, minimal decoration */
[data-bfx-theme="minimal"] button,
[data-bfx-theme="minimal"] [role="button"] {
  border-radius: 0;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 500;
}

[data-bfx-theme="minimal"] article {
  border-radius: 0;
  border: 1px solid #E5E5E5;
}

Result: Black and white, zero border radius everywhere, uppercase buttons with wide letter spacing. Developer tool aesthetic.

Example 4: Warm friendly SaaS

Think Notion meets Airbnb — warm neutrals, serif display headings, inviting.

[data-bfx-theme="warm"] {
  --pico-font-family: "Outfit", system-ui, sans-serif;
  --pico-font-size: 100%;
  --pico-line-height: 1.55;
  --pico-font-weight: 400;

  --pico-primary: #D4603A;          /* terracotta */
  --pico-primary-hover: #C04E2A;
  --pico-primary-focus: rgba(212, 96, 58, 0.2);
  --pico-primary-inverse: #fff;
  --pico-primary-background: #D4603A;
  --pico-primary-border: #D4603A;
  --pico-primary-hover-background: #C04E2A;
  --pico-primary-hover-border: #C04E2A;

  --pico-background-color: #F6F1EB; /* warm beige */
  --pico-card-background-color: #FFFDF9;
  --pico-card-sectioning-background-color: #F0EAE2;

  --pico-color: #2B2220;            /* dark brown */
  --pico-muted-color: #7A6A60;

  --pico-border-radius: 0.5rem;     /* soft rounded */

  --pico-secondary: #4A7C59;         /* forest green */
  --pico-secondary-hover: #3D6A4A;
  --pico-secondary-focus: rgba(74, 124, 89, 0.2);
  --pico-secondary-inverse: #fff;
}

/* Rounded buttons, no sharp edges */
[data-bfx-theme="warm"] button,
[data-bfx-theme="warm"] [role="button"] {
  border-radius: 8px;
}

/* Serif display headings for warmth */
[data-bfx-theme="warm"] h1,
[data-bfx-theme="warm"] h2,
[data-bfx-theme="warm"] h3 {
  font-family: "DM Serif Display", Georgia, serif;
  font-weight: 400;
}

Result: Terracotta buttons, warm beige backgrounds, serif headings, forest green accents. Feels like a cozy, premium SaaS product.


Composite component example

Here's the same "user profile card" rendered identically in markup but looking completely different across themes. This demonstrates how one set of bluefox-components macros can produce wildly different UIs:

{# This exact markup looks different in every theme #}
{% from "bfx/card.html" import card %}
{% from "bfx/badge.html" import badge %}
{% from "bfx/button.html" import button %}

{% call card(title="Team Member") %}
  <div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;">
    <div>
      <h3 style="margin: 0;">Sarah Chen</h3>
      <small>Engineering Lead</small>
    </div>
  </div>
  <p>
    Status: {{ badge("Active", variant="success") }}
    Role: {{ badge("Admin", variant="info") }}
    Dept: {{ badge("Engineering") }}
  </p>
  <div style="display: flex; gap: 0.5rem;">
    {{ button("View Profile", variant="secondary", size="sm") }}
    {{ button("Message", size="sm") }}
  </div>
{% endcall %}
Theme Fonts Corners Buttons Feel
Playful Nunito 800 12px rounded, pill buttons Purple pills with shadow Vibrant, friendly consumer app
Financial Inter + DM Serif Display headings 4px tight Navy uppercase with tracking Trustworthy, premium banking
Minimal IBM Plex Sans 0px sharp Black uppercase, wide tracking Developer tool, brutalist
Warm Outfit + DM Serif Display headings 8px soft Terracotta rounded Cozy, inviting SaaS

The HTML never changes. Only the CSS does. This is the power of theming through CSS custom properties.


Dark mode support

Each theme can include dark mode overrides using prefers-color-scheme:

[data-bfx-theme="my-brand"] {
  /* Light mode variables */
  --pico-background-color: #FAFAF8;
  --pico-color: #1A1A18;
  --pico-primary: #D4652A;
}

@media (prefers-color-scheme: dark) {
  [data-bfx-theme="my-brand"] {
    --pico-background-color: #141413;
    --pico-color: #E8E6E1;
    --pico-primary: #E88B5A;  /* lighter for dark backgrounds */

    --pico-card-background-color: #1A1A18;
    --pico-code-background-color: #0E0E0D;
    --pico-form-element-background-color: #1A1A18;
    --pico-form-element-border-color: #333330;
    --pico-muted-border-color: #333330;
  }
}

Dark mode tip

In dark mode, make your primary color lighter (not darker) so it remains visible against dark backgrounds. Aim for a WCAG AA contrast ratio of at least 4.5:1 for text.


Tips for building themes

  1. Start from a built-in theme. Copy financial.css, playful.css, minimal.css, or roommate.css and modify it. Much faster than starting blank.

  2. Pick a Google Font pair first. The font choice drives more visual personality than any color. Try Google Fonts filtered by category.

  3. Use the showcase to preview. Mount the component showcase at /components/ and use the theme switcher to see your theme on every component at once.

  4. Match your hover states. --pico-primary-hover should be ~10-15% darker than --pico-primary. Same for background and border hover variants.

  5. Don't forget form elements. Forms are where users spend the most time. Make sure --pico-form-element-* variables feel cohesive with your card and background colors.

  6. Test on both light and dark. If you support dark mode, test every component in both. Badges and alerts especially need careful dark-mode treatment.


For LLMs: generating a theme

If you are an LLM generating a bluefox-components theme, follow this pattern:

  1. Ask the user for their brand color (hex), font preference, and mood (corporate/playful/minimal/warm).

  2. Generate the CSS file using the [data-bfx-theme="name"] selector pattern. Include ALL variables from the template in section 2 above — do not skip any.

  3. Generate structural overrides for buttons, headings, and cards based on the mood:

    • Corporate: text-transform: uppercase, serif headings, tight radius
    • Playful: border-radius: 999px on buttons, bold weights, shadows
    • Minimal: border-radius: 0, wide letter-spacing, no shadows
    • Warm: serif display headings, 8px radius, font-weight: 400
  4. Generate the Google Fonts <link> tag for any non-system fonts used.

  5. Derive colors from the primary:

    • --pico-primary-hover: 10-15% darker
    • --pico-primary-focus: primary color at 20% opacity
    • --pico-primary-background: same as primary
    • Background colors: very light tint of primary (2-5% opacity)
    • Heading colors: use primary for h1-h3, body color for h4, muted for h5-h6
    • Border colors: 10-15% opacity of text color
    • Secondary accent: complementary or analogous color on the color wheel
  6. Place the file at bluefox_components/static/bfx/themes/{name}.css.

Quick-reference for LLMs

SELECTOR:     [data-bfx-theme="THEME_NAME"] { ... }
FONT LOAD:    <link> in {% block head %}
ACTIVATION:   <html data-bfx-theme="THEME_NAME"> or theme="THEME_NAME" in render context
STRUCTURE:    Override button/heading/card with [data-bfx-theme="THEME_NAME"] element { ... }
ALL VARS:     See the Complete Variable Reference section above