Recommended CSS
Keep styling familiar: write native CSS alongside your HTML, and use bindings only for values that need to change.
Use ordinary stylesheets and native CSS. Brio does not compile, relocate, namespace, or restrict your CSS. Prefer shared external styles for global design tokens, resets, layers, and reusable classes.
Hiding templates until mount with b-cloak
Section titled “Hiding templates until mount with b-cloak”Before Brio runs, the browser paints templates as authored: ${…} text,
every b-if branch, and unpopulated lists. Add b-cloak to the mount root or
any region, and put this rule in <head> so it applies before first paint:
<style>[b-cloak] { display: none !important; }</style><main id="app" b-cloak>…</main>- Hydration removes the cloak from each element: mount roots, rows, branches,
and component templates.
b-ignorecontent is never hydrated, so its cloak stays. - Unmount restores the original attributes, including
b-cloak. - Include the CSS yourself, before first paint. Brio does not inject it; deferred or module scripts would inject it too late.
Cloaked content stays hidden if JavaScript fails or is disabled. Cloak only regions that are meaningless without Brio; keep essential content server-rendered or static.
Native scoped styles
Section titled “Native scoped styles”Put a bare @scope in an inline style to scope its rules to that style element’s
physical parent. No directive or data boundary is needed:
<div> <style> @scope { h2 { color: green; } } </style> <div> <style> @scope { h2 { color: red; } } </style> <h2>Red here!</h2> </div> <h2>Green here!</h2></div>The inner rule wins through native scoping proximity when cascade priority
and specificity tie. The scope root selector does not add specificity to its
contained rules; explicit :scope adds pseudo-class specificity and targets
the root itself. Use ordinary nesting, media/container queries, and @supports
as supported by your browsers.
The same pattern works directly inside a component template: an inline style
whose parent is the component host scopes to that host. Putting it deeper in a
wrapper scopes to the wrapper. b-data changes expression context, not CSS scope.
Projected slots follow physical placement for CSS while retaining their author
expression context. A b-template wrapper disappears; use a surviving wrapper
or explicit selector if the style needs a stable root.
Explicit roots and limits
Section titled “Explicit roots and limits”Native CSS accepts @scope (.panel) to (.limit) { ... }. Roots are ordinary
document selectors and can match multiple elements, not just the style’s parent.
The limit excludes each matching element and its descendants. To style the
limit element but not its children/subtrees, use to (.limit > *). With an
implicit parent root, write @scope to (.limit) { ... }.
For an instance-specific explicit root, combine existing Brio bindings:
<section b-id="['style']" b-bind-class="{ [$id.style]: true }"> <style> @scope (.${$id.style}) to (.limit) { h2 { color: red; } } </style> <h2>Red here</h2> <div class="limit"><h2>Outside this rule's scope</h2></div></section>b-id provides the unique name; the class binding and text interpolation use
that same name. Brio adds no separate styling metadata.
Reactive values: prefer CSS custom properties
Section titled “Reactive values: prefer CSS custom properties”Changing an inline style’s text updates its stylesheet. For changing values, prefer binding custom properties instead of regenerating CSS text:
<section b-data="{ accent: 'teal' }" b-bind-style="{ '--accent': $data.accent }"> <style> @scope { :scope { border: 1px solid var(--accent); } h2 { color: var(--accent); } } </style> <h2>Reactive color</h2> <button b-on-click="$data.accent = 'purple'">Change accent</button></section>Ordinary <style> text supports the same ${...} interpolation as other
hydrated text, including reactive references. Use it for authored selectors or
structural CSS when needed; prefer simple references such as ${$id.style} or
${$data.selector} rather than expressions with nested braces. Brio does not
escape/sanitize interpolated CSS. Do not insert untrusted values into CSS or
executable templates. To keep ${...} literal, put b-ignore on the style
element. b-text is also available when explicitly binding complete CSS
text without secondary interpolation.
Native behavior and lifetime
Section titled “Native behavior and lifetime”- Scoped selectors are not Shadow DOM isolation. Global CSS can still match descendants; inherited properties and custom properties cross scope limits. Teleported content outside a scope stops matching its rules.
- Keyframe and cascade-layer names follow native document-global CSS naming,
even when authored inside
@scope. Choose distinctive names such asmy-app-card-pulse, and use CSS variables for animation parameters as needed. Brio does not rewrite animations or impose serialization restrictions. - Inline styles stay where authored, with their attributes and formatting. A hidden connected branch’s stylesheet remains active. A detached branch’s inline stylesheet disconnects and stops applying until reinsertion. This also affects rules targeting explicit roots or teleported elements elsewhere.
- Default
b-ifkeeps inline styles during leave transitions and removes them with the branch when leave completes. Managed insertion/removal likewise follows the actual DOM. Unmount stops Brio bindings and restores original markup; static CSS in that restored markup can remain active. - Dispose Brio scopes through its lifecycle APIs instead of deleting owned DOM manually. CSS connectivity is native, but reactive bindings still need cleanup.
- Browser CSS parsing and support apply normally: there is no framework fallback
or capability error for
@scope. Support starts at Chromium 118, Firefox 146, and Safari 17.4, with a reported Safari 26.0–26.3 input/textarea regression fixed in 26.4. Check your target browsers and provide ordinary global-class styles when broader compatibility is required. CSP inline-style rules still apply.