@taktikal/classnames
v2.0.0
Published
Classname utility for css modules based on the BEM naming convention
Readme
@taktikal/classnames
This package follows the BEM naming convention for css loosely with utility classes mixed in for convenience. This package was created to make writing BEM classNames a bit faster and cleaner.
Input:
import classNames from "@taktikal/classnames";
import styles from "scss/Button.scss";
const s = classNames(styles);
s("button", {
modifiers: {
primary: true,
accent: false,
disabled: true,
},
);Output in development:
Button___button--[hash] Button___button--primary--[hash] Button___button--disabled--[hash]Output in production:
[hash] [hash] [hash]Configuration
Short version
Set localIdentName to [name]___[local]--[hash:base64:5] in dev and [hash:base64:5] in prod.
Optional configuration:
// _app.tsx
import { config } from "@taktikal/classnames";
config({ /* ... */ });The options object for config:
interface {
utilityStyles?: Styles;
logWarnings?: boolean;
onWarn?: (warning: string) => void;
}Long version
Before you can use this package, the localIdentName for your css loader should look like this:
{
localIdentName: process.env.NODE_ENV === "production"
? "[hash:base64:5]"
: "[name]__[local]--[hash:base64:5]",
}[name] is the filename, [local] is the classname and [hash] is well... a hash.
An example for the file
/* Button.scss */
.btn { /* ... */ }
.btn--primary { /* ... */ }The resulting classNames would look like
{
"btn": "Button___btn--[hash]",
"btn--primary": "Button___btn--primary--[hash]"
}The next.config.js with SCSS and TypeScript should look something like this:
const withSass = require("@zeit/next-sass");
const withTypescript = require("@zeit/next-typescript");
const ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
module.exports = withTypescript(withSass({
cssModules: true,
cssLoaderOptions: {
importLoaders: 1,
localIdentName: process.env.NODE_ENV === "production"
? "[hash:base64:5]"
: "[name]___[local]--[hash:base64:5]",
},
webpack: (config, options) => {
// Add TypeScript type checking in terminal
if (options.isServer) {
config.plugins.push(new ForkTsCheckerWebpackPlugin());
}
// ...
},
}));Usage
classNames
import classNames from "@taktikal/classnames";
import styles from "src/scss/.../File.scss";
const s = classNames(styles);
...
<button
className={s({
name: "btn",
modifiers: {
primary: this.props.primary,
accent: this.props.accent,
disabled: this.props.disabled,
},
})}
utils: ["f16", "mb-40"]
>
{children}
</button>
// Or
<button
className={s("btn", {
modifiers: {
primary: this.props.primary,
accent: this.props.accent,
disabled: this.props.disabled,
},
})}
utils: ["f16", "mb-40"]
>
{children}
</button>If you are only using the className and modifiers with no utilities, you can use this shorthand.
<button className={s("btn", { primary: this.props.primary })}>
{children}
</button>If there are no modifiers or utility classes being used, you can just pass a string like this
<h1 className={s("card__title")}>Title</h1>utilityClass
If you want to use utility classes, they have to be created in the format .u-{className} { ... }
.u-colorPrimary {
color: $color-primary;
}Then you will have to let @taktikal/classNames know of your utility classes, you should do this in the entry to your app (e.g. _app.js or index.js).
import { config } from "@taktikal/classnames";
import utilStyles from "~scss/Utils.scss";
config({
utilityStyles: utilStyles,
// ...
});The utilClass function then takes in an array of utilities without the u- part of the className.
import { utilClass } from "src/utils/classNames";
<h1 className={utilClass(["mb-30", "colorPrimary"])}>Title</h1>If the class only uses one utility class, you can use the shorthand
utilClass("mb-30");The utility classes get applied first, then the className, then the modifiers.
For example:
<button
className={s({
name: "btn",
modifiers: { primary: true },
utils: ["mb-0"]
})}
>
{children}
</button>The className in the code above would be something like
Utils__u-mb-0--[hash] Button__button--[hash] Button__button--primary--[hash]Utility classes should be used when adding one or two simple things like text color, margin or padding. Avoid using utility classes heavily with BEM classNames as they can create a lot of noise.
Warnings
This structure allows us to do things like runtime validation of classNames in development. Here are some examples of warnings:
Button.scss: ClassName not found.
The className: 'button' does not exist in file 'Button.scss'.
Did you mean: 'btn'?
Button.scss: Modifier not found
The modifier: 'disabled' does not exist on class 'btn'.
A component reload (HMR or manual) will be required if the CSS is updated.
Utility class not found
The utility class 'titleBorde' does not exist.
Did you mean: 'titleBorder'?You can enable them in the config:
import { config } from "@taktikal/classnames";
config({
logWarnings: true,
// ...
});If you want to do something with the warnings, you can pass in a callback function
import { config } from "@taktikal/classnames";
config({
logWarnings: true,
onWarn: warning => {
// Do stuff with warning
},
// ...
});