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¶
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:
Or in Jinja2 via the base template:
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;
}
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¶
-
Start from a built-in theme. Copy
financial.css,playful.css,minimal.css, orroommate.cssand modify it. Much faster than starting blank. -
Pick a Google Font pair first. The font choice drives more visual personality than any color. Try Google Fonts filtered by category.
-
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. -
Match your hover states.
--pico-primary-hovershould be ~10-15% darker than--pico-primary. Same for background and border hover variants. -
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. -
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:
-
Ask the user for their brand color (hex), font preference, and mood (corporate/playful/minimal/warm).
-
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. -
Generate structural overrides for buttons, headings, and cards based on the mood:
- Corporate:
text-transform: uppercase, serif headings, tight radius - Playful:
border-radius: 999pxon buttons, bold weights, shadows - Minimal:
border-radius: 0, wide letter-spacing, no shadows - Warm: serif display headings,
8pxradius,font-weight: 400
- Corporate:
-
Generate the Google Fonts
<link>tag for any non-system fonts used. -
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
-
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