@lightsound/cn
v2.0.3
Published
A tiny, blazing fast utility for constructing className strings conditionally
Maintainers
Readme
@lightsound/cn
A tiny, blazing fast utility for constructing className strings conditionally.
Features
- Blazing Fast: Up to 36% faster than
clsx/lite - Tiny: ~130B gzipped (smaller than clsx/lite!)
- TypeScript: Full type support out of the box
- Simple API: Strings only - no objects, no arrays, maximum performance
- Zero Dependencies: No external dependencies
Why @lightsound/cn?
| Feature | @lightsound/cn | clsx/lite | clsx | | --------------- | -------------- | --------- | ----- | | Strings only | ✅ | ✅ | ❌ | | Objects support | ❌ | ❌ | ✅ | | Arrays support | ❌ | ❌ | ✅ | | Size (gzip) | ~130B | ~141B | ~239B | | Performance | ⚡⚡⚡ | ⚡⚡ | ⚡ |
If you only use string-based class composition (the most common pattern with Tailwind CSS), @lightsound/cn provides the best performance.
Note on package size: The npm install size of
@lightsound/cnis larger thanclsxbecause the package also includes the optionaltw-mergeentry point (with bundledtailwind-merge). However, the actual bundle size added to your app depends on what you import. If you only useimport { cn } from "@lightsound/cn", tree-shaking ensures that only ~130B (gzip) is included in your final bundle.
Benchmarks
Benchmarks are run on every CI build. See the latest CI run for up-to-date results.
| Test Case | @lightsound/cn | clsx/lite | Improvement | | ---------- | -------------- | --------- | -------------- | | 2 strings | 21.24 ns | 33.15 ns | 36% faster | | 3 strings | 51.04 ns | 66.68 ns | 23% faster | | 5 strings | 67.03 ns | 87.44 ns | 23% faster | | 10 strings | 102.15 ns | 140.04 ns | 27% faster |
- Runs: 30 iterations per scenario
- Iterations: 100,000 operations per run
- Outlier removal: Top and bottom 10% trimmed
- Metric: Median (more stable than mean)
- Warmup: 10,000 iterations before measurement
Installation
# npm
npm install @lightsound/cn
# pnpm
pnpm add @lightsound/cn
# yarn
yarn add @lightsound/cn
# bun
bun add @lightsound/cnUsage
import { cn } from "@lightsound/cn";
// or
import cn from "@lightsound/cn";
// Basic usage
cn("foo", "bar");
// => 'foo bar'
// Conditional classes
cn("btn", isActive && "btn-active", isDisabled && "btn-disabled");
// => 'btn btn-active' (if isActive is true, isDisabled is false)
// With ternary expressions
cn("btn", variant === "primary" ? "btn-primary" : "btn-secondary");
// => 'btn btn-primary'
// Falsy values are ignored
cn("foo", false, null, undefined, 0, "", "bar");
// => 'foo bar'API
cn(...classes)
Combines class names into a single string. Only accepts strings - non-string values (falsy values) are ignored.
Parameters
...classes:(string | false | null | undefined | 0)[]- Any number of class name strings or falsy values
Returns
string- The combined class names separated by spaces
Compatibility with clsx/lite
@lightsound/cn is designed as a faster, drop-in replacement for clsx/lite:
// Before
import clsx from "clsx/lite";
clsx("btn", isActive && "active", "btn-primary");
// After
import { cn } from "@lightsound/cn";
cn("btn", isActive && "active", "btn-primary");TypeScript Users
You're fully covered! The type definition only accepts string | false | 0 | null | undefined, so passing objects or arrays will result in a compile-time error:
cn("btn", { active: true }); // ❌ TypeScript error
cn("btn", ["a", "b"]); // ❌ TypeScript error
cn("btn", isActive && "active"); // ✅ Works perfectlyJavaScript Users
Note that runtime behavior differs from clsx/lite when passing unsupported types:
| Input | clsx/lite | @lightsound/cn |
| ------------------ | --------- | ------------------- |
| { active: true } | "" | "[object Object]" |
| ["a", "b"] | "" | "a,b" |
If you're using JavaScript, ensure your codebase only passes strings and falsy values to cn().
Using with Tailwind Merge
If you need to merge Tailwind CSS classes (resolving conflicts like px-2 and p-3), use cn from @lightsound/cn/tw-merge:
# Install tailwind-merge as a peer dependency
bun add tailwind-mergeimport { cn } from "@lightsound/cn/tw-merge";
// Conflicting classes are merged intelligently
cn("px-2 py-1", "p-3");
// => 'p-3'
cn("text-red-500", "text-blue-500");
// => 'text-blue-500'
cn("bg-gray-100 text-gray-900", "bg-blue-500 text-white");
// => 'bg-blue-500 text-white'
// Works with conditional classes too
cn("text-gray-500", isActive && "text-blue-500");
// => 'text-blue-500' (if isActive is true)Note: The
cnfrom@lightsound/cn/tw-mergerequirestailwind-mergeto be installed separately. If you don't need Tailwind class merging, usecnfrom@lightsound/cninstead.
Tailwind CSS IntelliSense
To enable Tailwind CSS IntelliSense for cn(), add this to your VS Code settings:
{
"tailwindCSS.experimental.classRegex": [
["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
]
}License
MIT © lightsound
