tl;dr
There's no shortage of solutions to the problem of global CSS. The properties that set this one apart:
- It's very simple, on the order of 100 well-tested lines of JS, with 0 dependencies.
- Works with all your favorite styling languages, including Sass, PostCSS, Less and Stylus.
- Doesn't rely on a specific bundler, meaning you can use Browserify, webpack, RequireJS, or any bundler-de-jour.
- Isn't tied to any UI framework, but has opt-in convenience for use with React.
- Generates stable and predictable class names for external parties, such as the consumers of your UI library on npm, or your test automation system.
The core API is very straightforward:
var ns = require('css-ns')('MyComponent');
ns('foo') // "MyComponent-foo"
Everything else is just added convenience for working with class names:
// Multiple class names:
ns('foo bar') // "MyComponent-foo MyComponent-bar"
// Dynamic list of class names, filtering falsy ones out:
ns([ 'foo', null, 'bar' ]) // "MyComponent-foo MyComponent-bar"
// Providing class names as object properties:
ns({ foo: true, unwanted: false, bar: true }) // "MyComponent-foo MyComponent-bar"
// Escaping the namespace where needed:
ns('foo =icon-star') // "MyComponent-foo icon-star"
And with the optional React integration:
// Simplest possible integration:
<div className={ns('foo')} /> // <div class="MyComponent-foo">
// Namespacing existing elements:
ns(<div className="foo" />) // <div class="MyComponent-foo">
// Creating a namespace-bound React instance:
var { React } = require('./config/css-ns')('MyComponent');
<div className="foo" /> // <div class="MyComponent-foo">
<div className={{ foo: true }} /> // <div class="MyComponent-foo">
Creating a bound React instance is a powerful way of enforcing a namespace within a file.
Getting started
Install with:
$ npm install --save css-ns
Then, create a namespace function:
var ns = require('css-ns')('MyComponent');
Usage example
It's easy to add css-ns
to most frameworks and workflows. For example, if you happen to use React, this is what MyComponent.js
might look like:
var ns = require('css-ns')('MyComponent');
module.exports = (props) => (
<div className={ns('this')}>
<button className={ns({ submit: true, isActive: props.isActive })} />
</div>
);
This component produces the following DOM when rendered with truthy props.isActive
:
<div class="MyComponent">
<button class="MyComponent-submit MyComponent-isActive"></button>
</div>
To ensure you won't accidentally forget to wrap a className
with ns()
, you can use the optional React integration. Here we also use __filename
(supported by node, Browserify, webpack and others) to make sure our namespace always matches the file that contains it:
// Instead of requiring React directly, let's require a
// wrapped version that's bound to the current namespace:
var { React } = require('./config/css-ns')(__filename);
// All className props within this file are automatically fed through css-ns. There's really no
// magic here; keep in mind <div /> is just JSX sugar for React.createElement('div', {});
module.exports = (props) => (
<div className="this">
<button className={{ submit: true, isActive: props.isActive }} />
</div>
);
You'll note we also require('./config/css-ns')
; this is explained thoroughly in Configuration.
Finally, if we were to style this component using Sass, those styles could be:
.MyComponent {
background: white;
&-isActive {
background: cyan;
}
&-submit {
font-weight: bold;
}
}
The &
reference is a Sass built-in, no plugins needed.
Configuration
The simple require('css-ns')(__filename)
one-liner might very well be enough for some projects. If you need to set some options, however, it might become tedious to repeat them in every file. Having an .*rc
-style configuration file would tie css-ns
to environments with a file system (browsers don't have one), so to create a configuration file, just use whatever module system you're already using. Let's say we're using ES6:
// e.g. config/css-ns.js
import { createCssNs } from 'css-ns';
export default namespace => createCssNs({
namespace,
exclude: /^fa-/ // exclude Font Awesome classes, as they have their own "fa-" namespace
});
Then, to create a local namespace with the exclude
option set:
import createCssNs from './config/css-ns'; // instead of the global 'css-ns'
const ns = createCssNs(__filename);
Or, in the more compact CommonJS form:
var ns = require('./config/css-ns')(__filename);
There's also a complete demo app configured this way.
API
The createCssNs()
factory takes either a string or an options object as its single argument:
var createCssNs = require('css-ns');
// This shorthand syntax...
var ns = createCssNs(__filename);
// ...is equivalent to this options object:
var ns = createCssNs({
namespace: __filename
});
All available options are:
Option | Type | Default | Description |
---|---|---|---|
namespace |
string | (none) | Mandatory base part for the namespace, e.g. "MyComponent" or __filename . For convenience, a possible file path and suffix are ignored, so that if the provided value is "/path/to/MyComponent.js" , the resulting namespace will still be "MyComponent" . |
prefix |
string | "" |
All namespaces are prefixed with this string. By default, namespaces aren't prefixed. Class names that begin with this prefix are automatically excluded . |
include |
regex | /^[a-z]/ |
Only class names matching this regex are namespaced. By default, only ones starting in lower-case are. This works out nicely with upper-cased namespace values: it ensures only one namespace can be applied to a class name, and calling ns() multiple times has the same effect as calling it once. |
exclude |
regex | /^$/ |
Class names matching this regex are not namespaced. By default, nothing is excluded (since /^$/ won't ever match a class name). When both include and exclude match, exclude wins. |
escape |
string | "=" |
Class names beginning with this string are not namespaced. By default, = is used for escaping. |
self |
regex | /^this$/ |
Class names matching this regex are replaced with the name of the namespace itself. This allows you to e.g. mark the root of your UI component without any suffixes, just the component name. |
glue |
string | "-" |
This string is used to combine the namespace and the class name. |
React |
object | (none) | Providing this option enables React integration. When provided, must be an instance of React, e.g. { react: require('react') } . See Use with React for details. |
Use with React
By default, css-ns
doesn't use or depend on React in any way. This ensures bundlers don't get confused in projects that don't need the React integration. To enable React integration, provide the React
option for createCssNs()
:
// e.g. config/css-ns.js
var createCssNs = require('css-ns');
var React = require('react');
module.exports = function(namespace) {
return createCssNs({
namespace,
React
});
};
Wrapped React instance
Providing the React
option will expose a wrapped React instance on resulting namespace functions:
var ns = require('./config/css-ns')('MyComponent');
ns.React.createElement('div', { className: 'foo' }) // <div class="MyComponent-foo">
Because JSX just sugar for React.createElement()
calls, this allows you to:
var { React } = require('./config/css-ns')('MyComponent');
<div className="foo"> // <div class="MyComponent-foo">
The wrapping is in fact extremely thin, and everything except React.createElement()
is just inherited from React proper.
Namespacing existing React elements
Providing the React
option will also enable support for React elements in the ns()
function, so that:
var React = require('react'); // vanilla, non-wrapped React instance
ns(<div className="foo" />) // <div class="MyComponent-foo">
This can be useful in the (rare) cases where your namespace has to be dynamically applied to elements created by some other module. Because the only safe way to do this is to invoke React.cloneElement()
under the hood, it can have performance implications if overused.
Use with Sass
Use with Sass requires no special support or plugins. This input:
.MyComponent {
background: cyan;
&-row {
color: red;
}
}
will be compiled to this CSS:
.MyComponent {
background: cyan;
}
.MyComponent-row {
color: red;
}
Use with PostCSS
Use with PostCSS is easiest with the postcss-nested
plugin. With it, this input:
.MyComponent {
background: cyan;
&-row {
color: red;
}
}
will be compiled to this CSS:
.MyComponent {
background: cyan;
}
.MyComponent-row {
color: red;
}
Use with Less
Use with Less requires no special support or plugins. This input:
.MyComponent {
background: cyan;
&-row {
color: red;
}
}
will be compiled to this CSS:
.MyComponent {
background: cyan;
}
.MyComponent-row {
color: red;
}
Use with Stylus
Use with Stylus requires no special support or plugins. This input:
.MyComponent
background: cyan
&-row
color: red
will be compiled to this CSS:
.MyComponent {
background: cyan;
}
.MyComponent-row {
color: red;
}
Test suite
The web-based test suite is available at http://jrw.fi/css-ns/.
Release
- Bump version number in
package.json
$ git commit -m "Version bump for release." && git push
$ cp css-ns.{js,d.ts} package.json dist/
$ cd dist && npm publish
- Create release on GitHub, e.g.
v1.1.3