DEV Community

Cover image for Use postcss-cva to generate cva method
ice breaker
ice breaker

Posted on

Use postcss-cva to generate cva method

cva-demo

What is cva

cva stands for class-variance-authority. It is a class library that is very suitable for creating and controlling Css variant methods. It is very suitable for atomic ideas like tailwindcss.

Many times we encapsulate components, especially using the idea of ​​atomization to write css, and then encapsulate the components, just use it!

Example

So how to use it for packaging? Let’s take the example of encapsulating an intuitive vue component Button:

<template>
  <button>
    <slot></slot>
  </button>
</template>
Enter fullscreen mode Exit fullscreen mode

Next, we need to control its style based on the passed Props of this component, including color type, size, status, shape, etc.

So if we use styles to control these, we usually write like this:

<template>
  <button :class="className">
    <slot></slot>
  </button>
</template>

<script setup lang="ts">
import { computed } from 'vue';

function getButtonClass(props){
  let classNames = [/* base */]
  // do something with props like push, splice, unshift ...
  return classNames.join(' ')
}

const props = withDefaults(defineProps<{
  // ...
}>(), {
  // ... 
})
const className = computed(() => {
  return getButtonClass(props)
})
</script>
Enter fullscreen mode Exit fullscreen mode

And cva is a method that helps us generate this getButtonClass function.

Parameters

The input parameters of a cva usually include 4 parts: base, variants, compoundVariants, defaultVariants:

import { cva } from 'class-variance-authority'
//                     ⬇️ base
const button = cva(['font-semibold', 'border', 'rounded'], {
  // ⬇️ variants
  variants: {
    intent: {
      primary: ['bg-blue-500', 'text-white', 'border-transparent', 'hover:bg-blue-600'],
      secondary: ['bg-white', 'text-gray-800', 'border-gray-400', 'hover:bg-gray-100']
    },
    size: {
      small: ['text-sm', 'py-1', 'px-2'],
      medium: ['text-base', 'py-2', 'px-4']
    }
  },
  // ⬇️ compoundVariants
  compoundVariants: [
    {
      intent: 'primary',
      size: 'medium',
      class: 'uppercase'
    }
  ],
  // ⬇️ defaultVariants
  defaultVariants: {
    intent: 'primary',
    size: 'medium'
  }
})

button()
// => "font-semibold border rounded bg-blue-500 text-white border-transparent hover:bg-blue-600 text-base py-2 px-4 uppercase"
button({ intent: 'secondary', size: 'small' })
// => "font-semibold border rounded bg-white text-gray-800 border-gray-400 hover:bg-gray-100 text-sm py-1 px-2"
Enter fullscreen mode Exit fullscreen mode

Many times we can construct such a function to encapsulate our components. This will make our code very intuitive. What should control the style will control the style, and what should control the behavior will control the behavior. So it is good to use cva to transform the vue Button component just now.

But what if our project does not use the atomic Css class library? Or if we want to generate the cva function when writing Css, then you have to use postcss-cva.

postcss-cva

postcss-cva is a postcss plugin based on css ast analysis.

It can convert the Css comments you write into cva functions.

css example

Let’s look at a simple example. During the encapsulation process of the Button component, the following Css is written

/* @meta path="./buttonClass" */
/* @dv size="md" */
.btn {
  /* @b */
  font-size: 16px;
  background: gray;
  border-radius: 4px;
}

.btn-primary {
  /* @v type="primary" */
  background: blue;
  color: white;
}

.btn-secondary {
  /* @v type="secondary" */
  font-size: 22px;
  color: yellow;
}

.btn-disabled {
  /* @cv type="primary" size="xs" */
  cursor: not-allowed;
}

.btn-md {
  /* @v size="md" */
  padding: 6px 10px;
  font-size: 16px;
}

.btn-xs {
  /* @v size="xs" */
  padding: 2px 6px;
  font-size: 14px;
}

.btn-sm {
  /* @v size="sm" */
  padding: 4px 8px;
  font-size: 12px;
}
Enter fullscreen mode Exit fullscreen mode

Atomic design

In it we define:

  • Its base class: base: .btn
  • Its variants: variants:
    • .btn-primary, .btn-secondary to control color types
    • .btn-md, .btn-xs, .btn-sm to control size
    • More to control, shape or other etc.
  • Its compound variants: compoundVariants: .btn-disabled (triggered when the passed parameter type="primary" size="xs" is met)
  • Its default variant: defaultVariants: size="md" (use size="md" when no parameters are passed by default)

Annotated reference

Then follow the comment reference of postcss-cva:

keyword target type description
@b base node add current node selector to base
@gb base global define base
@v variants node add current node selector to variants
@gv variants global define variants
@cv compoundVariants node add current node selector to compoundVariants
@gcv compoundVariants global define defaultVariants
@dv defaultVariants global define defaultVariants
@meta meta global define metadata

We define all variants by adding corresponding annotations.

defined by @meta path="./buttonClass"

At the same time, the file output directory of the cva function is also defined as the buttonClass.ts file in the directory where the current Button.vue file is located (the default format is ts)

Generate cva function

In this way, when we run the main function and introduce the css file where the vue component/comment is located, a cva function is generated:

import { cva, VariantProps } from "class-variance-authority";
const index = cva(["btn"], {
  variants: {
    "type": {
      "primary": ["btn-primary"],
      "secondary": ["btn-secondary"]
    },
    "size": {
      "md": ["btn-md"],
      "xs": ["btn-xs"],
      "sm": ["btn-sm"]
    }
  },
  compoundVariants: [{
    "class": ["btn-disabled"],
    "type": ["primary"],
    "size": ["xs"]
  }],
  defaultVariants: {
    "size": "md"
  }
});
export type Props = VariantProps<typeof index>;
export default index;
Enter fullscreen mode Exit fullscreen mode

然后,我们就可以直接引入进行封装了!

<template>
  <button :class="className">
    <slot>postcss-cva</slot>
  </button>
</template>

<script setup lang="ts">
import { computed } from 'vue';
import buttonClass, { Props as ButtonProps } from './buttonClass'

const props = withDefaults(defineProps<{
  // ButtonProps
  type?: 'primary' | 'secondary',
  size?: 'md' | 'sm' | 'xs'
}>(), {})
const className = computed(() => {
  return buttonClass(props)
}) 
</script>
Enter fullscreen mode Exit fullscreen mode

postcss-cva allows you to plan the cva function when designing and writing css.

Now you can not only use it directly as an external postcss plug-in, but it has also been integrated into IceStack inside.

Use it to manage and generate your Css UI now.

Refers

IceStack

postcss-cva

cva.style

Top comments (0)