Scoped rules hash a block of CSS and rename every class selector by appending a
short deterministic suffix: .foo becomes .foo_<hash>. You get back a tuple
of a class-name map and the renamed rules.
Because the suffix is derived from the rule content, identical rules always
produce the same class names. Styles are stable across builds and two
components with the same rules share one <style> block naturally.
You author these rules with the css`…` tagged template — plain CSS text
that compiles to the same [classMap, rules] tuple the underlying
scopedRules() primitive returns (see When to reach for scopedRules).
Usage
---
import { Style, css } from "@dynamic-type/ddi/server";
const [{ card }, rules] = css`
.card {
background: var(--ddi-c-bg-alt);
padding: var(--ddi-s-padding);
}
`;
---
<p class={card}>Styled content</p>
<Style rules={rules} />
The emitted HTML looks like:
<p class="card_1g91grm">Styled content</p>
<style>
.card_1g91grm {
background: var(--ddi-c-bg-alt);
padding: var(--ddi-s-padding);
}
</style>
Selectors with multiple classes, compound selectors, and descendant selectors
all work — every .identifier token in the selector is renamed:
// .foo.bar → .foo_abc.bar_abc
// div.foo → div.foo_abc
// .foo .bar → .foo_abc .bar_abc
Nested rules using & are left as-is — they are already scoped by the
renamed parent selector.
Utility classes in a block (u:)
The u: pseudo-property is the in-block form of DDI’s utility-class shorthand —
the same space-separated string you’d put in a class="..." attribute, resolved
to CSS properties and spliced into the block in place:
---
import { Style, css } from "@dynamic-type/ddi/server";
const [{ card }, rules] = css`
.card {
u: bbe:subtle p:m;
border-radius: var(--ddi-s-border-radius);
}
`;
---
<div class={card}>Content</div>
<Style rules={rules} />
Quotes around the value are optional (u: p:m and u: "p:m" parse
identically), multiple u: lines per block are allowed, and source order is
preserved. If two utility classes set the same CSS property, the later one wins:
// u: p:m p:s → padding: var(--ddi-s-s)
An unrecognized class throws, so a typo’d utility class fails loudly instead of silently producing incomplete CSS.
Interpolation
Interpolation (${…}) is allowed only inside a block, in a property or
value position — an interpolation in a selector throws. Keep interpolated
values primitive:
const [{card}, rules] = css` .card { color: ${accent}; } `;
A property that appears twice in a block is emitted twice (last one wins in the cascade) — handy for progressive-enhancement fallbacks:
// .box { width: 100%; width: fit-content; }
// → both declarations emitted, in order
When to reach for scopedRules
css`…` targets the static case. The css tag compiles to
scopedRules(rules: CSSRule[]) — the underlying primitive, which takes an array
of rule(selector, properties) objects instead of CSS text:
---
import { rule, $c, scopedRules, Style } from "@dynamic-type/ddi/server";
const [{ card }, rules] = scopedRules([
rule(".card", {
background: $c("bg-alt"),
padding: "var(--ddi-s-padding)",
}),
]);
---
<p class={card}>Styled content</p>
<Style rules={rules} />
Both forms return the identical [classMap, rules] tuple and hash the same, so
the class names match byte-for-byte. Reach for the object form when you need
something the flat CSS text can’t express: conditionals, value: false property
omission, or programmatically built media queries.
Isolation demo
Both fixtures below contain a .card element styled differently. Each css
block produces a unique hash because the rules differ — so the class names
don’t collide.
const [{ card: cardOne }, rulesOne] = css`
.card {
background: ${$c("bg")};
padding: ${$s("padding")};
}
`; <div class={cardOne}>This card gets the bg background.</div>const [{ card: cardTwo }, rulesTwo] = css`
.card {
background: ${$c("accent")};
color: ${$c("bg")};
padding: ${$s("padding")};
}
`; <div class={cardTwo}>This card gets the accent background.</div>API
css`…` — Tagged-template form. Parses a static CSS block (with the
u: pseudo-property and value-position ${…} interpolation) and returns
[Record<string, string>, CSSRule[]]. Compiles to scopedRules; for
dynamic/conditional styles, use scopedRules directly.
scopedRules(rules: CSSRule[]): [Record<string, string>, CSSRule[]]
Returns a tuple:
classMap— object mapping camelCase class name → hashed class name (e.g.{ fooBar: "foo-bar_abc1234" }). Use these values inclassorclass:listattributes.hashedRules—CSSRule[]with renamed selectors. Pass to<Style>.
<Style rules={CSSRule[]} />
Renders a raw <style> tag from a CSSRule[].
u(classNames: string): Record<string, string>
Resolves a space-separated utility-class string to a CSS properties object —
the object-form equivalent of the u: pseudo-property. Throws if any class
doesn’t match a known rule.