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 acalc()expression, which corrupts the--in a custom-property name. Pre-resolve to a literal —border-radius: 0.375rem, notcalc(var(--radius)_-_2px). Acalc()with plain numbers is fine:width: calc(100%_-_2rem). - Comma-form
rgba()for alpha. Usergba(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:3remandbackground: #fff #f1f5f9:hover. - Complex multi-value properties — gradients, multi-layer
box-shadow, and multi-propertytransition/transform— are space- and comma-heavy. Give the element a class and write those rules in a scoped<style>block instead of forcing them intocss="".
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.