DEV Community

saifobeidat
saifobeidat

Posted on

Override Tailwind classes of a reusable Vue component without using props

Sometimes we face a case where we need to override a reusable' component base classes from outside, without the headache of defining tons of props. Instead we will be using the "initial" variant.

Main Problem

We can't just pass the Tailwind classes to the component and it automatically will be take the priority and override the base classes

So, let's say we have this "AppButton.vue" component with these base classes: w-8 font-extrabold

<!-- AppButton.vue -->
<template>
  <div class="w-8 font-extrabold"> ... </div>
</template>
Enter fullscreen mode Exit fullscreen mode

and we wish to pass different font weight and width, we will be intuitively doing this in our home page:

<!-- Home.vue -->
<template>
  <AppButton class="w-20 font-light"/>
</template>

Enter fullscreen mode Exit fullscreen mode

But that won't work always, why?
It depends on the order of those classes when they get compiled, in the above example, the base classes w-8 font-extrabold have higher order in the compiled stylesheet file which means they have higher specificity

Image description


Solution (1):

Decrease the CSS specificity of the base classes

If we can guarantee that our base classes w-8 font-extrabold have less specificity, we can ensure that the passed classes from outside will override them

how?

To use the :where() pseudo selector!

According to MDN, :where() is a CSS functional pseudo-class selector that takes in a list of selectors as an argument and applies the given styles to any element from that list. :where() is very useful for making a long selector list shorter

How can we benefit of :where()?

If we can have our base classes to be compiled to something similar to this:

html :where(.initial .w-8, .initial .font-light)
Enter fullscreen mode Exit fullscreen mode

The above code can be translated to:
Every item that is prefixed with the .initial class will take zero specificity, and by this our passed classes to the AppButton can override those zero specificity base classes

So how can we do this using Tailwind?

Add a new variant called initial to the Tailwind plugins array:

  //tailwind.config.js
module.exports = { 
  plugins: [
    function ({ addVariant }) {
      addVariant("initial", "html :where(&)");
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Now we are able to prefix the base classes with initial:* so the AppButton.vue will be as changed to:

<!-- AppButton.vue -->
<template>
  <div class="initial:w-8 initial:font-extrabold"> ... </div>
</template>
Enter fullscreen mode Exit fullscreen mode

It works now
We will find that the classes are overridden:

Image description


Solution (2)

Use Props

This solution is define the needed props "weight and width":

<!-- AppButton.vue -->
<template>
  <div :class="[weight, width]"> ... </div>
</template>

<script>
export default {
   props:{
    weight:{
      type:'String',
      default:'font-extrabold'
    },
    width:{
      type:'String',
      default:'w-8'
    }
   }
}
</script>
Enter fullscreen mode Exit fullscreen mode

And in our home template, we'll be passing them:

<!-- Home.vue -->
<template>
  <AppButton width="w-20" weight="font-light"/>
</template>

Enter fullscreen mode Exit fullscreen mode

I don't prefer this solution, as we need to add a prop for every new default base class.

Why to bloat the code with props when we can use the first solution.

Solution (3)

Is to pass the classes as !important:

<template>
  <div class="!w-20 !font-light"> ... </div>
</template>
Enter fullscreen mode Exit fullscreen mode

but I don't recommend it as it will override all the attached styles to that base class like lg:font-semibold

Thank you!

Top comments (0)