[d24f17c] | 1 | # Classnames
|
---|
| 2 |
|
---|
| 3 | > A simple JavaScript utility for conditionally joining classNames together.
|
---|
| 4 |
|
---|
| 5 | <p>
|
---|
| 6 | <a aria-label="NPM version" href="https://www.npmjs.com/package/classnames">
|
---|
| 7 | <img alt="" src="https://img.shields.io/npm/v/classnames.svg?style=for-the-badge&labelColor=0869B8">
|
---|
| 8 | </a>
|
---|
| 9 | <a aria-label="License" href="#">
|
---|
| 10 | <img alt="" src="https://img.shields.io/npm/l/classnames.svg?style=for-the-badge&labelColor=579805">
|
---|
| 11 | </a>
|
---|
| 12 | <a aria-label="Thinkmill Logo" href="https://www.thinkmill.com.au/open-source?utm_campaign=github-classnames">
|
---|
| 13 | <img src="https://img.shields.io/badge/Sponsored%20BY%20Thinkmill-ed0000.svg?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTg2IiBoZWlnaHQ9IjU4NiIgdmlld0JveD0iMCAwIDU4NiA1ODYiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMF8xOTk2XzQwNikiPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTU4NiAyOTNDNTg2IDQ1NC44MTkgNDU0LjgxOSA1ODYgMjkzIDU4NkMxMzEuMTgxIDU4NiAwIDQ1NC44MTkgMCAyOTNDMCAxMzEuMTgxIDEzMS4xODEgMCAyOTMgMEM0NTQuODE5IDAgNTg2IDEzMS4xODEgNTg2IDI5M1pNMjA1Ljc3NiAzNTguOTQ0QzE5MS4zNzYgMzU4Ljk0NCAxODUuOTA0IDM1Mi4zMiAxODUuOTA0IDMzNS45MDRWMjYyLjc1MkgyMTQuNDE2VjIzNy42OTZIMTg1LjkwNFYyMDEuMTJIMTUzLjA3MlYyMzcuNjk2SDEyOC41OTJWMjYyLjc1MkgxNTMuMDcyVjM0MC44QzE1My4wNzIgMzcyLjc2OCAxNjYuNjA4IDM4NS43MjggMTk3LjQyNCAzODUuNzI4QzIwMy40NzIgMzg1LjcyOCAyMTAuOTYgMzg0LjU3NiAyMTUuODU2IDM4My4xMzZWMzU3LjUwNEMyMTMuNTUyIDM1OC4zNjggMjA5LjUyIDM1OC45NDQgMjA1Ljc3NiAzNTguOTQ0Wk00MDcuMzc2IDIzNC4yNEMzODUuMiAyMzQuMjQgMzcxLjA4OCAyNDQuMDMyIDM2MC40MzIgMjYwLjczNkMzNTIuOTQ0IDI0My40NTYgMzM3LjM5MiAyMzQuMjQgMzE3LjIzMiAyMzQuMjRDMjk5Ljk1MiAyMzQuMjQgMjg2Ljk5MiAyNDEuMTUyIDI3Ni42MjQgMjU1LjI2NEgyNzYuMDQ4VjIzNy42OTZIMjQ0LjY1NlYzODRIMjc3LjQ4OFYzMDUuNjY0QzI3Ny40ODggMjc3LjQ0IDI4OC43MiAyNjAuNzM2IDMwOC4zMDQgMjYwLjczNkMzMjUuMjk2IDI2MC43MzYgMzM0LjUxMiAyNzIuODMyIDMzNC41MTIgMjkzLjU2OFYzODRIMzY3LjM0NFYzMDUuMDg4QzM2Ny4zNDQgMjc3LjE1MiAzNzguODY0IDI2MC43MzYgMzk4LjE2IDI2MC43MzZDNDE0LjU3NiAyNjAuNzM2IDQyNC42NTYgMjcxLjEwNCA0MjQuNjU2IDI5Ny4wMjRWMzg0SDQ1Ny40ODhWMjkzLjg1NkM0NTcuNDg4IDI1NC40IDQzOC40OCAyMzQuMjQgNDA3LjM3NiAyMzQuMjRaIiBmaWxsPSJ3aGl0ZSIvPgo8L2c+CjxkZWZzPgo8Y2xpcFBhdGggaWQ9ImNsaXAwXzE5OTZfNDA2Ij4KPHJlY3Qgd2lkdGg9IjU4NiIgaGVpZ2h0PSI1ODYiIGZpbGw9IndoaXRlIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg==&labelColor=C60200&locoColor=white&logoWidth=0">
|
---|
| 14 | </a>
|
---|
| 15 |
|
---|
| 16 | </p>
|
---|
| 17 |
|
---|
| 18 | Install from the [npm registry](https://www.npmjs.com/) with your package manager:
|
---|
| 19 | ```bash
|
---|
| 20 | npm install classnames
|
---|
| 21 | ```
|
---|
| 22 |
|
---|
| 23 | Use with [Node.js](https://nodejs.org/en/), [Browserify](https://browserify.org/), or [webpack](https://webpack.github.io/):
|
---|
| 24 |
|
---|
| 25 | ```js
|
---|
| 26 | const classNames = require('classnames');
|
---|
| 27 | classNames('foo', 'bar'); // => 'foo bar'
|
---|
| 28 | ```
|
---|
| 29 |
|
---|
| 30 | Alternatively, you can simply include `index.js` on your page with a standalone `<script>` tag and it will export a global `classNames` method, or define the module if you are using RequireJS.
|
---|
| 31 |
|
---|
| 32 | ### Project philosophy
|
---|
| 33 |
|
---|
| 34 | We take the stability and performance of this package seriously, because it is run millions of times a day in browsers all around the world. Updates are thoroughly reviewed for performance implications before being released, and we have a comprehensive test suite.
|
---|
| 35 |
|
---|
| 36 | Classnames follows the [SemVer](https://semver.org/) standard for versioning.
|
---|
| 37 |
|
---|
| 38 | There is also a [Changelog](https://github.com/JedWatson/classnames/blob/master/HISTORY.md).
|
---|
| 39 |
|
---|
| 40 | ## Usage
|
---|
| 41 |
|
---|
| 42 | The `classNames` function takes any number of arguments which can be a string or object.
|
---|
| 43 | The argument `'foo'` is short for `{ foo: true }`. If the value associated with a given key is falsy, that key won't be included in the output.
|
---|
| 44 |
|
---|
| 45 | ```js
|
---|
| 46 | classNames('foo', 'bar'); // => 'foo bar'
|
---|
| 47 | classNames('foo', { bar: true }); // => 'foo bar'
|
---|
| 48 | classNames({ 'foo-bar': true }); // => 'foo-bar'
|
---|
| 49 | classNames({ 'foo-bar': false }); // => ''
|
---|
| 50 | classNames({ foo: true }, { bar: true }); // => 'foo bar'
|
---|
| 51 | classNames({ foo: true, bar: true }); // => 'foo bar'
|
---|
| 52 |
|
---|
| 53 | // lots of arguments of various types
|
---|
| 54 | classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux'
|
---|
| 55 |
|
---|
| 56 | // other falsy values are just ignored
|
---|
| 57 | classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1'
|
---|
| 58 | ```
|
---|
| 59 |
|
---|
| 60 | Arrays will be recursively flattened as per the rules above:
|
---|
| 61 |
|
---|
| 62 | ```js
|
---|
| 63 | const arr = ['b', { c: true, d: false }];
|
---|
| 64 | classNames('a', arr); // => 'a b c'
|
---|
| 65 | ```
|
---|
| 66 |
|
---|
| 67 | ### Dynamic class names with ES2015
|
---|
| 68 |
|
---|
| 69 | If you're in an environment that supports [computed keys](https://www.ecma-international.org/ecma-262/6.0/#sec-object-initializer) (available in ES2015 and Babel) you can use dynamic class names:
|
---|
| 70 |
|
---|
| 71 | ```js
|
---|
| 72 | let buttonType = 'primary';
|
---|
| 73 | classNames({ [`btn-${buttonType}`]: true });
|
---|
| 74 | ```
|
---|
| 75 |
|
---|
| 76 | ### Usage with React.js
|
---|
| 77 |
|
---|
| 78 | This package is the official replacement for `classSet`, which was originally shipped in the React.js Addons bundle.
|
---|
| 79 |
|
---|
| 80 | One of its primary use cases is to make dynamic and conditional `className` props simpler to work with (especially more so than conditional string manipulation). So where you may have the following code to generate a `className` prop for a `<button>` in React:
|
---|
| 81 |
|
---|
| 82 | ```js
|
---|
| 83 | import React, { useState } from 'react';
|
---|
| 84 |
|
---|
| 85 | export default function Button (props) {
|
---|
| 86 | const [isPressed, setIsPressed] = useState(false);
|
---|
| 87 | const [isHovered, setIsHovered] = useState(false);
|
---|
| 88 |
|
---|
| 89 | let btnClass = 'btn';
|
---|
| 90 | if (isPressed) btnClass += ' btn-pressed';
|
---|
| 91 | else if (isHovered) btnClass += ' btn-over';
|
---|
| 92 |
|
---|
| 93 | return (
|
---|
| 94 | <button
|
---|
| 95 | className={btnClass}
|
---|
| 96 | onMouseDown={() => setIsPressed(true)}
|
---|
| 97 | onMouseUp={() => setIsPressed(false)}
|
---|
| 98 | onMouseEnter={() => setIsHovered(true)}
|
---|
| 99 | onMouseLeave={() => setIsHovered(false)}
|
---|
| 100 | >
|
---|
| 101 | {props.label}
|
---|
| 102 | </button>
|
---|
| 103 | );
|
---|
| 104 | }
|
---|
| 105 | ```
|
---|
| 106 |
|
---|
| 107 | You can express the conditional classes more simply as an object:
|
---|
| 108 |
|
---|
| 109 | ```js
|
---|
| 110 | import React, { useState } from 'react';
|
---|
| 111 | import classNames from 'classnames';
|
---|
| 112 |
|
---|
| 113 | export default function Button (props) {
|
---|
| 114 | const [isPressed, setIsPressed] = useState(false);
|
---|
| 115 | const [isHovered, setIsHovered] = useState(false);
|
---|
| 116 |
|
---|
| 117 | const btnClass = classNames({
|
---|
| 118 | btn: true,
|
---|
| 119 | 'btn-pressed': isPressed,
|
---|
| 120 | 'btn-over': !isPressed && isHovered,
|
---|
| 121 | });
|
---|
| 122 |
|
---|
| 123 | return (
|
---|
| 124 | <button
|
---|
| 125 | className={btnClass}
|
---|
| 126 | onMouseDown={() => setIsPressed(true)}
|
---|
| 127 | onMouseUp={() => setIsPressed(false)}
|
---|
| 128 | onMouseEnter={() => setIsHovered(true)}
|
---|
| 129 | onMouseLeave={() => setIsHovered(false)}
|
---|
| 130 | >
|
---|
| 131 | {props.label}
|
---|
| 132 | </button>
|
---|
| 133 | );
|
---|
| 134 | }
|
---|
| 135 | ```
|
---|
| 136 |
|
---|
| 137 | Because you can mix together object, array and string arguments, supporting optional `className` props is also simpler as only truthy arguments get included in the result:
|
---|
| 138 |
|
---|
| 139 | ```js
|
---|
| 140 | const btnClass = classNames('btn', this.props.className, {
|
---|
| 141 | 'btn-pressed': isPressed,
|
---|
| 142 | 'btn-over': !isPressed && isHovered,
|
---|
| 143 | });
|
---|
| 144 | ```
|
---|
| 145 |
|
---|
| 146 | ### Alternate `dedupe` version
|
---|
| 147 |
|
---|
| 148 | There is an alternate version of `classNames` available which correctly dedupes classes and ensures that falsy classes specified in later arguments are excluded from the result set.
|
---|
| 149 |
|
---|
| 150 | This version is slower (about 5x) so it is offered as an opt-in.
|
---|
| 151 |
|
---|
| 152 | To use the dedupe version with Node.js, Browserify, or webpack:
|
---|
| 153 |
|
---|
| 154 | ```js
|
---|
| 155 | const classNames = require('classnames/dedupe');
|
---|
| 156 |
|
---|
| 157 | classNames('foo', 'foo', 'bar'); // => 'foo bar'
|
---|
| 158 | classNames('foo', { foo: false, bar: true }); // => 'bar'
|
---|
| 159 | ```
|
---|
| 160 |
|
---|
| 161 | For standalone (global / AMD) use, include `dedupe.js` in a `<script>` tag on your page.
|
---|
| 162 |
|
---|
| 163 | ### Alternate `bind` version (for [css-modules](https://github.com/css-modules/css-modules))
|
---|
| 164 |
|
---|
| 165 | If you are using [css-modules](https://github.com/css-modules/css-modules), or a similar approach to abstract class 'names' and the real `className` values that are actually output to the DOM, you may want to use the `bind` variant.
|
---|
| 166 |
|
---|
| 167 | _Note that in ES2015 environments, it may be better to use the "dynamic class names" approach documented above._
|
---|
| 168 |
|
---|
| 169 | ```js
|
---|
| 170 | const classNames = require('classnames/bind');
|
---|
| 171 |
|
---|
| 172 | const styles = {
|
---|
| 173 | foo: 'abc',
|
---|
| 174 | bar: 'def',
|
---|
| 175 | baz: 'xyz',
|
---|
| 176 | };
|
---|
| 177 |
|
---|
| 178 | const cx = classNames.bind(styles);
|
---|
| 179 |
|
---|
| 180 | const className = cx('foo', ['bar'], { baz: true }); // => 'abc def xyz'
|
---|
| 181 | ```
|
---|
| 182 |
|
---|
| 183 | Real-world example:
|
---|
| 184 |
|
---|
| 185 | ```js
|
---|
| 186 | /* components/submit-button.js */
|
---|
| 187 | import { useState } from 'react';
|
---|
| 188 | import classNames from 'classnames/bind';
|
---|
| 189 | import styles from './submit-button.css';
|
---|
| 190 |
|
---|
| 191 | const cx = classNames.bind(styles);
|
---|
| 192 |
|
---|
| 193 | export default function SubmitButton ({ store, form }) {
|
---|
| 194 | const [submissionInProgress, setSubmissionInProgress] = useState(store.submissionInProgress);
|
---|
| 195 | const [errorOccurred, setErrorOccurred] = useState(store.errorOccurred);
|
---|
| 196 | const [valid, setValid] = useState(form.valid);
|
---|
| 197 |
|
---|
| 198 | const text = submissionInProgress ? 'Processing...' : 'Submit';
|
---|
| 199 | const className = cx({
|
---|
| 200 | base: true,
|
---|
| 201 | inProgress: submissionInProgress,
|
---|
| 202 | error: errorOccurred,
|
---|
| 203 | disabled: valid,
|
---|
| 204 | });
|
---|
| 205 |
|
---|
| 206 | return <button className={className}>{text}</button>;
|
---|
| 207 | }
|
---|
| 208 | ```
|
---|
| 209 |
|
---|
| 210 | ## Polyfills needed to support older browsers
|
---|
| 211 |
|
---|
| 212 | #### `classNames >=2.0.0`
|
---|
| 213 |
|
---|
| 214 | `Array.isArray`: see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray) for details about unsupported older browsers (e.g. <= IE8) and a simple polyfill.
|
---|
| 215 |
|
---|
| 216 | ## LICENSE [MIT](LICENSE)
|
---|
| 217 |
|
---|
| 218 | Copyright (c) 2018 Jed Watson.
|
---|
| 219 | Copyright of the Typescript bindings are respective of each contributor listed in the definition file.
|
---|