Is there a way to force Vue to prefer the inherited class?

I have a component where I want Vue to ignore the class that was already there and use the inherited class instead. But only if the class is of the same type as the inherited class. Here is an example of what I mean:

I have a button component like this

MyButton.vue

<button class="text-sm font-roboto justify-center text-red-500">
  <slot></slot>
</button>

Then in my project I would like to use MyButton component but I want to override the justify-center and text-red-500 classes with my own, for example like this:

<MyButton class="justify-start text-gray-100">click me</MyButton>

The result is this rendered button:

<button class="text-sm font-roboto justify-center text-red-500 justify-start text-gray-100">
  click me
</button>

The problem is that HTML prioritizes the class justify-center and justify-start class is ignored. I would like Vue to be smart enough that it understands Tailwind classes and if it sees that there was justify-center originally and now I pass in justify-start then it should remove justify-center and add justify-start.

I also want it to do this for all Tailwind classes. If it sees that there was originally a text-.... class then it should remove that and replace it with the inherited class. Same for fonts and colors and shadows etc.

So the result would be like this:

<button class="text-sm font-roboto justify-start text-gray-100">
  click me
</button>

This is what props are for. Pass props down that then correlate to pre-defined classes.

This is React code I ripped from a project of mine as I don’t have to time write something for Vue, but the pattern is all the same.

const Button = (
    {
      size = 'sm',
      variant = 'primary',
      type = 'button',
      fullWidth = false,
      square = false,
      children,
      className = '',
      ...rest
    }: IButtonProps,
  ) => {
    const classes = mergeClasses([
      buttonBase,
      buttonSize[size],
      buttonVariant[variant],
      square ? buttonSquare[size] : '',
      fullWidth ? 'w-full' : '',
      className,
    ]);

    return (
      <button className={classes} type={type} {...rest}>
        {children}
      </button>
    )
  }
)
export const buttonBase = 'inline-flex items-center justify-center border font-medium';

export const buttonVariant = {
  primary: 'text-white bg-primary-600 hover:bg-primary-700 border-transparent',
  secondary: 'text-gray-700 bg-gray-200 hover:bg-gray-300 border-transparent',
  outline: 'text-gray-700 border-gray-300 shadow-sm bg-white hover:bg-gray-50',
  danger: 'text-white bg-red-600 border-transparent hover:bg-red-700',
  success: 'text-white bg-emerald-600 border-transparent hover:bg-emerald-700',
  transparent: 'text-primary-600 border-transparent',
};

export const buttonSize = {
  xs: 'px-2.5 py-1.5 text-xs rounded',
  sm: 'px-3 py-2 text-sm rounded-md leading-4',
  md: 'px-4 py-2 text-sm rounded-md',
  lg: 'px-4 py-2 text-base rounded-md',
  xl: 'px-6 py-3 text-base rounded-md',
  '2xl': 'px-8 py-4 text-base rounded-md',
};

export const buttonSquare = {
  xs: '!px-1 !py-1',
  sm: '!px-2 !py-2',
  md: '!px-3 !py-3',
  lg: '!px-4 !py-4',
  xl: '!px-6 !py-6',
  '2xl': '!px-8 !py-8',
};

Merge classes is just a simple helper

const mergeClasses = (classes: string[]) => classes.join(' ').trim();

Passing a million props into a component wasn’t a good fit for me but I did finally solve it using tailwind-merge and using useAttrs() composable.

<template>
    <button :class="buttonClass">
        <slot></slot>
        <i class="text-base text-gray-400 font-normal">
        class="{{ buttonClass }}"</i>
    </button>
</template>

<script setup lang="ts">
    import { computed, useAttrs } from 'vue'
    import { twMerge } from 'tailwind-merge'

    const attrs = useAttrs()

    const buttonClass = computed(() => {
        return twMerge('font-bold text-3xl justify-center text-red-500 ', attrs.class)
    })
</script>

<script lang="ts">
    export default {
        inheritAttrs: false
    }
</script>