Opensourcing classd: A fast and modern classNames alternative
Tags: react, javascript, webdev
Published: 2019-09-03 15:25:43

---
title: Opensourcing classd: A fast and modern classNames alternative
published: true
description: classd is a fast, modern and minimal javascript utility to conditionally compose classnames
tags: react, javascript, webdev, opensource
cover_image: https://thepracticaldev.s3.amazonaws.com/i/gd1aa6yvzxp83wbok25o.png
---

If you are a frontend developer, there is a fair chance that you have used
[Jedwatson's classNames](https://github.com/JedWatson/classnames) package in your projects. In fact, classNames is the official replacement for classSet, which was originally shipped in the React.js Addons bundle. It's one of the most used packages in the world of React.

### A simple usecase for `classNames`

For instance, if we needed to conditionally apply css classes to an element inside a React component based on the component state, we may do it like the following:

```js
class EnhancedButton extends React.Component {
    // ...
    
    render () {
        const btnClass = classNames({
            'btn': true,
            'btn-large': true,
            'btn-primary': true,
            'btn-pressed': this.state.isPressed,
            'btn-over': !this.state.isPressed && this.state.isHovered
        });
 
        return <button className={btnClass}>{this.props.label}</button>;
    }
}
```

The approach above is neat, but given that many of the classes are applied unconditionally, the pattern of setting them to  true in the configuration object becomes rather redundant. To counter that redundancy, we may write something like the following:

```js
const btnClass = classNames(
    'btn', 
    'btn-large',
    'btn-primary',
    {
        'btn-pressed': this.state.isPressed,
        'btn-over': !this.state.isPressed && this.state.isHovered  
    }
);
```

### Age of ES6 [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals)

However, as [ES6 or ES2015 template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) became [supported across mainstream browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Browser_compatibility), it became simpler to write the above snippet as:

```js
const btnClass = `
    btn 
    btn-large 
    btn-primary 
    ${this.state.isPressed ? 'btn-pressed' : ''}
    ${(!this.state.isPressed && this.state.isHovered) ? 'btn-over' : ''}
`;
```

Though the template literal way of writing class names is somewhat simpler and faster, it's **still fairly redundant** and it's not free of certain pitfalls. For example, while writing `${(!this.state.isPressed && this.state.isHovered) ? 'btn-over' : ''}`, making sure that an empty string `''` gets added if the condition fails, is quite redundant and long. And the template literal **does not remove extra/unnecessary whitespace and newlines** from the output on its own.

For example, why not write something like `${!this.state.isPressed && this.state.isHovered && 'btn-over'}` ? But there is a pitfall; if the condition resolves to `true`, the `btn-over` gets added, but if the condition resolves to `false`, the string `'false'` gets added to the output. What if we write expressions that resolve to `undefined` or `null` or `NaN` or anything similar ? Javascript would simply treat them as strings and move on. It goes without saying that there are plenty of such 'shoot in the foot' scenarios with this approach.

Moreover, what if we already have a valid configuration object and we want to reduce it to a classnames string ? There is no obvious way to do that directly using only the template literals, we may possibly do it like `Object.keys(config).reduce(...)`, or we may use the classNames package to do it for us. Of course, using the classNames package is more performant, because the package is well optimized for this usecase.

But what if there was a way to write the above example like the following without having any behavioral pitfalls and without losing any performance,

```js
const btnClass = secretSauce`
    btn
    btn-large
    btn-primary
    ${this.state.isPressed && 'btn-pressed'}
    ${!this.state.isPressed && this.state.isHovered && 'btn-over'}
`;
```



## Enter [`classd`](https://www.npmjs.com/package/classd)

`classd` is the secretSauce you needed in the example above. It's tagged template based fast and modern classNames alternative that preserves all the awesome bits of classNames and augments it with more.

The `classd` tag processes the interpolation values in the template literal according to the following specification.

1. Strings and numbers are valid values and are added to the output.
2. It drops `undefined`, `null`, `NaN` and `boolean` values.
3. If the value is an Array or an Iterable, it flattens the value and recursively processes the elements.
4. If the value is an Object or a Map, it drops keys associated with falsy values and adds the remaining keys to the output.
5. If the value is a function, it calls the function and adds its return value if that's valid
6. It removes all unnecessary whitespace.

Here are a few examples:

```js
classd`foo bar`; // => 'foo bar'
classd`foo ${null && 'bar'}`; // => 'foo'
classd`foo-${true && 'bar'}`; // => 'foo-bar'
classd`${true} ${false}`; // => ''
classd`${{ foo: true, bar: false}}`; // => 'foo'
classd`${{foo: true}} ${{bar: true}} ${{baz: false}}`; // => 'foo bar'
classd`a ${[ 'b', 'c', false && 'd' ]}`; // => 'a b c'
classd`${['a', { b: 1, c: 0 }]}`; // 'a b'
classd`    a    b  \n  ${Array(10).fill(' ')} c`; // => 'a b c'
```

### Installation and usage

The `classd` package exports 4 functions:

1. `classd` (Tag for template literals, default)
2. `classDedupe` (Tag for template literals)
3. `classdFn` (Variadic function, for compatibility, similar to `classNames`)
4. `classDedupeFn` (Variadic function, for compatibility, similar to `classNames/dedupe`)

The package is available on [NPM](https://www.npmjs.com) can be installed using package managers like `npm` and `yarn`. It can also be pulled from CDN directly into your webpages.


#### Install using package manager
```bash
# via npm 
npm install --save classd
 
# or Yarn 
yarn add classd
```

#### Use in ES6 Modules
```js
 
// ES6 import (default - classd tag for template literals)
import classd from 'classd';
 
// example use
const width = 1080;
const classes = classd`container padding-${{
    lg: width > 1280, 
    md: width > 960 && width < 1280,
    sm: width <= 960
}} margin-0 ${width > 960 && 'blue'} ${width < 960 && 'red'}`;
console.log(classes); // => 'container padding-md margin-0 blue'
 
 
// ES6 import any of the exported functions
import { classd, classDedupe, classdFn, classDedupeFn } from 'classd';
 
// example use (of classdFn)
const width = 1080;
const classes = classdFn ('container', {
    'padding-lg': width > 1280, 
    'padding-md': width > 960 && width < 1280,
    'padding-sm': width <= 960
}, (width > 960 && 'blue'), 'margin-0');
console.log(classes); // => 'container padding-md blue margin-0'
```

#### Use in Commonjs modules (Nodejs)

```js
// commonjs require classd tag for template literals (default export)
const classd = require('classd').default
 
// commonjs require any of the exported functions
const { classd, classDedupe, classdFn, classDedupeFn } = require('classd');
 
// commonjs require classd module
const classd = require('classd'); // exports can be used as classd.classd, classd.classDedupe etc
```

#### Pull from CDN
```html

<script src='https://cdn.jsdelivr.net/npm/classd@1.0/lib/index.js'></script>
<script type='text/javascript'>
    const { classd, classDedupe, classdFn, classDedupeFn } = window.classd;
    console.log(classd`container ${1 > 0 && 'blue'}`); // => 'container blue'
</script> 
```

### Well, what are `classDedupe`, `classdFn` and `classDedupeFn` ?

The `classdFn` follows the same specifications as the `classd` tag. It's a straightforward replacement for `classNames`. Everything that's valid with `classNames` is also valid with `classdFn`. In addition, `classdFn` supports passing Maps, Sets, and other Iterables as arguments. Moreover it's slightly faster than `classNames` in general usage.

If you want to migrate an existing project from using `classNames` to `classd`, using the `classdFn` is the fastest and simplest thing to do. The migration from `classNames` is as simple as:

```js
// before
import classNames from 'classnames';

//after
import { classdFn as classNames } from 'classd';
```

The `classDedupe` tag is an enhanced and about 60% slower version of the `classd` tag. It does everything that the `classd` tag does. In addition to that it checks for repeating names among the class names and ensures that each valid class name appears only once in the output string.

The `classDedupeFn` is the function equivalent of the `classDedupe` tag. It follows the same signature as `classdFn` and `classNames`.

It differs from the `classNames/dedupe` in the behaviour that, the `classNames/dedupe` unsets a class if a configuration object appearing later in its arguments unsets it; whereas `classDedupe` does not unset a class name once it's set.


### What about performance and stability ?

As conditionally applying class names is a common task in web frontend, and the functions are supposed to be called many times during a render cycle, it's imperative that the implementation of `classd` be highly performant and stable. Therefore we take the stability and performance of this package very seriously. Updates are thoroughly reviewed for performance impacts before being released. We maintain a comprehensive test suite to ensure stability.

Here is a **[JSPerf benchmark](https://jsperf.com/classd-v1-0-0-test-1)** of the `classd` package, compared against `classNames`. As we can see, the `classd` tag is as performant as `classNames`, while the `classdFn` is slightly faster.

![JSPerf benchmark](https://thepracticaldev.s3.amazonaws.com/i/ffmeo7wjdrvkdllzcp9i.png)


### Source code and contributing

The source code is available on Github for you. Any contributions in the form of Pull Request, Issue or Suggestion are welcome. **If you like it, please give it a star on Github.**

{% github GnsP/classd %}


Thanks for reading and do give it a try !