Bolt CMS Docs
Sign in

Rendering Content

Paradigm CSS

How styling works in Bolt CMS — the css="" attribute, the spaces-to-underscores rule, responsive breakpoints, states, variables, components, and the conventions that keep it predictable.

Overview

Bolt CMS styles every page with Paradigm CSS, a CSS-in-JS engine loaded from includes/paradigm.css.js. Instead of maintaining separate stylesheets, you write styles directly on each element with a css="" attribute. At load, ParadigmCSS.render() scans the DOM, gives every styled element a unique class, and injects the generated rules into a single <style> tag in the document head. There is no build step, no compiler, and no configuration file to maintain.

<div css="padding: 1.5rem; background: #ffffff; border-radius: 0.5rem;">
    <p css="font-size: 0.875rem; color: #64748b;">Styled inline</p>
</div>

Each declaration is an ordinary CSS property: value pair, and declarations are separated by semicolons — the same shape as a native style attribute. What makes Paradigm CSS different is everything below: responsive breakpoints, states, variables, and components all live inside that same attribute.

Spaces become underscores

This is the one rule that catches everyone first: any space inside a single value must be written as an underscore. The engine splits each value on spaces to find the breakpoint and state tokens described below, so a literal space inside a value would fragment the declaration. Underscores are turned back into spaces in the generated CSS.

<div css="padding: 0.75rem_1.5rem; margin: 0_auto; border: 1px_solid_#e2e8f0;">
    <p css="font-family: 'Inter',_system-ui,_sans-serif;">Body copy</p>
</div>

That markup generates:

padding: 0.75rem 1.5rem;
margin: 0 auto;
border: 1px solid #e2e8f0;
font-family: 'Inter', system-ui, sans-serif;

Commas without spaces never need underscores — rgba(15,23,42,0.6) and repeat(3,minmax(0,1fr)) are correct exactly as written.

Responsive breakpoints

To make a value responsive, add space-separated breakpoint:value tokens after the base value, all inside the same declaration. The base value applies everywhere; each prefixed token takes over from its breakpoint up.

<div css="padding: 1rem md:2rem lg:3rem;">Responsive padding</div>

<div css="grid-template-columns: repeat(1,minmax(0,1fr)) md:repeat(3,minmax(0,1fr));">
    Single column on mobile, three columns from medium up
</div>

The first declaration produces a base rule plus one rule inside each media query:

padding: 1rem;
@media (min-width: 768px) { padding: 2rem; }
@media (min-width: 992px) { padding: 3rem; }

The breakpoint labels that ship with the engine:

Label Applies at
all All screen sizes — the default when no prefix is given
sm min-width: 576px and up
md min-width: 768px and up
lg min-width: 992px and up
xl min-width: 1200px and up
-md max-width: 768px and below
-lg max-width: 992px and below
print Print media only

Breakpoints are configurable. Assign a new map to ParadigmCSS.breakpoints before render() runs to change the min-widths or add your own labels — useful when a site wants its responsive scale to match a specific design system.

States

Pseudo-state variants — hover, focus, active, and the rest — are written as value:state tokens, again space-separated after the base value. Each state becomes its own rule, so the base and the state never collide.

<a css="color: #64748b #0f172a:hover; text-decoration: none underline:hover;">
    Hover me
</a>

... generates a base rule and a :hover rule for each property:

color: #64748b;
text-decoration: none;
/* on :hover */
color: #0f172a;
text-decoration: underline;

Breakpoint and state combine on a single token in the order breakpoint:value:state:

<button css="background: #f8fafc md:#e2e8f0:hover;">Adapts and reacts</button>

Any CSS pseudo-class name works as the state token, including hover, focus, focus-visible, active, disabled, first-child, and last-child.

Variables

Define values once on ParadigmCSS.variables and reference them anywhere with a $name token. Variables are resolved during the render pass, so a single edit updates every element that uses them.

ParadigmCSS.variables = {
    brand: '#3b82f6',
    radius: '0.5rem',
    gutter: '1.5rem'
};
<button css="background: $brand; border-radius: $radius; padding: 0.5rem_$gutter;">
    Save
</button>

Components

For style bundles you reuse across many elements, register them on ParadigmCSS.components and apply them with an @name class. Each component is a list of declarations the engine expands in place.

ParadigmCSS.components = {
    card: [
        ':background:#ffffff',
        ':border:1px_solid_#e2e8f0',
        ':border-radius:0.5rem',
        ':padding:1.5rem'
    ]
};
<div class="@card">Reusable card styling, applied by name</div>

An element can combine a component class with its own one-off css="" declarations — the component supplies the shared base and the attribute layers on anything specific.

Color and design tokens

Bolt's design tokens are stored as space-separated RGB channels in CSS custom properties. Reference a token for an opaque color by wrapping it in var(--token):

<div css="background: var(--card); color: var(--foreground); border-color: var(--border);"></div>

For a translucent color, use the comma form of rgba() with the channel values and no internal spaces. Write the alpha directly into the function rather than using a slash:

<div css="background: rgba(15,23,42,0.6); border-color: rgba(226,232,240,0.75);"></div>

Conventions that keep it predictable

A short checklist that prevents the values that look fine in the markup but render wrong:

  • Underscores for internal spaces. Every space inside one value is an underscore: 0.75rem_1.5rem, 1px_solid_#e2e8f0, 'Inter',_system-ui. Spaces only ever separate breakpoint and state tokens.
  • No CSS variable inside calc(). The engine rewrites the operators in a calc() expression, which corrupts the -- in a custom-property name. Pre-resolve to a literal — border-radius: 0.375rem, not calc(var(--radius)_-_2px). A calc() with plain numbers is fine: width: calc(100%_-_2rem).
  • Comma-form rgba() for alpha. Use rgba(r,g,b,a) with no spaces; the slash syntax (rgb(... / 0.5)) contains spaces and breaks the value.
  • One token per breakpoint or state. Stack them inside a single declaration: gap: 1rem md:2rem lg:3rem and background: #fff #f1f5f9:hover.
  • Complex multi-value properties — gradients, multi-layer box-shadow, and multi-property transition/transform — are space- and comma-heavy. Give the element a class and write those rules in a scoped <style> block instead of forcing them into css="".

Targeting descendants

A ? suffix scopes a declaration to a descendant selector relative to the element. Underscores inside the selector become spaces, so you can express a child combinator:

<ul css="margin-top: 0.5rem?>_*_+_*;">
    <li>First</li>
    <li>Spaced from the one above</li>
</ul>

The token 0.5rem?>_*_+_* resolves to a rule on > * + *, applying margin-top: 0.5rem to every child except the first — a stacked-spacing helper without touching each child individually.

Rendering

ParadigmCSS.render() runs once at page load, after the DOM is ready, to generate styles from every css="" attribute. If you insert markup after load — for example from an API response — call it again so the new css="" attributes are processed:

// After inserting new HTML into the DOM
ParadigmCSS.render();

See JavaScript Libraries for the full rendering API and the page boot sequence.

Extensions Design system