Learn how to use the Deep selector in Vue, a powerful tool for resolving certain CSS issues.
CSS can be an absolute delight or your worst nightmare, especially when dealing with components that wrap a series of child components that need to be styled by the parent.
In this article we are going to explore Vue's deep
selector, and why it can be a powerful tool to have under your belt.
Granted, the deep
selector is not a very common way to resolve CSS injection issues. There are few real scenarios I can think of that will benefit from this approach — one of them is modifying the styling of a third-party library component. Or perhaps even setting specific CSS rules for descendants on a per-component basis, which is the one we will be using as an example today.
Be aware that this approach will propagate to ALL child components, so that is something you need to be mindful about. Good class naming and careful consideration of your CSS structure are a must.
Having said that, let's get set up.
To better understand how to use the deep
selector in Vue, we are going to build a very lean example application that will have some <BaseButton>
components. These buttons will not be styled in any particular way, but they will change depending on the parent that contains them.
The first component, <BaseButton>
will be a simple wrapper for an HTML <button>
. There is a reason for that <div>
there, and we will go into detail later on in the article.
<template>
<div>
<button v-on="$listeners">
<slot/>
</button>
</div>
</template>
As we agreed, no styling will be set for this button. Be aware that it is still subject to global CSS selectors that modify it, for example setting button { background-color: 'blue'; }
in your global styles.
Next step is creating two parents that will use this particular button component. We're going to make them loop with a v-for
and render three of them per parent, just for example purposes.
The first component BlueParent
looks like this.
<template>
<div>
<h1>I is blue</h1>
<BaseButton v-for="i in 3" :key="`blue${i}`">{{ i }}</BaseButton>
</div>
</template>
<script>
import BaseButton from "./BaseButton";
export default {
components: { BaseButton }
};
</script>
As you can see, we're importing BaseButton
and rendering it on the screen three times. That's it. 🙃
The next component will be RedParent
and it looks like this.
<template>
<div>
<h1>I is red</h1>
<BaseButton v-for="i in 3" :key="`red${i}`">{{ i }}</BaseButton>
</div>
</template>
<script>
import BaseButton from "./BaseButton";
export default {
components: { BaseButton }
};
</script>
Let's get straight down to business. Open BlueParent
and add the following code to the bottom of the file.
<style scoped>
div >>> button {
background-color: lightblue;
}
</style>
There's a few notable things happening here. Let's walk through them step by step.
First of all, we're setting a <style>
block that is going to be scoped to this component. Scoped
styles apply only to this particular component, which means that if we were to set:
div {
background-color: black;
}
This particular div inside of BlueParent
would have a black background color. Why doesn't it apply to all divs in the app as usual?
Scoped
styles are applied to elements through a data property. That means that when Vue compiles your application, it will inject a random string as a data
property to your element.
In this case, our wrapper <div>
may be receive a data attribute, like <div data-v-123>
.
Once this is applied randomly to each INSTANCE of your component (each one will be unique), Vue creates styles in your app that target this data
instead of div
as you wrote it:
div[data-v-123] {
background-color: black;
}
Keeping this in mind. Let's move on to the next important thing in BlueParent
's style block.
div >>> button
The triple >>>
is what is called a deep
CSS selector for Vue. What it means, literally, is: "Find any buttons inside this div, and apply the following style to them, EVEN the ones that are rendered by the children components."
If you add this <BlueParent>
to your app now and look at it in the browser, you will see that all three buttons are now colored blue on the background.
Let's do some experimenting though. Add a simple <button>
inside the template of BlueParent
.
<template>
<div>
<h1>I is blue</h1>
<BaseButton v-for="i in 3" :key="`blue${i}`">{{ i }}</BaseButton>
<button>Blue</button>
</div>
</template>
If you look once again in the browser, even this new <button>Blue</button>
will receive the styles!
One last test. Go ahead and change the style code to reflect the following:
<style scoped>
.blue > button {
background-color: lightblue;
}
</style>
Now that the deep
selector is gone, and it's only a simple >
selector, the styles will no longer be applied to the elements inside <BaseButton>
.
Now, let's look at <RedParent>
.
<style scoped>
div /deep/ button {
background-color: red;
}
</style>
In this example, we're using the other way to write a deep
selector. So >>>
is the same as /deep/
! The reason for having these two ways of declaring it, is that sometimes when you are using precompilers, like SASS, they may have issues understanding >>>
and will fail to compile your CSS. If this happens, resort to /deep/
.
Once again, add this component your app, run it in your browser, and you will see the three extra buttons with the background color of red.
One last thing before we wrap it up, though. Remember that <div>
we added to <BaseButton>
?
When you style components by selecting their ROOT/FIRST element, you don't need to use the deep combinator. Try it out! Add the class="buttonWrapper"
to the wrapping <div>
in <BaseButton>
.
<template>
<div class="buttonWrapper">
<button v-on="$listeners">
<slot/>
</button>
</div>
</template>
Now go back to either of the parent components, and add the following CSS.
div > .buttonWrapper {
background-color: yellow;
}
You will see that the div
is being correctly targeted, and its background will now turn yellow.
The deep
selector is not something you usually encounter out in the wild in many example Vue components because it's a very specific solution to a very specific problem, but this opens up possibilities to even reducing the amount of props you may need in your components to inject different styles.
If you want to see this in action, here's a code sandbox with the article's code: https://codesandbox.io/s/deep-css-example-l1p5e.
As always, thanks for reading, and let me know on Twitter @marinamosti if you ever encountered a fancy example of using the deep
selector!
P.S. All hail the magical avocado 🥑
P.P.S. ❤️🔥🐶☠️
Marina Mosti is a frontend web developer with over 18 years of experience in the field. She enjoys mentoring other women on JavaScript and her favorite framework, Vue, as well as writing articles and tutorials for the community. In her spare time, she enjoys playing bass, drums and video games.