JavaScriptT2 Dark_1200x303

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.

Setting up an Example

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>

Styling the Children through the Parent

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.

Wrapping Up

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_2020
About the Author

Marina Mosti

Marina Mosti is a full-stack web developer with over 13 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.

She currently holds a position as Lead FE Developer at VoiceThread, and she is the author of the FormVueLatte library as well as a member of the Vuelidate team. In her spare time, she enjoys playing bass, drums, and videogames.

Related Posts

Comments

Comments are disabled in preview mode.