Scoped Styles

A component's <style> block only reaches that component's own elements. Write .btn in one file and it cannot touch .btn in another.

No BEM, no CSS modules, no prefixes.

<!-- SaveButton.html -->
<button class="btn">Save</button>

Another component's .btn is simply a different .btn:

<!-- CancelButton.html -->
<button class="btn">Cancel</button>

Put both on a page and neither one wins:

output

How it works

Each component gets a hash class of its own, like jaf-x7k2m.

The class is added to every element in the component's markup, and to every part of every selector in its styles:

source
/* What you write */
.card { border: 1px solid gray; }
.card .title { font-size: 1.5rem; }
.btn:hover { background: navy; }

/* What the browser gets */
.card.jaf-x7k2m { border: 1px solid gray; }
.card.jaf-x7k2m .title.jaf-x7k2m { font-size: 1.5rem; }
.btn.jaf-x7k2m:hover { background: navy; }

That is the whole mechanism.

Pseudo-classes, pseudo-elements, media queries and nesting all behave normally, because nothing else about your CSS changes.

Plain tag selectors are safe too

p becomes p.jaf-x7k2m, so styling a bare tag is a local decision rather than a site-wide one:

<p>Only this component's paragraphs turn grey.</p>
output

Only this component's paragraphs turn grey.

The same goes for *: a component's * { margin: 0 } becomes *.jaf-x7k2m and resets its own elements, not the document's.

What scoping does not reach

Two selectors are left alone, because they address elements outside the component: anything starting with :root, and anything starting with :host.

Two kinds of element never get the hash class, so scoped rules cannot style them:

  • Slot content - the markup came from whoever used your component, and carries their hash.
  • Child components - a <Card /> tag in your markup keeps its own scope. Style it from inside Card, or pass it a prop.
Need to cross that line? <style global> is the deliberate escape, and it is what you want for slot content, resets and themes. See Global Styles.