Skip to the solution
EasyFrontend10 min read

What are scoped styles in Vue?

649 views

The problem

Question presented to candidate: "We use <style scoped> everywhere, yet a .title rule in a parent still changed a child component's root, and our attempt to restyle a third-party component's inner element did nothing. Explain how scoped styles actually work, and how you would fix both situations."

What a strong answer should cover:

  • <style scoped> is a compile-time transform: every element the component renders gets a data-v-<hash> attribute, and every selector gets [data-v-<hash>] added to its last compound selector.
  • A child component's root element carries both the parent's and the child's attribute, so parent rules can reach it (by design, for layout).
  • To reach inside a child, use :deep(.x); to style slot content from the component that renders the slot, :slotted(); to escape scoping for one rule, :global().
  • Content from v-html and multi-root children do not get the parent's attribute.
  • v-bind() in <style> turns a reactive value into a CSS custom property.
  • Alternatives: CSS Modules (<style module>), utility classes; scoping is not Shadow DOM.

Clarifying questions expected:

  • "Is the third-party component's markup rendered by a Vue component (so :deep works) or injected HTML?"

Code / implementation expected: Optional. Reading the compiled selectors, or a small component showing :deep(), is a strong bonus.

  • scoped-styles

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

10 min read Β· 11 sections

Read the code

VueScoped styles, child root leakage, :deep() and v-bind() in CSS
<script setup>
import { ref, defineComponent, h } from "vue";

// A child component with no styles of its own: a root div with a <p> inside
const Child = defineComponent({
  setup() {
    return () => h("div", { class: "box" }, [h("p", { class: "note" }, "inside the child")]);
  },
});

const accent = ref("crimson");
</script>

<template>
  <p class="note">in the parent</p>
  <Child />
  <button @click="accent = accent === 'crimson' ? 'teal' : 'crimson'">swap colour</button>
</template>

<style scoped>
/* Compiled to .note[data-v-xxxx]: only elements rendered by THIS component match */
.note {
  color: v-bind(accent);
  font-weight: bold;
}
/* The child ROOT element also carries this component scope attribute, so this matches it */
.box {
  border: 2px dashed gray;
  padding: 8px;
}
/* :deep() reaches inside the child */
.box :deep(.note) {
  font-style: italic;
}
</style>
Compiling scoped CSS with @vue/compiler-sfc compileStyle()
import { compileStyle, parse, compileScript } from "@vue/compiler-sfc";
const id = "data-v-7ba5bd90";
const css = `.card { padding: 16px; }
.card h2 { color: teal; }
.card :deep(.title) { font-weight: bold; }
:slotted(p) { margin: 0; }
:global(body) { margin: 0; }
.card > .badge:hover { opacity: 0.8; }
@media (max-width: 600px) { .card { padding: 8px; } }
.btn { color: v-bind(color); }`;
const r = compileStyle({ source: css, filename: "Card.vue", id, scoped: true });
console.log(r.code);
console.log("errors:", r.errors.map(String));
const warn = console.warn; console.warn = (...a) => console.log("WARN:", ...a.map(String));
const old = compileStyle({ source: `.a >>> .b { color: red } .a /deep/ .c { color: blue } .a ::v-deep .d { x: y }`, filename: "Old.vue", id, scoped: true });
console.warn = warn;
console.log(old.code);

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 13 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextEasy

What is a Single File Component (SFC)?

Continue