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 🙏

© 2025 – Pkg Stats / Ryan Hefner

responsive-class-variants

v1.2.3

Published

rcv helps you create responsive class variants

Readme

Responsive Class Variants

rcv helps you create responsive class variants. It handles the logic of generating the classes and prefixes them with the breakpoint name. You will still need to make sure, that the CSS is available for the breakpoints you use.

Features

  • Handles the logic of generating the classes and prefixes them with the breakpoint name.
  • You just need to provide the base classes, the variants and optionally compound variants.
  • Slots support: Create multiple class-generating functions for different parts of a component.
  • You can use the default breakpoints (sm, md, lg, xl) or provide your own.
  • You can pass an optional onComplete callback to the createRcv function. This callback will be called with the generated classes. Helpful if you want to pass your classes to a library like twMerge.

Installation

pnpm add responsive-class-variants

Usage

rcv is a function that takes a config object and returns a function that takes a props object and returns a string of classes. The props object is an object with the keys of the variants and the values are the values of the variants.

The config object has the following properties:

  • base: The base classes that are always applied.
  • variants: An object with the keys of the variants and the values are the values of the variants.
  • compoundVariants: An array of compound variants that apply additional classes when multiple variants have specific values.
  • onComplete: An optional callback function that receives the generated classes and returns the final classes.

rcv works very well with tailwindcss but it can be used with any CSS solution.

Basic Usage (Single Component):

const getButtonVariants = rcv({
  base: "px-4 py-2 rounded",
  variants: {     
    intent: {
       primary: "bg-blue-500 text-white",
       secondary: "bg-gray-200 text-gray-800"
     },
     size: {
       small: "text-sm",
       large: "text-lg"
     },
     disabled: {
       true: "opacity-50 cursor-not-allowed"
     },
     error: {
       true: "bg-red-500 text-white"
     }
   },
   compoundVariants: [
     {
       disabled: true,
       error: true,
       className: "opacity-50 cursor-not-allowed"
     }
   ]
 });

 // Usage:
 getButtonVariants({ intent: "primary", size: "large", disabled: true })
 // Or with responsive values:
 getButtonVariants({ intent: { initial: "primary", md: "secondary" } })

Slots (Multi-Part Components):

When you need to style multiple parts of a component, you can use slots. This is perfect for complex components like cards, alerts, or modals.

Simple Slots

const getCardVariants = rcv({
  slots: {
    base: "rounded-xl p-8 bg-white dark:bg-gray-900",
    title: "text-xl font-bold text-gray-900 dark:text-white",
    content: "text-gray-700 dark:text-gray-300"
  }
});

// Usage - call the factory before destructuring the slot functions
const { base, title, content } = getCardVariants();

// Apply to your JSX - no arguments needed for simple slots!
<div className={base()}>
  <h2 className={title()}>Card Title</h2>
  <p className={content()}>Card content goes here</p>
</div>

Slots with Variants

Variants can target specific slots by using objects instead of strings:

const getCardVariants = rcv({
  slots: {
    base: "rounded-xl p-8 bg-white dark:bg-gray-900",
    title: "text-xl font-bold text-gray-900 dark:text-white",
    content: "text-gray-700 dark:text-gray-300"
  },
  variants: {
    shadow: {
      none: {},
      sm: { base: "shadow-sm" },
      md: { base: "shadow-md" },
      lg: { base: "shadow-lg" }
    },
    size: {
      sm: { 
        title: "text-lg",
        content: "text-sm"
      },
      lg: { 
        title: "text-2xl",
        content: "text-lg"
      }
    }
  }
});

const { base, title, content } = getCardVariants();

// Usage with variants
<div className={base({ shadow: "md", size: "lg" })}>
  <h2 className={title({ shadow: "md", size: "lg" })}>Large Card Title</h2>
  <p className={content({ shadow: "md", size: "lg" })}>Larger content text</p>
</div>

Slots with Compound Variants

Compound variants can also target specific slots using the class property:

const getAlertVariants = rcv({
  slots: {
    root: "rounded py-3 px-5 mb-4",
    title: "font-bold mb-1",
    message: "text-sm"
  },
  variants: {
    variant: {
      outlined: { root: "border" },
      filled: {}
    },
    severity: {
      error: {},
      success: {},
      warning: {}
    }
  },
  compoundVariants: [
    {
      variant: "outlined",
      severity: "error",
      class: {
        root: "border-red-700 dark:border-red-500",
        title: "text-red-700 dark:text-red-500",
        message: "text-red-600 dark:text-red-500"
      }
    },
    {
      variant: "filled",
      severity: "success",
      class: {
        root: "bg-green-100 dark:bg-green-800",
        title: "text-green-900 dark:text-green-50",
        message: "text-green-700 dark:text-green-200"
      }
    }
  ]
});

const { root, title, message } = getAlertVariants();

// Usage
<div className={root({ variant: "outlined", severity: "error" })}>
  <h3 className={title({ variant: "outlined", severity: "error" })}>Error!</h3>
  <p className={message({ variant: "outlined", severity: "error" })}>Something went wrong</p>
</div>

Slots with Responsive Values

Slots work seamlessly with responsive values:

const getCardVariants = rcv({
  slots: {
    base: "rounded-xl p-4 bg-white",
    title: "font-bold text-gray-900"
  },
  variants: {
    size: {
      sm: { 
        base: "p-2",
        title: "text-sm"
      },
      lg: { 
        base: "p-8",
        title: "text-2xl"
      }
    }
  }
});

const { base, title } = getCardVariants();

// Responsive usage
<div className={base({ size: { initial: "sm", md: "lg" } })}>
  <h2 className={title({ size: { initial: "sm", md: "lg" } })}>Responsive Card</h2>
</div>

With css classes (like BEM or any other naming convention):

const getButtonVariants = rcv({
  base: "btn",
  variants: {
    intent: {
      primary: "btn--primary",
      secondary: "btn--secondary"
    },
    size: {
      small: "btn--sm",
      large: "btn--lg"
    },
    disabled: {
      true: "btn--disabled"
    },
    error: {
      true: "btn--error"
    }
  },
  compoundVariants: [
    {
      disabled: true,
      error: true,
      className: "btn--disabled--error"
    }
  ]
});

Because of the tailwind JIT compiler, you need to make sure, that all possible classes are available with your component. Let's say you have a button component and you want to use the size variant responsively. You need to make sure, that the small and large classes are available with your component. You can e.g. define a SIZES object to define the classes for each size and breakpoints. This example assumes you have the default breakpoints (sm, md, lg, xl).

const SIZES = {
 sm: {
   sm: "sm:text-sm",
   lg: "sm:text-lg"
 },
 md: {
   sm: "md:text-sm",
   lg: "md:text-lg"
 },
 lg: {
   sm: "lg:text-sm",
   lg: "lg:text-lg"
 },
 xl: {
   sm: "xl:text-sm",
   lg: "xl:text-lg"
 }
}

The structure doesn't really matter, the classes just need to be in the compiled javascript to be picked up by the JIT compiler.

Custom breakpoints (via createRcv)

const rcv = createRcv(['mobile', 'tablet', 'desktop']);

const getButtonVariants = rcv({
  base: "px-4 py-2 rounded",
  variants: {
    intent: {
      primary: "bg-blue-500 text-white",
      secondary: "bg-gray-200 text-gray-800"
    }
  }
});

// Usage with custom breakpoints:
getButtonVariants({ intent: { initial: "primary", mobile: "secondary", desktop: "primary" } })

// Works with slots too:
const getCardVariants = rcv({
  slots: {
    base: "rounded-xl p-4 bg-white",
    title: "font-bold text-gray-900"
  },
  variants: {
    size: {
      sm: { base: "p-2", title: "text-sm" },
      lg: { base: "p-8", title: "text-2xl" }
    }
  }
});

onComplete callback (via createRcv)

You can pass an optional onComplete callback to the createRcv function. This callback will be called with the generated classes. Helpful if you want to pass your classes to a library like tailwind Merge.

const rcv = createRcv(['mobile', 'tablet', 'desktop'], (classes) => twMerge(classes));

Typescript helpers

rcv provides a helper type to make it easier to type your component props.

If you use the default breakpoints (sm, md, lg, xl), you can use the ResponsiveValue type to make existing props responsive.

type ButtonProps = {
  intent: "primary" | "secondary";
  size: ResponsiveValue<"sm" | "lg">;
  disabled: boolean;
  error: boolean;
};

If you use custom breakpoints you need to pass the breakpoints to the ResponsiveValue type.

import { createRcv, type ResponsiveValue as RcvResponsiveValue } from "responsive-class-variants";

const breakpoints = ["tablet", "desktop", "wide"] as const;

export const customRcv = createRcv(breakpoints);

type Breakpoints = (typeof breakpoints)[number];

export type ResponsiveValue<T> = RcvResponsiveValue<T, Breakpoints>;