Search code examples
javascriptcssvue.jssass

Is there a better way to manage variant with css


I got a message component with at this time only two variants(error, success).

The project use vue3 script setup and scss for style.

<script setup lang="ts">
defineOptions({
  name: 'NotificationMessage',
})

withDefaults(defineProps<{ variant?: string }>(), { variant: 'success' })
</script>

<template>
  <div :class="`notifications-message notifications-message--${variant}`">
    <div class="tw-flex tw-flex-row tw-items-center tw-gap-4">
      <span class="notifications-message__icon material-symbols-outlined"> check_circle </span>
      <div>message</div>
    </div>
    <span class="tw-red-primary tw-text-xl material-symbols-outlined"> close </span>
    <div class="notifications-message__loader" />
  </div>
</template>

<style lang="scss" scoped>
@keyframes widthIncrease {
  100% {
    width: 100%;
  }
}
.notifications-message {
  @apply tw-w-64 tw-pt-2 tw-pb-3 tw-px-4 tw-flex tw-flex-row tw-justify-between tw-items-center;
  color: #6a5d6a;
  word-wrap: break-word;
  display: flex;
  border-radius: 4px;
  max-width: 100%;
  box-shadow: 0 0 3px 1px $blue-secondary;
  background-color: $white-primary;

  &--success {
    .notifications-message {
      &__icon {
        color: $green-primary;
      }

      &__loader {
        background-color: $green-primary;
      }
    }
  }

  &--error {
    .notifications-message {
      &__icon {
        color: $red-primary;
      }

      &__loader {
        background-color: $red-primary;
      }
    }
  }

  &__loader {
    @apply tw-absolute tw-bottom-0 tw-left-0 tw-w-0 tw-h-1 tw-rounded-bl-md tw-rounded-br-md;
    animation: widthIncrease 4s linear forwards;
  }
}
</style>

Is there a possibility to assign directly based on the parent class variant.

I know the selector :has() exist when we want to do something only if the parent contains something.

So can we do the same on a child to avoid repetition?

Something like this:

.notifications-message {
// ....
 &__icon {
    
    // If notifications-message contains class --success {
      color: $green-primary;
    }

    // If notifications-message contains class --error {
      color: $red-primary;
    }
 }
}

Solution

  • There is no one good way. Every developer would tell you something else and most of them are somewhat good. It depends on use case and amount of changes you need to make in each variation.

    For your case I would suggest using CSS properties as in each variant you are using the same color for every overwritten prop.
    If in each variations you would change more options and each options would have different values (for example: 3 props, each with different shades of color) then something like you already did would be better.

    example:

    .notifications-message {
        --notification-color: gray;
        color: var(--notification-color);
        border: 1px solid var(--notification-color);
        box-shadow: 0 2px 10px var(--notification-color);
        margin-bottom: 1rem;
        padding: .5rem;
    }
    
    .notifications-message.notifications-message--success {
        --notification-color: green;
    }
    
    .notifications-message.notifications-message--error {
        --notification-color: red;
    }
    <div class="notifications-message">default</div>
    <div class="notifications-message notifications-message--success">success</div>
    <div class="notifications-message notifications-message--error">error</div>

    As for the :has() I would strongly suggest not to use it. At lease for now. It is usable in every browser that is running webkit BUT...
    moz is hesitant with implementing it outside of optional features that you have to enable in browser to use it.
    Can i use for :has()
    More info here