Scoped & Global CSS
Styling is defined in the companion .component.css stylesheet. At compile-time, the Avenx compiler scopes component styles to keep them from bleeding into other views.
1. Scoped CSS Blocks (<@css>)
Section titled “1. Scoped CSS Blocks (<@css>)”CSS rules defined inside <@css> use named blocks without dot prefixes. The compiler extracts this CSS, hashes the block names into unique class suffixes, and binds them to the component’s HTML tags via the @css attribute.
<@css> card { padding: 1.5rem; border: 1px solid #eee;
/* Pseudo-selectors must be nested inside the named block */ &:hover { border-color: #6366f1; } }</@css><div @css card> <!-- Component Content --></div>2. Tag-based Scoped CSS (<@css />)
Section titled “2. Tag-based Scoped CSS (<@css />)”Besides the attribute syntax shown above, the compiler also recognizes a self-closing tag form of @css inside HTML templates: <@css blockName />. It applies the same generated scoped class as the attribute syntax, but where the class ends up depends on where you place the tag.
Scoping the host tag
Section titled “Scoping the host tag”When <@css blockName /> appears as the first thing inside an element, the scoped class is merged onto that host element:
<div> <@css card /> <h1>Card title</h1></div>This is equivalent to writing:
<div @css card> <h1>Card title</h1></div>Scoping the preceding sibling
Section titled “Scoping the preceding sibling”When <@css blockName /> appears immediately after an element (as its next sibling), the scoped class is applied to that preceding element instead:
<div>Card content</div><@css card />This is equivalent to writing:
<div @css card>Card content</div>Attribute vs. tag syntax
Section titled “Attribute vs. tag syntax”| Attribute syntax | Tag syntax | |
|---|---|---|
| Form | <div @css card> |
<@css card /> |
| Placement | On the element itself | As a child or sibling of the element |
| Best for | Static, hand-written templates | Templates where the target element is generated or you don’t want to touch its opening tag directly |
Both forms produce the same scoped class and can be used interchangeably; choose whichever fits your template’s structure better.
3. Scoping Limitations and Nesting Rules
Section titled “3. Scoping Limitations and Nesting Rules”Nested selectors are scoped by prefixing the generated component class. Selectors that do not use the & nesting reference are scoped directly and are not interpreted as descendant selectors.
For example, the following does not target h1 elements inside the component:
<@css> card { h1 { color: red; } }</@css>To target descendant elements, use the & nesting reference:
<@css> card { & h1 { color: red; } }</@css>Parent Selectors
Section titled “Parent Selectors”Use & to reference the current selector when applying pseudo-classes or combining selectors.
<@css> button { &:hover { background-color: #6366f1; } }</@css>Nested At-Rules
Section titled “Nested At-Rules”The & nesting reference behaves the same way inside nested at-rules such as @media, @supports, and @container.
<@css> card { @media (max-width: 768px) { & h1 { font-size: 1rem; } } }</@css>4b. Deep Scoped Selectors (:deep() & ::v-deep)
Section titled “4b. Deep Scoped Selectors (:deep() & ::v-deep)”Scoped CSS keeps rules inside the component. Use deep selectors when a parent stylesheet must style child-component DOM or slotted content across that boundary—without switching the whole block to <@global>.
StyleProcessor strips :deep(...) / ::v-deep(...) (and bare :deep / ::v-deep) at compile time, then applies the component scope hash only to the outer part of the selector. Descendants inside the deep wrapper stay unscoped.
Supported syntax
Section titled “Supported syntax”| Form | Example | Idea |
|---|---|---|
| Parenthesized modern | .card :deep(.badge) |
Prefer this form |
| Parenthesized Vue-style | .card ::v-deep(.badge) |
Same behavior |
| Combinator form | .card :deep .badge |
Space/>/+/~ after :deep |
Conceptually:
/* Source (scoped component) */.card :deep(.badge) { color: #6366f1;}compiles like:
.card[data-ax-scope-a1b2c3] .badge { color: #6366f1;}instead of attaching the scope attribute to .badge itself.
Example
Section titled “Example”<@css> card { padding: 1rem;
& :deep(.child-title) { font-weight: 600; } }</@css>Use deep selectors sparingly: they intentionally pierce encapsulation. Prefer props, slots, or CSS variables when a child can own its own styles.
4c. Inline Component CSS (static styles)
Section titled “4c. Inline Component CSS (static styles)”Besides companion .component.css / <@css> blocks, a component class may declare a static styles string. At runtime, StyleMountManager injects that CSS into a shared <style data-avenx-style="..."> element in document.head (one element per component class, reference-counted across instances).
import { AvenxComponent } from 'avenx-core/runtime';
export class Badge extends AvenxComponent { static styles = ` .badge { display: inline-block; padding: 0.15rem 0.5rem; border-radius: 999px; background: #eef2ff; color: #3730a3; } `;}Notes:
stylesmust be a non-empty string on the constructor (componentClass.styles). Empty or non-string values are ignored.- Mount increments a ref-count; unmount decrements and removes the
<style>node when no instances remain. - Prefer
<@css>/ scoped stylesheets for compile-time scoping hashes; usestatic stylesfor simple runtime-injected class CSS shared by all instances of that class.
4. Global CSS & Custom Variables (<@global>)
Section titled “4. Global CSS & Custom Variables (<@global>)”Declare global styles or design token variables using the <@global> block. Use the @def directive to define custom color codes or measurements. The compiler replaces these variables statically at build time.
<@global> @def primary-color #6366f1; @def font-sans 'Inter', sans-serif;
body { margin: 0; font-family: @font-sans; }</@global>
<@css> btn { background-color: @primary-color; color: white; }</@css>5. Native CSS Custom Properties (Variables) Scoping
Section titled “5. Native CSS Custom Properties (Variables) Scoping”In addition to @def static macros, Avenx-JS also scopes native CSS custom properties (the standard --variable-name: value; / var(--variable-name) syntax) that are declared inside a <@css> block.
When the compiler processes a <@css> block, StyleProcessor rewrites both the declaration and every var() usage of a custom property so that it is unique to that component instance:
<@css> card { --color-primary: #6366f1;
background-color: var(--color-primary);
& .title { color: var(--color-primary); } }</@css>Conceptually, this compiles to something like:
.avenx-a1b2c3d4 { --ax-a1b2c3d4-color-primary: #6366f1; background-color: var(--ax-a1b2c3d4-color-primary);}
.avenx-a1b2c3d4 .title { color: var(--ax-a1b2c3d4-color-primary);}The --ax-<hashId>- prefix is derived from the same per-component hash used to scope class selectors, so a custom property named --color-primary in one component never collides with a --color-primary declared in another component’s <@css> block, even though both are written identically in source.
Scoped vs. Global Variables
Section titled “Scoped vs. Global Variables”This automatic renaming only applies to custom properties declared inside a <@css> block. It does not apply to variables declared in <@global> or on :root, which are compiled as-is and remain globally accessible:
<@global> :root { --brand-color: #6366f1; }</@global>
<@css> card { /* Reads the global variable, unaffected by scoping */ border-color: var(--brand-color);
/* Declared and scoped locally to this component */ --card-padding: 1.5rem; padding: var(--card-padding); }</@css>Declared in <@global> / :root |
Declared inside <@css> |
|
|---|---|---|
| Renamed at compile time | No | Yes, to --ax-<hashId>-<name> |
| Visible outside the component | Yes | No — effectively private to that component |
| Typical use | Design tokens / theme variables shared across the app | Component-local values, including ones derived from props or state |
Native Variables vs. @def Macros
Section titled “Native Variables vs. @def Macros”It’s worth distinguishing the two variable systems available inside <@css> and <@global> blocks:
@defmacros (e.g.@def primary-color #6366f1;, referenced as@primary-color) are resolved by simple text substitution at compile time. The@primary-colorreference is replaced with its literal value before the CSS is emitted, so it produces no runtime CSS variable at all.- Native custom properties (e.g.
--color-primary: #6366f1;, referenced asvar(--color-primary)) remain real CSS custom properties in the compiled output. They are only renamed to avoid cross-component collisions — they still behave like normal CSS variables at runtime, including being overridable via inline styles or JavaScript.
Use @def macros for static design tokens that never need to change at runtime, and native custom properties when you need actual runtime-computed or overridable CSS variables scoped to a component.
6. Scoping Limitations and Nesting Rules
Section titled “6. Scoping Limitations and Nesting Rules”The StyleProcessor scopes selectors declared inside <@css> blocks by prepending the generated component hash to nested selectors that do not contain the nesting reference character &.
Because of this scoping behavior, descendant selectors must use & explicitly. Writing a nested selector without & does not produce a descendant selector.
Nested Selectors Without &
Section titled “Nested Selectors Without &”Consider the following scoped style:
<@css> card { h1 { color: red; } }</@css>The nested h1 selector does not contain &, so the compiler prepends the generated scope class directly to the selector (.avenx-hashh1). There is no space between the generated scope class and h1. As a result, this selector does not target an h1 element that is a descendant of the scoped card block.
Descendant Selectors With &
Section titled “Descendant Selectors With &”To target an element inside the scoped block, use the & nesting reference character:
<@css> card { & h1 { color: red; } }</@css>The & refers to the generated scoped selector (.avenx-hash). Conceptually, this compiles to .avenx-hash h1, creating a descendant selector that correctly targets h1 elements inside the scoped block.
<div @css card> <h1>Card title</h1></div>6. Deep Selectors & Target Patterns for Child Components / Slots
Section titled “6. Deep Selectors & Target Patterns for Child Components / Slots”By default, scoped component styles only apply to elements defined directly within that component’s template. When you need a parent component’s styles to affect elements inside a child component, transcluded slot content, or third-party DOM elements, use deep selector patterns with the & nesting character.
1. Targeting Transcluded Slot Content
Section titled “1. Targeting Transcluded Slot Content”Elements passed into a child component via slots are rendered inside the child, but can be styled from the parent component using & descendant selectors:
<@css> modal-wrapper { & .slot-header { font-size: 1.25rem; font-weight: bold; color: #1e293b; }
& p { line-height: 1.6; } }</@css>
<div @css modal-wrapper> <CardDialog> <template data-slot-props="slotProps"> <h2 class="slot-header">Modal Title</h2> <p>Transcluded slot body content styled by parent modal wrapper.</p> </template> </CardDialog></div>2. Targeting Child Component Elements (Deep Styling)
Section titled “2. Targeting Child Component Elements (Deep Styling)”To target elements inside a child component’s DOM tree from a parent component’s <@css> block, use & followed by the child’s class or element selector:
<@css> parent-container { /* Styles the child component's root or child nodes */ & .child-badge { background-color: #e0e7ff; color: #3730a3; border-radius: 9999px; padding: 0.25rem 0.75rem; }
/* Targets third-party or child SVG icons */ & svg { width: 1.25rem; height: 1.25rem; fill: currentColor; } }</@css>3. Combining Scoped CSS with Global Tokens
Section titled “3. Combining Scoped CSS with Global Tokens”For complete design control, combine component-scoped styles with global design tokens declared inside <@global>:
<@global> @def primary-color #6366f1; @def radius-md 8px;</@global>
<@css> badge-card { border-radius: @radius-md; border: 1px solid #cbd5e1;
& .badge-title { color: @primary-color; }
&:hover { border-color: @primary-color; } }</@css>7. CSS Preprocessors (Sass, SCSS, PostCSS, Less)
Section titled “7. CSS Preprocessors (Sass, SCSS, PostCSS, Less)”Avenx-JS has built-in integration support for modern CSS preprocessors such as Sass, SCSS, PostCSS, and Less.
Enabling a Preprocessor
Section titled “Enabling a Preprocessor”To configure a preprocessor, add the style settings configuration block in your avenx.config.json configuration file:
{ "style": { "preprocessor": "scss" }}Available preprocessor options are:
scss(SCSS syntax via Dart Sass)sass(Indented Sass syntax via Dart Sass)postcss(PostCSS processing)less(Less processing)
How It Works
Section titled “How It Works”When a preprocessor is enabled:
- Compilation: The compiler automatically passes all global style inputs inside
<@global>and scoped styling inside<@css>blocks through the corresponding preprocessor module. - Global Variables & Scope: Scoped styling blocks are wrapped in a temporary parent class wrapper during preprocessing so that local variables and nesting (e.g.
& span) resolve correctly relative to any variables or mixins defined inside<@global>. - Fallback Behavior
If the configured preprocessor package is not installed, Avenx-JS falls back to raw CSS processing and emits the AVX_W24 (COMPILER_PREPROCESSOR_MISSING) warning.
8. Debugging Scoped CSS with Source Maps
Section titled “8. Debugging Scoped CSS with Source Maps”When component styles are compiled, Avenx-JS transforms named block selectors (<@css> card </@css>) into scoped CSS classes (e.g. .avenx-a1b2c3d4) and bundles them into the final CSS output.
To trace compiled CSS rules in browser developer tools directly back to your original .component.css or Single File Component source lines:
- Enable source maps in your project’s
avenx.config.json:{"style": {"sourceMap": true}} - When inspecting elements in Chrome DevTools, Firefox Developer Tools, or Safari Web Inspector, CSS declarations point directly to the exact file and line number of the source
.component.cssfile (e.g.user-card.component.css:14) rather than line numbers inbundle.css. - In development mode or when
"sourceMap": "inline"/"inlineSourceMap": trueis set, source maps are embedded directly as base64 comments (/*# sourceMappingURL=data:application/json... */). In production builds (avenx build), separate external map files (e.g.bundle.css.map) are generated alongsidebundle.css.
8. Advanced Scoping Notes
Section titled “8. Advanced Scoping Notes”<@global> escape hatch
Section titled “<@global> escape hatch”Rules inside <@global> are not rewritten with the component scope hash. They apply application-wide (design tokens, resets, utility classes). Prefer keeping component-private rules in <@css> so they do not leak.
Reactive inline styles (data-ax-style)
Section titled “Reactive inline styles (data-ax-style)”For per-instance dynamic CSS values, use the data-ax-style template directive with a JavaScript object. Prefer scoped classes for static layout; use data-ax-style for values that change with reactive state (colors, transforms, dimensions).
Preprocessor troubleshooting
Section titled “Preprocessor troubleshooting”| Warning | Identifier | When it appears | What to do |
|---|---|---|---|
AVX_W24 |
COMPILER_PREPROCESSOR_MISSING |
Configured preprocessor package is not installed | Install the package (e.g. sass) or remove the style.preprocessor setting |
AVX_W31 |
COMPILER_PREPROCESSOR_FAILED |
Preprocessor throws (syntax error, bad hook return) | Fix the stylesheet/source; compiler falls back to raw CSS |
Full details: Compiler Warnings.