Apply CSS to Sub-Components of a Scoped Component

— 7 minute read


When composing a Vue.js interface of lots of sub-components we often want to make a few small styling tweaks to those sub-components without making changes to that component directly - either because we can't cos it's a 3rd party component, or because we don't want that change to apply everywhere.

The scoped Attribute

If we're using scoped component styling then what appears to be a simple task becomes a bit more tricky. When we use this mode, Vue will automatically add a unique attribute to the elements within that component. It will then add the same value to the CSS rule so that no CSS styles 'bleed' outside that specific component.

For example,

<template>
<div class="my-div">Test</div>
</template>

<style lang="scss" scoped>
.my-div {
font-size: 3rem;
}
</style>

Would be transformed into something like this:

<div class="my-div" data-v-abc123>Test</div>

With the CSS looking like:

.my-div[data-v-abc123] {
font-size: 3rem;
}

An Example Form

Imagine we're using Bootstrap Vue and have a simple component template (NameField) like this:

<template>
<div class="name-field">
<b-form-group label="Full Name" label-for="fullName">
<b-form-input id="fullName" />
</b-form-group>
</div>
</template>

<style lang="scss" scoped>

</style>

And then we had a second component (named Form) where we render the NameField component:

<template>
<div class="form">
<NameField />
</div>
</template>

This setup would render HTML like this:

<div class="form" data-v-88788404> <!-- this is the Form component's root element -->
<div class="name-field" data-v-29fd966b data-v-88788404> <!-- this is the NameField component's root element -->
<div data-v-29fd966b role="group" class="form-group" id="__BVID__20">
<label for="fullName" class="d-block">Full Name</label>
<div class="bv-no-focus-ring">
<input data-v-29fd966b id="fullName" type="text" class="form-control">
</div>
</div>
</div>
</div>

Notice all the attributes added for the scoped CSS. If you look even closer you will see that the NameField has two attributes, one for its own scope and another for the parent (Form) component's scope. This means we can apply styles directly to the NameField if we wanted, without having to use the trick we're about to talk about!

Styling the Form Field

If we wanted to change the background-color of the rendered input element we might think we can just add a selector for .form-control to the Form component's style block, and define the new background colour.

.form-control {
background-color: red;
}

This would output the following CSS into the document, with the data attribute that matches the one given to our Form component's root element. This means that the style wouldn't be applied, because the form-control element we're targeting doesn't have that attribute.

.form-control[data-v-88788404] {
background: red;
}

Targeting Deep Elements

To get around this issue we need to use the ::v-deep directive on any styles that are targeting an element deep within the component structure - which is what we're doing here!

We simply prepend our style rule with ::v-deep like this:

::v-deep .form-control {
background-color: red;
}

By doing this our output is changed to:

[data-v-88788404] .form-control {
background: red;
}

We don't lose the scoped nature of our CSS (the scoping attribute is still included) but it has changed the selector so the form-control element should just be a child of the scoped element.

A Warning!

One thing to be wary of with this technique is that your styles will apply to all elements rendered inside your root component. So in our example if we had another .form-control element, or if a sub-component had form-control elements then it would also get this style.

We can get around this by being more specific with our selector to narrow down what we want to target. For example:

::v-deep .name-field .form-control {
background-color: red;
}

Filed under