cem-element demo
CSS scoping contract
Executable samples for private, named-shared, instance, static, fragment, and anonymous CSS ownership.
Keep component CSS local while preserving intentional sharing
These cases show private declaration CSS, named shared groups,
per-instance payload overrides, static-style enforcement,
fragment and external templates, anonymous declarations, descendant
selectors, and stable keyframe rewriting.
Declaration styles are compiled into native @scope
boundaries. Ordinary page cascade can still enter a component, while
a declaration selector cannot leak into unrelated page elements.
See also
External template loading
Grid components and instance styling
Module maps and resource resolution
This page enables retained CSS loading. The final two samples load
a local stylesheet and resolve its image through the nearest module map.
{style |```
:host { --sample-cascade-color: green; }
button
{ border: 0.2rem dashed green;
color: var(--sample-cascade-color);
} ```
}
{button @type=button | {slot | DCE dashed border}}
First DCE dashed border
Second DCE dashed border
Browser default border
{style |```
:host { --shared-bare-color: green; }
.sample-shared-bare { color: var(--shared-bare-color); } ```
}
{b @class=sample-shared-bare | shared bare}
{b @class=sample-shared-bare | shared peer}
{style @scope="css-samples" |```
:host { --shared-explicit-bg: rgb(219, 234, 254); }
.shared-bg { background-color: var(--shared-explicit-bg); } ```
}
{span @class=shared-bg | {b shared-bg} class defined }
{span @class=shared-bg | Use of {b shared-bg} class }
{style |```
.sample-mixed { border: 0.2rem solid blue; } ```
}
{style @scope="css-samples" |```
.sample-mixed-shared { color: red; } ```
}
{span @class="sample-mixed sample-mixed-shared" | blue border, red text}
{span @class=sample-mixed-shared | red text}
{style @scope="css-samples" |```
.must-not-apply { color: red; } ```
}
{span @class=must-not-apply |
not red as owning {code cem-element} declaration has no matching scope }
{style @scope="other-lib" |```
.must-not-apply { color: red; } ```
}
{span @class=must-not-apply | mismatch rejected, DCE and style scope value differ }
{style |```
.sample-valid-bare { color: green; } ```
}
{style @scope="other-lib" |```
.must-not-apply { color: green; } ```
}
{span @class="sample-valid-bare must-not-apply" | valid bare remains shared}
{style |```
.sample-invalid-declaration { color: blue; } ```
}
{span @class=sample-invalid-declaration | invalid declaration falls back private}
{style |```
:host { --instance-border: blue; }
button {
border: 0.2rem solid var(--instance-border);
} ```
}
{button @type=button |
{slot | instance}
}
blue
red
{module |
{slice @name=scope | css-samples}
{body |
{style @scope="{$scope}" |```
.must-not-apply { color: red; } ```
}
{cem:if @test=scope |
{style |```
.must-not-apply { background: red; } ```
}
}
{span @class=must-not-apply | dynamic styles rejected}
}
}
{style |```
.sample-fragment {
background-color: rgb(254, 243, 199);
border: 0.2rem solid rgb(180, 83, 9);
} ```
}
{span @class=sample-fragment | fragment}
{style |```
.sample-anonymous { color: violet; } ```
}
{b @class=sample-anonymous | anonymous violet}
{style |```
@keyframes seeded-pulse {
from { opacity: 0.35; transform: scale(0.96); }
to { opacity: 1; transform: scale(1); }
}
[part~="indicator"] {
display: inline-block;
animation: seeded-pulse 800ms ease-in-out infinite alternate;
} ```
}
{span @part=indicator | deterministic keyframes}
{style |```
:host { color: rgb(0, 128, 0); }
b { color: rgb(0, 0, 255); }
input:checked + b {
color: rgb(0, 0, 139);
text-shadow: 0 0 4px springgreen;
}
```}
{label |
green
{input @type=checkbox @checked=true}
{b | glowing blue}
}
projected external template
{attribute @name=blur | 0}
{style |```
[part~="preview"] { filter: var(--sample-filter); }
svg { position: absolute; width: 0; height: 0; }
```}
{div @part=preview
@style='--sample-filter: url("#{$instanceID}-filter")' |
{slot | Filter preview}}
{cem:variable @name=svgNS @select='"http://www.w3.org/2000/svg"' }
{cem:element @name=svg @namespace="{$svgNS}" |
{cem:attribute @name=aria-hidden @value=true}
{cem:element @name=filter @namespace="{$svgNS}" |
{cem:attribute @name=id @value="{$instanceID}-filter"}
{cem:element @name=feGaussianBlur @namespace="{$svgNS}" |
{cem:attribute @name=stdDeviation @value="{$blur}"}
} } }
Sharp text
Blurred text
{style |``` @import "card-theme"; ```}
{p | Mapped defaults}
{module-map |
{resource @specifier=card-theme @target="./scoped-css-default.css" @content-type="text/css"}
{resource @specifier=card-icon @target="./lib-dir/Smiley.svg" @content-type="image/svg+xml"}}
{cem-css-default-card}
{style |``` @import "card-theme"; ```}
{p | Nested override}
{module-map |
{resource @specifier=card-theme @target="./scoped-css-override.css" @content-type="text/css"}
{resource @specifier=card-icon @target="./confused.svg" @content-type="image/svg+xml"}}
{cem-css-override-card}
{module-map |
{resource @specifier=card-theme @target="./scoped-css-default.css" @content-type="text/css"}
{resource @specifier=card-icon @target="./lib-dir/Smiley.svg" @content-type="image/svg+xml"}}
{cem-css-override-frame}
Private CSS uses native @scope (<cem-tag>); named shared CSS uses the public
declaration-owned scope="name"; payload CSS uses an implicit parent-rooted
@scope. An explicit uid-seed stabilizes internal render identity and
rewritten keyframe names; it does not create a CSS boundary. Internal
data-cem-render-scope is not a CSS hook, and no per-instance CSS marker is generated.