Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

cvu

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cvu

A tiny, performant, utility for constructing variant based CSS class strings.

  • 0.1.1
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
8
increased by60%
Maintainers
1
Weekly downloads
 
Created
Source

cvu

A tiny, performant, utility for constructing variant based CSS class strings.


Installation

NPM:

npm i cvu

Yarn:

yarn add cvu

PNPM:

pnpm i cvu

Tailwind CSS

If you're a Tailwind user, here are some additional (optional) steps to get the most out of cvu.

IntelliSense

You can enable autocompletion inside cvu using the steps below:

VSCode

  1. Install the "Tailwind CSS IntelliSense" Visual Studio Code extension.
  2. Add the following to your settings.json:
{
  "tailwindCSS.experimental.classRegex": [
    [
      "cvu\\s*(?:<[\\s\\S]*?>)?\\s*\\(([^)]*)\\)",
      "[\"'`]([^\"'`]*).*?[\"'`]"
    ]
  ]
}
Handling Style Conflicts

Although cvu's API is designed to help you avoid style conflicts, there is still a small margin of error.

If you're keen to lift that burden altogether, check out tailwind-merge package.

For bulletproof components, wrap your cvu calls with twMerge.

Example with tailwind-merge
import { cvu, type VariantProps } from 'cvu';
import { twMerge } from 'tailwind-merge';

const buttonVariants = cvu(
  ['your', 'base', 'classes'],
  {
    variants: {
      intent: {
        primary: ['your', 'primary', 'classes'],
      },
    },
    defaultVariants: {
      intent: 'primary',
    },
  },
);

export const buttonClassNames = (props: VariantProps<typeof buttonVariants>) => {
  return twMerge(buttonVariants(props));
};

Getting Started

Your First Utility

Here is a simple example of a cvu generated utility function for generating class names for a button component.

Note

The use of Tailwind CSS here is purely for demonstration purposes. cvu is not tied to any specific CSS framework.

import { cvu } from 'cvu';

const buttonClassnames = cvu(
  ['font-semibold', 'border', 'rounded'],
  // --or--
  // 'font-semibold border rounded'
  {
    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: {
        sm: 'text-sm py-1 px-2',
        md: ['text-base', 'py-2', 'px-4'],
      },
    },
    compoundVariants: [
      {
        intent: 'primary',
        size: 'md',
        className: 'uppercase',
      },
    ],
    defaultVariants: {
      intent: 'primary',
      size: 'md',
    },
  },
);

buttonClassnames();
// => 'font-semibold border rounded bg-blue-500 text-white border-transparent hover:bg-blue-600 text-base py-2 px-4 uppercase'

buttonClassnames({ intent: 'secondary', size: 'sm' });
// => 'font-semibold border rounded bg-white text-gray-800 border-gray-400 hover:bg-gray-100 text-sm py-1 px-2'

Compound Variants

Variants that apply when multiple other variant conditions are met.

import { cvu } from 'cvu';

const buttonClassnames = cva('…', {
  variants: {
    intent: {
      primary: '…',
      secondary: '…',
    },
    size: {
      sm: '…',
      md: '…',
    },
  },
  compoundVariants: [
    // Applied via
    //  `buttonClassnames({ intent: 'primary', size: 'md' });`
    {
      intent: 'primary',
      size: 'md',
      // This is the className that will be applied.
      className: '…',
    },
  ],
});
Targeting Multiple Variant Conditions
import { cvu } from 'cvu';

const buttonClassnames = cva('…', {
  variants: {
    intent: {
      primary: '…',
      secondary: '…',
    },
    size: {
      sm: '…',
      md: '…',
    },
  },
  compoundVariants: [
    // Applied via
    //  `buttonClassnames({ intent: 'primary', size: 'md' });`
    //     or
    //  `buttonClassnames({ intent: 'secondary', size: 'md' });`
    {
      intent: ['primary', 'secondary'],
      size: 'md',
      // This is the className that will be applied.
      className: '…',
    },
  ],
});

Additional Classes

All cvu utilities provide an optional string argument, which will be appended to the end of the generated class name.

This is useful in cases where want to pass a React className prop to be merged with the generated class name.

import { cvu } from 'cvu';

const buttonClassnames = cvu('rounded', {
  variants: {
    intent: {
      primary: 'bg-blue-500',
    },
  },
});

buttonClassnames(undefined, 'm-4');
// => 'rounded m-4'

buttonClassnames({ intent: 'primary' }, 'm-4');
// => 'rounded bg-blue-500 m-4'

TypeScript

VariantProps

cvu offers the VariantProps helper to extract variant types from a cvu utility.

import { cvu, type VariantProps } from 'cvu';

type ButtonClassnamesProps = VariantProps<typeof buttonClassnames>;
const buttonClassnames = cvu(/* … */);
VariantPropsWithRequired

Additionally, cvu offers the VariantPropsWithRequired helper to extract variant types from a cvu utility, with the specified keys marked as required.

import { cvu, type VariantPropsWithRequired } from 'cvu';

type ButtonClassnamesProps = VariantPropsWithRequired<typeof buttonClassnames, 'intent'>;
const buttonClassnames = cvu('…', {
  variants: {
    intent: {
      primary: '…',
      secondary: '…',
    },
    size: {
      sm: '…',
      md: '…',
    },
  },
});

const wrapper = (props: ButtonClassnamesProps) => {
  return buttonClassnames(props);
};

// ❌ TypeScript Error:
// Argument of type "{}": is not assignable to parameter of type "ButtonClassnamesProps".
//   Property "intent" is missing in type "{}" but required in type
//   "ButtonClassnamesProps".
wrapper({});
 
// ✅
wrapper({ intent: 'primary' });

Composing Utilities

import { cvu, cx, type VariantProps } from "cvu";
 
/**
 * Box
 */
export type BoxClassnamesProps = VariantProps<typeof boxClassnames>;
export const boxClassnames = cvu(/* … */);
 
/**
 * Card
 */
type CardBaseClassNamesProps = VariantProps<typeof cardBaseClassnames>;
const cardBaseClassnames = cvu(/* … */);
 
export interface CardClassnamesProps extends BoxClassnamesProps, CardBaseClassnamesProps {}
export const cardClassnames = ({ /* destructured props */ }: CardClassnamesProps = {}) =>
  cx(boxClassnames({ /* … */  }), cardBaseClassnames({ /* … */ }));

API

cvu

Builds a typed utility function for constructing className strings with given variants.

import { cvu } from 'cvu';

const classVariants = cvu('base', variantsConfig);
Parameters
  1. base - the base class name (string, string[], or other clsx compatible value).
  2. variantsConfig - (optional)
    • variants - your variants schema

    • componentVariants - variants based on a combination of previously defined variants

    • defaultVariants - set default values for previously defined variants.

      Note: these default values can be removed completely by setting the variant as null.

Acknowledgements

  • Stitches

    For pioneering the variants API movement.

  • cva

    For the inspiration behind cvu. I personally didn't find the library to quite meet my needs or API preferences, but it's a great library nonetheless.

  • clsx

    An amazing library for lightweight utility for constructing className strings conditionally.

License

MIT © Eric Taylor

Keywords

FAQs

Package last updated on 25 May 2023

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc