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 🙏

© 2026 – Pkg Stats / Ryan Hefner

clsx-cn

v1.1.0

Published

clsx, cva, and cn for Tailwind CSS — includes tailwind-merge for conflict-aware cn().

Readme

clsx-cn

clsx, cva, and cn for Tailwind CSS and shadcn/ui-style components — bundled in one package.

tailwind-merge is included as a dependency — cn and merge-enabled exports use it automatically.

What’s included

| In clsx-cn | Role | |--------------|------| | clsx | Join conditional class names | | cva, cx, compose | Variant-driven classes (CVA-compatible API) | | cn | clsx + tailwind-merge | | createCn | Custom cn with your own merge function | | cvaWithTwMerge, cxWithTwMerge, composeWithTwMerge | Variants with merge in the internal pipeline | | defineConfig, defineConfigWithTwMerge | Custom cva / cx / compose instances |

Default export is clsx.

Install

pnpm add clsx-cn
# npm install clsx-cn
# yarn add clsx-cn

Works in browsers, Node, Bun, and bundlers (ESM, sideEffects: false).

Quick start

Typical shadcn lib/utils.ts:

import { cn, type ClassValue, type MaybeClassValue } from 'clsx-cn'

export { cn, type ClassValue, type MaybeClassValue }

Button with variants:

import { cn, cva, type VariantProps } from 'clsx-cn'

const buttonVariants = cva(
  'inline-flex items-center justify-center rounded-md text-sm font-medium',
  {
    variants: {
      variant: {
        default: 'bg-primary text-primary-foreground',
        outline: 'border border-input bg-background',
      },
      size: {
        default: 'h-9 px-4 py-2',
        sm: 'h-8 px-3 text-xs',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  },
)

type ButtonProps = VariantProps<typeof buttonVariants>

function Button({
  className,
  variant,
  size,
  ...props
}: ButtonProps & React.ComponentProps<'button'>) {
  return (
    <button
      className={cn(buttonVariants({ variant, size }), className)}
      {...props}
    />
  )
}

Choosing an API

| Goal | Use | |------|-----| | shadcn-style class merging | cn(...) | | Variants only (merge at call site with cn) | cva + cn(buttonVariants({ ... }), className) | | Variants with merge built into the pipeline | cvaWithTwMerge (or defineConfigWithTwMerge) | | Join classes without Tailwind deduping | clsx | | Custom Tailwind merge config | createCn + extendTailwindMerge from tailwind-merge |

API

cn

Runs clsx, then tailwind-merge:

import { cn } from 'clsx-cn'

cn('px-2 py-1', 'p-3') // → 'p-3'
cn('text-sm', false && 'hidden') // → 'text-sm'

Headless UI (and similar) className resolvers: cn accepts a prop that may be static or a state callback — alone or after a base class. When a resolver is passed, cn returns a merged callback for the component:

<Button className={cn(className)} />

<DialogBackdrop
  className={cn(
    'fixed inset-0 bg-black/80 data-open:fade-in-0',
    className,
  )}
/>

clsx

Conditional classes only — no conflict resolution:

import { clsx } from 'clsx-cn'

clsx('foo', { bar: true, baz: false }) // → 'foo bar'

cva

CVA-compatible variants. Both call styles:

import { cva } from 'clsx-cn'

const badge = cva('rounded-full px-2', {
  variants: { tone: { info: 'bg-blue-100', warn: 'bg-yellow-100' } },
})

const badgeAlt = cva({
  base: 'rounded-full px-2',
  variants: { tone: { info: 'bg-blue-100', warn: 'bg-yellow-100' } },
})
  • Compound variants: each matching rule contributes class or className (one per rule, class preferred).
  • Boolean / 0 keys: false"false", 0"0" (same as upstream CVA).

cvaWithTwMerge / cxWithTwMerge / composeWithTwMerge

Same as cva / cx / compose, but the internal cx step runs tailwind-merge:

import { cvaWithTwMerge as cva } from 'clsx-cn'

const box = cva('p-4', {
  variants: { size: { sm: 'p-2', lg: 'p-8' } },
})

box({ size: 'sm' }) // → 'p-2' (not 'p-4 p-2')

createCn + tailwind-merge

Customize merge behavior (install resolves tailwind-merge transitively; import it for custom config):

import { createCn } from 'clsx-cn'
import { extendTailwindMerge } from 'tailwind-merge'

const twMerge = extendTailwindMerge({
  classGroups: {
    shadow: [{ shadow: ['100', '200', '300'] }],
  },
})

export const cn = createCn(twMerge)

defineConfig / defineConfigWithTwMerge

import { defineConfig, defineConfigWithTwMerge } from 'clsx-cn'

const { cva, cx } = defineConfig({
  hooks: {
    onComplete: (className) => className, // after clsx
  },
})

const { cva: cvaMerged } = defineConfigWithTwMerge()

Deprecated hook cx:done is still supported; onComplete takes precedence.

compose

import { cva, compose } from 'clsx-cn'

const layout = compose(
  cva('flex', { variants: { direction: { row: 'flex-row', col: 'flex-col' } } }),
  cva('', { variants: { gap: { sm: 'gap-2', lg: 'gap-4' } } }),
)

layout({ direction: 'row', gap: 'sm' })

Compared to installing separately

| Before | After | |--------|--------| | clsx + class-variance-authority + tailwind-merge (+ often a local cn helper) | clsx-cn |

You get one install for clsx, CVA, and Tailwind class merging. For a custom merge config, use createCn with extendTailwindMerge from tailwind-merge.

Development

pnpm install
pnpm run build
pnpm run typecheck
pnpm run dev   # watch

License

MIT © nunesunil