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;
}