Vue에서 scoped 스타일은 해당 컴포넌트의 요소에만 스타일이 적용되도록 제한하지만, 실제로는 이름이 충돌할 경우 특정 규칙에 따라 우선순위가 정해집니다.
HTML+CSS 처럼 cascade 방식으로 스타일이 적용될 줄 알았던 코드가 뱉은 오류와 해결방법을 정리해봅니다.
App.vue
<template>
<ParentComponent />
</template>
Parent component
<template>
<ChildComponent />
<AnotherComponent />
</template>
<script setup>
import ChildComponent from '@/components/ChildComponent.vue'
import AnotherComponent from '@/components/AnotherComponent.vue'
</script>
<style lang="scss" scoped>
.child-wrap {
background-color: rgba(blue, 0.3);
.h1 {
color: blue;
}
}
</style>
Child component
<template>
<div class="child-wrap box">
<div class="h1">CHILD COMPONENT</div>
</div>
</template>
<script setup>
import AnotherComponent from '@/components/AnotherComponent.vue'
</script>
<style lang="scss" scoped>
.child-wrap {
background-color: rgba(red, 0.3);
.h1 {
color: red;
}
}
</style>
Another Component
<template>
<div class="box">
<div>Another COMPONENT</div>
</div>
</template>

div.child-wrap
1. ParentComponent.vue의 background-color: rgba(blue, 0.3) 출력
2. ChildComponent.vue의 background-color: rgba(red, 0.3) 출력
3. ParentComponent.vue의 background-color: rgba(blue, 0.3)가 우선순위를 가지고 출력됨
div.h1
color: red 출력Vue에서 scoped 스타일은 해당 컴포넌트의 요소에만 스타일이 적용되도록 제한하지만, 실제로는 이름이 충돌할 경우 특정 규칙에 따라 우선순위가 정해집니다. 이 경우, ParentComponent.vue와 ChildComponent.vue 모두 .child-wrap 클래스를 사용하고 있지만, 각 컴포넌트의 스타일이 scoped로 적용되므로, 실제로 적용되는 스타일의 우선순위는 CSS 셀렉터의 특이성(specificity) 및 선언 순서에 따라 달라집니다.
ParentComponent.vue의 스타일은 부모 컴포넌트에서 자식 컴포넌트인ChildComponent를 감싸는.child-wrap요소에 적용됩니다.ChildComponent.vue에서도.child-wrap클래스가 선언되어 있으므로, 이 클래스에 대한 스타일이 자식 컴포넌트에 영향을 미칩니다.
우선순위가 결정되는 방식
scoped스타일은 각 컴포넌트마다 고유한 클래스 이름을 자동으로 생성하여 스타일을 적용합니다.- 이때
ParentComponent.vue에서 선언한 스타일은 부모 컴포넌트에서 자식 컴포넌트를 감싸고 있는 요소에 적용됩니다.ChildComponent.vue의 스타일은 해당 컴포넌트에만 영향을 미치기 때문에,ParentComponent.vue에서 설정한background-color는 여전히 자식 컴포넌트의.child-wrap요소에 상속되지 않고 겹칠 수 있습니다.
따라서 부모 스타일이 더 우선시되는 이유는 ParentComponent.vue의 background-color: rgba(blue, 0.3);가 자식 컴포넌트에서 적용되는 ChildComponent.vue의 background-color: rgba(red, 0.3);보다 더 늦게 적용되기 때문입니다.
여기서 발생하는 이유는 CSS 셀렉터의 특이성(specificity) 때문입니다. 각 컴포넌트의 scoped 스타일은 기본적으로 해당 컴포넌트에만 적용되도록 만들어집니다. 그리고 각 컴포넌트에서 선언된 스타일의 특이성도 다를 수 있기 때문에, 특정 스타일이 더 우선시될 수 있습니다.
ParentComponent.vue에서.h1의color를blue로 설정했으나,ChildComponent.vue에서는.h1의color를red로 설정했습니다.ChildComponent.vue에서 선언된color: red는scoped스타일로 적용되기 때문에ChildComponent내의.h1요소에만 영향을 미칩니다.ParentComponent.vue에서의 스타일도scoped로 적용되지만, 이 스타일은 자식 컴포넌트의.h1에 영향을 미치지 않습니다.
즉, ParentComponent.vue의 스타일이 적용되지 않는 이유는 부모 컴포넌트의 스타일이 자식 컴포넌트에 영향을 미치지 않기 때문입니다. 각 컴포넌트의 scoped 스타일은 기본적으로 해당 컴포넌트에만 국한되므로, 자식 컴포넌트인 ChildComponent.vue에서는 ChildComponent의 .h1에만 red 색상이 적용됩니다.

Parent component
<template>
<ChildComponent />
</template>
<script setup>
import ChildComponent from '@/components/ChildComponent.vue'
</script>
<style lang="scss" scoped>
.child-wrap {
background-color: rgba(blue, 0.3);
.h1 {
color: blue;
}
}
</style>
Child component
<template>
<div class="child-wrap box">
<div class="h1">CHILD COMPONENT</div>
</div>
<AnotherComponent />
</template>
<script setup>
import AnotherComponent from '@/components/AnotherComponent.vue'
</script>
<style lang="scss" scoped>
.child-wrap {
background-color: rgba(red, 0.3);
.h1 {
color: red;
}
}
</style>
div.child-wrap
1. ParentComponent.vue의 스타일은 출력되지 않음
2. ChildComponent.vue의 background-color: rgba(red, 0.3) 만 출력
div.h1
color: red 출력기본 구조 (ChildComponent에 <AnotherComponent />가 없을 때):
ParentComponent.vue에서 .child-wrap의 background-color를 rgba(blue, 0.3)로 설정하고 있습니다.ChildComponent.vue에서 .child-wrap의 background-color를 rgba(red, 0.3)로 설정하고 있습니다.이때 ChildComponent 내부에는 <AnotherComponent />가 포함되어 있지 않으므로, ParentComponent.vue의 스타일 (background-color: rgba(blue, 0.3))이 ChildComponent.vue의 .child-wrap에 적용됩니다. 이 스타일은 부모 컴포넌트인 ParentComponent.vue에서 정의한 것이지만, ChildComponent의 DOM 요소에 scoped 스타일을 적용할 때 부모 스타일도 덮어쓰지 않고 그대로 적용됩니다.
<AnotherComponent />가 ChildComponent에 추가된 경우:
ChildComponent.vue 내에 <AnotherComponent />가 추가되었습니다.<AnotherComponent />의 추가가 ParentComponent.vue의 스타일에 영향을 주지는 않습니다. 그럼에도 불구하고, ChildComponent.vue 내에서 .child-wrap에 적용된 background-color: rgba(red, 0.3)가 최종적으로 우선시됩니다.scoped 스타일은 기본적으로 해당 컴포넌트 내의 DOM에만 스타일을 적용하게 되어 있습니다. 즉, ParentComponent.vue의 스타일은 ParentComponent.vue의 DOM에만 영향을 미치며, ChildComponent.vue 내에서 .child-wrap 요소에 대한 스타일은 ChildComponent.vue 내에서 선언된 background-color: rgba(red, 0.3)로 덮어씌워집니다.ChildComponent.vue의 .child-wrap 스타일이 자식 컴포넌트에서 AnotherComponent를 추가했는지 여부와는 관계없이 계속 적용됩니다.ChildComponent.vue에서 .child-wrap 스타일을 background-color: rgba(red, 0.3)로 정의하고 있기 때문에 ChildComponent.vue의 스타일이 우선시됩니다. 이는 scoped 스타일의 우선순위에 따른 결과입니다. ParentComponent.vue의 스타일은 자식 컴포넌트인 ChildComponent에 영향을 미치지 않으며 자식 컴포넌트에서 정의된 스타일이 덮어씌워지게 됩니다.
ParentComponent.vue의 background-color: rgba(blue, 0.3)는 ParentComponent의 스타일로, ChildComponent.vue 내의 .child-wrap 요소에 직접 영향을 미치지 않습니다.ChildComponent.vue 내에서 .child-wrap의 스타일을 background-color: rgba(red, 0.3)로 설정했기 때문에, 이 스타일이 최종적으로 적용됩니다.<AnotherComponent />의 추가는 ParentComponent.vue의 스타일에 영향을 주지 않으며, ChildComponent.vue의 스타일만 적용됩니다.결론적으로,
AnotherComponent를 추가하더라도ChildComponent.vue의.child-wrap스타일에 영향을 미치지 않으며,ChildComponent.vue의 스타일인background-color: rgba(red, 0.3)만 적용됩니다.
data-v-parent)을 자식 컴포넌트의 루트 요소에 추가합니다.data-v-child)만 가지게 되며, 부모의 스코프 속성은 자동으로 추가되지 않습니다.이러한 동작의 이유는 Vue가 컴포넌트 간의 스타일 격리를 유지하면서도, 단일 자식 컴포넌트의 경우 부모-자식 관계를 좀 더 밀접하게 유지하려는 의도 때문입니다.따라서, 컴포넌트 구조를 변경하면(예: 단일 자식에서 여러 자식으로, 또는 그 반대로) 스코프된 스타일의 적용 방식이 달라질 수 있습니다. 이는 Vue의 설계된 동작이며, 개발자가 컴포넌트 구조와 스타일 적용을 더 세밀하게 제어할 수 있게 해줍니다.
<style scoped>를 사용하면, 해당 컴포넌트의 요소들에 고유한 속성(예: data-v-123abc)을 추가합니다. 그리고 CSS 선택자에도 이 속성을 추가하여 스타일이 해당 컴포넌트에만 적용되도록 합니다.css.childWrap[data-v-parent] { background-color: rgba(blue, 0.3); }css.childWrap[data-v-child] { background-color: rgba(red, 0.3); }.childWrap 요소는 data-v-child 속성을 가집니다.data-v-parent 속성을 찾지만, ChildComponent 내부에는 이 속성이 없으므로 적용되지 않습니다.data-v-parent 속성을 추가했습니다. 그래서 ParentComponent의 스타일이 일부 적용될 수 있었습니다..childWrap을 정의하고 있어, 이 요소는 오직 data-v-child 속성만 가지게 됩니다. 따라서 ParentComponent의 스타일은 더 이상 영향을 미치지 않습니다.요약하면, Vue의 스코프된 스타일 시스템으로 인해 각 컴포넌트의 스타일이 독립적으로 적용되며, 컴포넌트 구조의 변경으로 인해 ParentComponent의 스타일이 ChildComponent에 영향을 미치지 않게 된 것입니다.
<style module> 을 사용하여 CSS 모듈을 적용하면, 클래스 이름 충돌을 피할 수 있습니다.
<template>
<ul :class="$style.list">
<!-- 내용 -->
</ul>
</template>
<style module>
.list {
color: green;
list-style-type: lower-alpha;
}
</style>