Skip to main content

CSS Variables

Bulma v1 introduces comprehensive support for CSS custom properties (CSS variables), enabling runtime customization without Sass compilation. bestax-bulma gives you a first-class React surface for all 500+ of them through the Theme component: named, camelCase props for the common variables, a bulmaVars escape hatch for the long tail, and automatic scoping so themes compose via React context — no manual style-sheet injection, no string concatenation, fully type-checked.

Bulma's canonical reference

The authoritative documentation for every Bulma CSS variable lives on the Bulma site:

  • CSS Variables in Bulma — how Bulma uses CSS variables and their --bulma- prefix.
  • Themes — Bulma's theme system (a theme is a set of CSS variables scoped at :root or below).
  • Dark Mode — the prefers-color-scheme and .theme-dark conventions Bulma ships.

This page focuses on the React surface (Theme, bulmaVars); refer to Bulma's docs for the variable catalog and CSS-level semantics.

What are CSS Variables?​

CSS variables (officially called CSS custom properties) are entities defined by CSS authors that contain specific values to be reused throughout a document. Unlike Sass variables, which are compile-time constants, CSS variables are runtime values that can be:

  • Changed dynamically with JavaScript
  • Inherited through the CSS cascade
  • Scoped to specific DOM elements
  • Modified without rebuilding your CSS

Runtime Customization​

Browser DevTools​

You can modify Bulma's appearance directly in browser developer tools by changing CSS variable values. This is powerful for testing and debugging:

Steps to test:

  1. Open your browser's developer tools
  2. Select the html element in the Elements panel
  3. In the Styles panel, add or modify a CSS variable like --bulma-primary-h: 270deg
  4. Watch the changes apply instantly across your entire application
/* Example: Change primary color to purple in DevTools */
:root {
--bulma-primary-h: 270deg;
--bulma-primary-s: 100%;
--bulma-primary-l: 50%;
}

JavaScript Runtime Changes​

CSS variables can be modified programmatically:

// Change primary color dynamically
document.documentElement.style.setProperty('--bulma-primary-h', '120deg');

// Change theme scheme
document.documentElement.style.setProperty('--bulma-scheme-h', '210deg');

Using the Theme Component​

Theme is bestax's React wrapper for Bulma's CSS variables. Two things make it idiomatic to use from React rather than hand-writing style.setProperty calls:

  • Named props for the common variables — primaryH, schemeH, radius, familyPrimary, etc. TypeScript autocompletes them and catches typos at build time.
  • bulmaVars for everything else — a single object prop that accepts raw CSS variable names, so you never hit a ceiling.

Themes nest naturally: outer <Theme> sets app-wide defaults, inner ones scope overrides to a subtree. Use isRoot to inject variables at :root for true app-wide reach.

Global Theme Application​

import { Theme, Button, Box, Title } from '@allxsmith/bestax-bulma';

function App() {
return (
<Theme
isRoot
primaryH="270"
primaryS="100%"
primaryL="50%"
schemeH="260"
schemeS="30%"
>
<Box p="4">
<Title>Purple-themed Application</Title>
<Button color="primary">Purple Button</Button>
</Box>
</Theme>
);
}

Scoped Theme Application​

function ScopedTheming() {
return (
<div>
<Title>Standard Theme</Title>

<Theme primaryH="120" primaryS="100%" primaryL="40%">
<Box p="4" mt="3">
<Title size="4">Green Section</Title>
<Button color="primary">Green Button</Button>
</Box>
</Theme>

<Theme primaryH="15" primaryS="85%" primaryL="55%">
<Box p="4" mt="3">
<Title size="4">Orange Section</Title>
<Button color="primary">Orange Button</Button>
</Box>
</Theme>
</div>
);
}

Using bulmaVars Object​

For less common variables or when you have many to set:

function AdvancedTheming() {
const customTheme = {
'--bulma-family-primary': '"Helvetica Neue", sans-serif',
'--bulma-family-code': '"Fira Code", monospace',
'--bulma-size-normal': '16px',
'--bulma-weight-bold': '700',
'--bulma-title-color': 'hsl(0, 0%, 21%)',
'--bulma-subtitle-color': 'hsl(0, 0%, 48%)',
'--bulma-card-shadow': '0 8px 32px rgba(0, 0, 0, 0.1)',
'--bulma-button-border-radius': '12px',
};

return (
<Theme bulmaVars={customTheme}>
<Box p="4">
<Title>Custom Typography & Styling</Title>
<Button color="primary">Custom Styled Button</Button>
</Box>
</Theme>
);
}

Complete CSS Variables Catalog​

Bulma v1 provides 500+ CSS variables organized by category. Here are the key categories — for the full, authoritative list see the Bulma CSS Variables reference and the per-component pages under bulma.io/documentation.

Scheme Variables​

VariableDescriptionExample Value
--bulma-scheme-hBase hue for color scheme210
--bulma-scheme-sBase saturation50%
--bulma-light-lLight background lightness96%
--bulma-dark-lDark background lightness4%
--bulma-soft-lSoft color lightness85%
--bulma-bold-lBold color lightness15%

Color Variables​

VariableDescriptionExample Value
--bulma-primary-hPrimary color hue171
--bulma-primary-sPrimary color saturation100%
--bulma-primary-lPrimary color lightness41%
--bulma-link-hLink color hue233
--bulma-info-hInfo color hue198
--bulma-success-hSuccess color hue153
--bulma-warning-hWarning color hue42
--bulma-danger-hDanger color hue348

Typography Variables​

VariableDescriptionExample Value
--bulma-family-primaryPrimary font family'BlinkMacSystemFont', 'Segoe UI', 'Roboto'
--bulma-family-codeCode font family'Source Code Pro', monospace
--bulma-size-normalNormal text size1rem
--bulma-weight-normalNormal font weight400
--bulma-weight-boldBold font weight700

Layout Variables​

VariableDescriptionExample Value
--bulma-block-spacingBlock element spacing1.5rem
--bulma-radiusDefault border radius4px
--bulma-radius-roundedRounded border radius9999px
--bulma-column-gapColumn spacing0.75rem
--bulma-grid-gapGrid spacing1rem

Component-Specific Variables​

Button Variables​

VariableDescription
--bulma-button-padding-verticalButton vertical padding
--bulma-button-padding-horizontalButton horizontal padding
--bulma-button-border-radiusButton border radius
--bulma-button-focus-box-shadow-sizeFocus shadow size

Card Variables​

VariableDescription
--bulma-card-colorCard text color
--bulma-card-background-colorCard background
--bulma-card-shadowCard drop shadow
--bulma-card-radiusCard border radius

Input Variables​

VariableDescription
--bulma-input-color-lInput text lightness
--bulma-input-background-lInput background lightness
--bulma-input-border-lInput border lightness
--bulma-input-focus-shadow-sizeFocus shadow size

Dynamic Theming Examples​

Dark Mode Toggle​

Bulma ships a built-in dark-mode scheme. The simplest way to drive it is the Theme component's colorMode prop ('light' | 'dark' | 'system'), which writes Bulma's data-theme attribute on <html> for you. 'system' follows the OS prefers-color-scheme.

function DarkModeApp() {
const [mode, setMode] = useState<'light' | 'dark' | 'system'>('system');

return (
<Theme isRoot colorMode={mode}>
<Box p="4">
<Title>Dynamic Dark Mode</Title>
<Button onClick={() => setMode('dark')} color="primary">
Dark
</Button>
<Button onClick={() => setMode('light')} color="primary">
Light
</Button>
<Button onClick={() => setMode('system')}>System</Button>
</Box>
</Theme>
);
}

For full control you can instead override the scheme variables yourself via bulmaVars (e.g. --bulma-scheme-h, --bulma-light-l, --bulma-dark-l), but colorMode covers the common case.

Color Palette Generator​

function ColorPaletteApp() {
const [hue, setHue] = useState(171);

return (
<Theme primaryH={hue.toString()} isRoot>
<Box p="4">
<Title>Dynamic Color Palette</Title>
<input
type="range"
min="0"
max="360"
value={hue}
onChange={e => setHue(parseInt(e.target.value))}
/>
<p>Hue: {hue}°</p>

<div style={{ display: 'flex', gap: '1rem', marginTop: '1rem' }}>
<Button color="primary">Primary</Button>
<Button color="info">Info</Button>
<Button color="success">Success</Button>
<Button color="warning">Warning</Button>
<Button color="danger">Danger</Button>
</div>
</Box>
</Theme>
);
}

Advantages Over Sass Variables​

Build-time vs Runtime​

/* Sass variables (build-time only) */
$primary: #ff6b35;
$family-primary: 'Helvetica Neue', sans-serif;

@import 'bulma/bulma.sass';
/* CSS variables (runtime modifiable) */
:root {
--bulma-primary-h: 18deg;
--bulma-primary-s: 100%;
--bulma-primary-l: 60%;
--bulma-family-primary: '"Helvetica Neue", sans-serif';
}

Benefits of CSS Variables​

  1. Runtime modification: Change themes without rebuilding CSS
  2. User preferences: Allow users to customize appearance
  3. Context-aware theming: Different themes for different sections
  4. Performance: No need for multiple CSS bundles
  5. Testing: Easy to test different color schemes
  6. Debugging: Modify values in DevTools for instant feedback

Dark Mode & Contrast​

Bulma's scheme variables (--bulma-text, --bulma-scheme-main, …) flip automatically when data-theme="dark" is present — and when no data-theme attribute has been set at all (the usual case for an app that never configured colorMode), they follow the visitor's OS prefers-color-scheme. Dark mode is effectively on by default, even for designs that never intended to support it.

That creates a silent contrast trap the moment you introduce your own fixed color tokens: on a dark-mode machine, Bulma's text goes near-white while your fixed light backgrounds stay light — white text on cream, unreadable, and invisible to you unless your own OS is in dark mode.

Apply exactly one of these rules:

Rule 1 — single-mode design: lock the scheme. If the design is light-only (or dark-only), pin it at the app root so an OS preference can never invert text out from under your palette:

<Theme isRoot colorMode="light">
<App />
</Theme>

Rule 2 — both modes: never expose a fixed token to the flip. Derive your tokens from Bulma's scheme variables, or provide the dark-mode pair yourself:

/* Preferred: your tokens track the scheme automatically. */
:root {
--my-canvas: var(--bulma-scheme-main);
--my-ink: var(--bulma-text);
}

/* Or keep custom values, but flip them for BOTH ways dark mode arrives:
the explicit attribute (colorMode="dark")… */
[data-theme='dark'] {
--my-canvas: #14251b;
--my-ink: #eef3e7;
}

/* …and the OS preference, which applies when no data-theme is set
(colorMode="system" removes the attribute): */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) {
--my-canvas: #14251b;
--my-ink: #eef3e7;
}
}

This is exactly why deriving from the scheme variables is the preferred form — one line, and both dark-mode paths are covered automatically.

Corollary — fixed-color surfaces need fixed-color content. A surface that never flips (a dark hero, a brand banner) must pin its content's colors too: filled buttons (color="light", or color="primary" isInverted) and explicit text colors — not thin outlined buttons or scheme-derived defaults, which wash out when the surrounding scheme flips.

Best Practices​

Organization​

Group related variables for better maintainability:

const brandTheme = {
// Brand colors
'--bulma-primary-h': '210',
'--bulma-primary-s': '100%',
'--bulma-primary-l': '50%',

// Typography
'--bulma-family-primary': '"Inter", sans-serif',
'--bulma-weight-normal': '400',
'--bulma-weight-bold': '600',

// Layout
'--bulma-radius': '8px',
'--bulma-block-spacing': '2rem',
};

Scope​

  • Prefer a single isRoot Theme at the top of your tree for global values.
  • Use nested, non-root Theme elements only to override a specific subtree (a dashboard section, a marketing block) — each non-root theme wraps its children in a <div>, so don't nest one around a single button when a helper prop would do.

Consistency​

  • Establish a design system with your CSS variables
  • Document which variables your team uses most commonly
  • Create reusable theme objects for common scenarios

Further Reading​