Indicate HN: Class Variance Authority – form-fine variants in your UI system

73
Indicate HN: Class Variance Authority – form-fine variants in your UI system

#reveal,Class,Variance

CVA

Class Variance Authority

NPM Version

Types Included

Minizipped Size

Apache-2.0 License

NPM Downloads

Follow @joebell_ on Twitter

Introduction

CSS-in-TS libraries comparable to Stitches and Vanilla Extract are unbelievable alternate options for building form-fine UI system; casting off the final worries of class names and StyleSheet composition.

…but CSS-in-TS (or CSS-in-JS) will not be for all individuals.

You’ll possess stout protect a watch on over your StyleSheet output. Your job would possibly perchance maybe well maybe require you to make utilize of a framework comparable to Tailwind CSS. It is most likely you’ll maybe well maybe correct prefer writing your individual CSS.

Increasing variants with the “ancient” CSS advance can change into an arduous assignment; manually matching classes to props and manually including varieties.

cva goals to set up these peril aspects away, allowing you to point of curiosity on the extra relaxing parts of UI pattern.

Acknowledgements

  • Stitches (Modulz)
    Large as a result of the Modulz group for pioneering the variants API motion – your birth-source contributions are immensely most standard
  • clb (Bill Criswell)
    This project first and major place started out with the device of merging into the unbelievable clb library, but after some discussion with Bill, we felt it used to be most efficient to poke down the route of a separate project.
    I’m so grateful to Bill for sharing his work publicly and for getting me desirous about building a kind-fine variants API for classes. Have to you’ve a moment, please poke and giant name the project on GitHub. Thank you Bill!

Installation

npm i class-variance-authority
“Net I even prefer to write such a protracted bundle name for every import?”

Unfortunately, yes. Originally, the idea used to be the publish the bundle as cva, but this name has been taken and marked as a “placeholder”. I’ve reached out to the author and NPM beef up, but possess but to hear back.

In the duration in-between, you would possibly perchance maybe well maybe moreover always alias the bundle in your convenience…

Aliasing in TypeScript

  1. Add the alias to your tsconfig.json paths:

    {
      "compilerOptions": {
        "baseUrl": ".",
        "paths": {
          "cva": ["node_modules/class-variance-authority"]
        }
      }
    }
  2. Then import savor so:

    import { cva } from "cva";
    
    // …

Getting Began

Disclaimer: Even supposing cva is a tiny library, or not it is most efficient to make utilize of in a SSR/SSG ambiance – your user doubtlessly would not need this JavaScript, significantly for static system.

Your First Factor

To kick things off, let’s manufacture a “overall” button side, utilizing cva to address our variant’s classes

Indicate: Employ of Tailwind CSS will not be necessary

“font-semibold border rounded bg-blue-500 textual vow material-white border-clear hover:bg-blue-600 textual vow material-adversarial py-2 px-4 uppercase”

button({ intent: “secondary”, size: “tiny” });
// => “font-semibold border rounded bg-white textual vow material-gray-800 border-gray-400 hover:bg-gray-100 textual vow material-sm py-1 px-2″”>

import { cva } from "class-variance-authority";

const button = cva(["font-semibold", "border", "rounded"], {
  variants: {
    intent: {
      predominant: [
        "bg-blue-500",
        "text-white",
        "border-transparent",
        "hover:bg-blue-600",
      ],
      // or
      // predominant: "bg-blue-500 textual vow material-white border-clear hover:bg-blue-600",
      secondary: [
        "bg-white",
        "text-gray-800",
        "border-gray-400",
        "hover:bg-gray-100",
      ],
    },
    size: {
      tiny: ["text-sm", "py-1", "px-2"],
      medium: ["text-base", "py-2", "px-4"],
    },
  },
  compoundVariants: [{ intent: "primary", size: "medium", class: "uppercase" }],
  defaultVariants: {
    intent: "predominant",
    size: "medium",
  },
});

button();
// => "font-semibold border rounded bg-blue-500 textual vow material-white border-clear hover:bg-blue-600 textual vow material-adversarial py-2 px-4 uppercase"

button({ intent: "secondary", size: "tiny" });
// => "font-semibold border rounded bg-white textual vow material-gray-800 border-gray-400 hover:bg-gray-100 textual vow material-sm py-1 px-2"

TypeScript Helpers

cva affords the VariantProps helper to extract variant varieties

;
export const yourComponent = cva(/… */);”>
// styles/system.ts
import form { VariantProps } from "class-variance-authority";
import { cva, cx } from "class-variance-authority";

/
 YourComponent
 */
export form YourComponentProps = VariantProps<typeof yourComponent>;
export const yourComponent = cva(/... */);

Composing Classes

Whereas cva would not but offer a built-in methodology for composing classes, it does offer the instruments to lengthen system in your individual phrases…

As an illustration; two cva styles, concatenated along with cx:

;
export const box = cva([“box”, “box-border”], {
variants: {
margin: { 0: “m-0”, 2: “m-2”, 4: “m-4”, 8: “m-8” },
padding: { 0: “p-0”, 2: “p-2”, 4: “p-4”, 8: “p-8” },
},
defaultVariants: {
margin: 0,
padding: 0,
},
});

/Card
*/
type CardBaseProps = VariantProps;
const cardBase = cva([“card”, “border-solid”, “border-slate-300”, “rounded”], {
variants: {
shadow: {
md: “drop-shadow-md”,
lg: “drop-shadow-lg”,
xl: “drop-shadow-xl”,
},
},
});

export interface CardProps extends BoxProps, CardBaseProps {}
export const card = ({ margin, padding, shadow }: CardProps = {}) =>
cx(field({ margin, padding }), cardBase({ shadow }));”>

// styles/system.ts
import form { VariantProps } from "class-variance-authority";
import { cva, cx } from "class-variance-authority";

/
 Box
 */
export form BoxProps = VariantProps<typeof box>;
export const field = cva(["box", "box-border"], {
  variants: {
    margin: { 0: "m-0", 2: "m-2", 4: "m-4", 8: "m-8" },
    padding: { 0: "p-0", 2: "p-2", 4: "p-4", 8: "p-8" },
  },
  defaultVariants: {
    margin: 0,
    padding: 0,
  },
});

/
 Card
 */
form CardBaseProps = VariantProps<typeof cardBase>;
const cardBase = cva(["card", "border-solid", "border-slate-300", "rounded"], {
  variants: {
    shadow: {
      md: "fall-shadow-md",
      lg: "fall-shadow-lg",
      xl: "fall-shadow-xl",
    },
  },
});

export interface CardProps extends BoxProps, CardBaseProps {}
export const card = ({ margin, padding, shadow }: CardProps = {}) =>
  cx(field({ margin, padding }), cardBase({ shadow }));

API Reference

cva

Builds a class variance authority

const side = cva("adversarial", alternate options);
  1. adversarial: the adversarial class name (string, string[] or null)
  2. alternate options (not necessary)
    • variants: your variants schema
    • compoundVariants: variants basically based on a combination of previously outlined variants
    • defaultVariants: place default values for previously outlined variants

cx

Concatenates class names

const className = cx(classes);
  • classes: array of classes to be concatenated

Examples

⚠️ Warning: The examples under are purely demonstrative and have not been examined thoroughly (but)

BEM
/styles.css */
.button {
  //
}

.button--predominant {
  //
}
.button--secondary {
  //
}

.button--tiny {
  //
}
.button--medium {
  //
}

.button--predominant-tiny {
  //
}
“button button–predominant button–medium”

button({ intent: “secondary”, size: “tiny” });
// => “button button–secondary button–tiny””>

import { cva } from "class-variance-authority";

const button = cva("button", {
  variants: {
    intent: {
      predominant: "button--predominant",
      secondary: "button--secondary",
    },
    size: {
      tiny: "button--tiny",
      medium: "button--medium",
    },
  },
  compoundVariants: [
    { intent: "primary", size: "medium", class: "button--primary-small" },
  ],
  defaultVariants: {
    intent: "predominant",
    size: "medium",
  },
});

button();
// => "button button--predominant button--medium"

button({ intent: "secondary", size: "tiny" });
// => "button button--secondary button--tiny"
11ty (with Tailwind)
${label}`;
};”>
// button.11ty.js
const { cva } = require("class-variance-authority");

// ⚠️ Disclaimer: Employ of Tailwind CSS will not be necessary
const button = cva("button", {
  variants: {
    intent: {
      predominant: [
        "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: {
      tiny: ["text-sm", "py-1", "px-2"],
      medium: ["text-base", "py-2", "px-4"],
    },
  },
  compoundVariants: [{ intent: "primary", size: "medium", class: "uppercase" }],
  defaultVariants: {
    intent: "predominant",
    size: "medium",
  },
});

module.exports = operate ({ price, intent, size }) {
  return `
React (with CSS Modules)
/button.css */
.adversarial {
  //
}

.predominant {
  //
}
.secondary {
  //
}

.tiny {
  //
}
.medium {
  //
}

.primaryMedium {
  //
}
;

export const Button: React.FC = ({ intent, size, …props }) => (

);”>

// button.tsx
import React from "react";
import { cva } from "class-variance-authority";
import form { VariantProps } from "class-variance-authority";

import {
  adversarial,
  predominant,
  secondary,
  tiny,
  medium,
  primaryMedium,
} from "./button.css";

// ⚠️ Disclaimer: Employ of Tailwind CSS will not be necessary
const button = cva(adversarial, {
  variants: {
    intent: {
      predominant,
      secondary,
    },
    size: {
      tiny,
      medium,
    },
  },
  compoundVariants: [
    { intent: "primary", size: "medium", class: primaryMedium },
  ],
  defaultVariants: {
    intent: "predominant",
    size: "medium",
  },
});

export form ButtonProps = VariantProps<typeof button>;

export const Button: React.FC<ButtonProps> = ({ in

Read More

Charlie Layers
WRITTEN BY

Charlie Layers

Fill your life with experiences so you always have a great story to tell