npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

classname-variants

v1.5.0

Published

Variant API for plain class names

Downloads

11,943

Readme

classname-variants 🌈

Library to create type-safe components that render their class name based on a set of variants.

Features

  • ⚛️ Supports React, Preact and vanilla DOM
  • 🛡️ Fully type-safe and excellent auto completion support
  • ✅ Supports both optional and required variants
  • 🔗 Supports custom strategies like tailwind-merge
  • 🪶 Light-weight without any dependencies

npm bundle size

Examples

Here is an example that uses React and Tailwind CSS:

import { styled } from "classname-variants/react";

const Button = styled("button", {
  variants: {
    size: {
      small: "text-xs",
      large: "text-lg",
    },
    primary: {
      true: "bg-teal-500 text-white",
    },
  },
});

function UsageExample() {
  return <Button primary size="large" />;
}

Edit classname-variants/react

While the library has been designed with tools like Tailwind in mind, it can be also used with custom classes or CSS modules:

Preact + CSS modules

import { styled } from "classname-variants/preact";
import styles from "./styles.module.css";

const Button = styled("button", {
  variants: {
    size: {
      small: styles.small,
      large: styles.large,
    },
  },
});

Vanilla DOM

The core of the library is completely framework-agnostic:

import { variants } from "classname-variants";

const button = variants({
  base: "rounded text-white",
  variants: {
    color: {
      brand: "bg-sky-500",
      accent: "bg-teal-500",
    },
  },
});

document.write(`
  <button class="${button({ color: "accent" })}">
    Click Me!
  </button>
`);

API

Defining variants

You can add any number of variants by using the variants key.

{
  variants: {
    color: {
      primary: "bg-teal",
      secondary: "bg-indigo",
      danger: "bg-red"
    },
    size: {
      small: "text-sm",
      medium: "text-md",
      large: "text-lg",
    }
  }
}

Boolean variants

Variants can be typed as boolean by using true / false as key:

{
  variants: {
    primary: {
      true: "bg-teal-500",
    },
  },
}
<Button primary>Click Me!</Button>

The compoundVariants option can be used to apply class names based on a combination of other variants:

{
  variants: {
    color: {
      neutral: "bg-gray-200",
      accent: "bg-teal-400",
    },
    outlined: {
      true: "border-2",
    },
  },
  compoundVariants: [
    {
      variants: {
        color: "accent",
        outlined: true,
      },
      className: "border-teal-500",
    },
  ],
}

Default variants

If you define a variant it becomes a required prop unless you specify a default (or the variant is boolean). You can use the defaultVariants property to specify defaults:

{
  variants: {
    color: {
      primary: "bg-teal-300",
      secondary: "bg-teal-100"
    },
  },
  defaultVariants: {
    color: "secondary",
  }
}

Base class

Use the base property to specify class names that should always be applied:

{
  base: "text-black rounded-full px-2",
  variants: {
    // ...
  }
}

Components without variants

Sometimes it can be useful to define styled components that don't have any variants, which can be done like this:

import { styled } from "classname-variants/react";

const Button = styled("button", "bg-transparent border p-2");

Styling custom components

You can style any custom React/Preact component as long as they accept a className prop (or class in case of Preact).

function MyComponent(props) {
  return <div {...props}>I'm a stylable custom component.</div>;
}

const MyStyledComponent = styled(MyComponent, {
  base: "some-class",
  variants: {
    // ...
  },
});

Polymorphic components with "as"

If you want to keep all the variants you have defined for a component but want to render a different HTML tag or a different custom component, you can use the as prop to do so:

import { styled } from "classname-variants/react";

const Button = styled("button", {
  variants: {
    //...
  },
});

The component can then be rendered as button or as anchor or even as custom component exposed by some router:

<>
  <Button>I'm a button</Button>
  <Button as="a" href="/">
    I'm a link!
  </Button>
  <Button as={Link} to="/">
    I'm a styled Link component
  </Button>
</>

Using a custom strategy to combine class names

The built-in strategy for combining multiple class names into one string is simple and straightforward:

(classes) => classes.filter(Boolean).join(" ");

If you want, you can use a custom strategy like tailwind-merge instead:

import { classNames } from "classname-variants";
import { twMerge } from "tailwind-merge";

classNames.combine = twMerge;

Tailwind IntelliSense

In order to get auto-completion for the CSS classes themselves, you can use the Tailwind CSS IntelliSense plugin for VS Code. In order to make it recognize the strings inside your variants-config, you have to somehow mark them and configure the plugin accordingly.

One way of doing so is by using tagged template literals:

import { variants, tw } from "classname-variants";

const button = variants({
  base: tw`px-5 py-2 text-white`,
  variants: {
    color: {
      neutral: tw`bg-slate-500 hover:bg-slate-400`,
      accent: tw`bg-teal-500 hover:bg-teal-400`,
    },
  },
});

You can then add the following line to your settings.json:

"tailwindCSS.experimental.classRegex": ["tw`(.+?)`"]

[!NOTE] The tw helper function is just an alias for String.raw() which has the nice side effect backslashes are not treated as escape character in JSX.

In order to get type coverage even for your Tailwind classes, you can use a tool like tailwind-ts.

License

MIT