• Stars
    star
    159
  • Rank 235,916 (Top 5 %)
  • Language
    TypeScript
  • Created almost 3 years ago
  • Updated 7 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Variant API for plain class names

classname-variants

Stitches-like variant API for plain class names.

The library is framework-agnostic and can be used with any kind of CSS flavor.

It is especially useful though if used with Tailwind or CSS Modules in combination with React, as it provides some dedicated helpers and even allows for a styled-components like API, but with class names instead of styles!

Edit classname-variants/react

Basics

Let's assume we want to build a button component with Tailwind CSS that comes in different sizes and colors.

It consists of some base classes that are always present as well as some optional classes that need to be added depending on the desired variants.

const button = variants({
  base: "rounded text-white",
  variants: {
    color: {
      brand: "bg-sky-500",
      accent: "bg-teal-500",
    },
    size: {
      small: "px-5 py-3 text-xs",
      large: "px-6 py-4 text-base",
    },
  },
});

The result is a function that expects an object which specifies what variants should be selected. When called, it returns a string containing the respective class names:

document.write(`
  <button class="${button({
    color: "accent",
    size: "large",
  })}">
    Click Me!
  </button>
`);

Advanced Usage

Boolean variants

Variants can be of type boolean by using "true" as the key:

const button = variants({
  base: "text-white",
  variants: {
    rounded: {
      true: "rounded-full",
    },
  },
});

Compound variants

The compoundVariants option can be used to apply class names based on a combination of other variants.

const button = variants({
  variants: {
    color: {
      neutral: "bg-gray-200",
      accent: "bg-teal-400",
    },
    outlined: {
      true: "border-2",
    },
  },
  compoundVariants: [
    {
      variants: {
        color: "accent",
        outlined: true,
      },
      className: "border-teal-500",
    },
  ],
});

Default variants

The defaultVariants option can be used to select a variant by default:

const button = variants({
  variants: {
    color: {
      neutral: "bg-gray-200",
      accent: "bg-teal-400",
    },
  },
  defaultVariants: {
    color: "neutral",
  },
});

React

The library contains utility functions that are useful for writing React components.

It works much like variants() but instead of a class name string, the resulting function returns an object with props.

import { variantProps } from "classname-variants/react";

const buttonProps = variantProps({
  base: "rounded-md text-white",
  variants: {
    color: {
      brand: "bg-sky-500",
      accent: "bg-teal-500",
    },
    size: {
      small: "px-5 py-3 text-xs",
      large: "px-6 py-4 text-base",
    },
    rounded: {
      true: "rounded-full",
    },
  },
  defaultVariants: {
    color: "brand",
  },
});

This way a component's props (or part of them) can be directly spread into the target element. All variant-related props are used to construct the className property while all other props are passed through verbatim:

type Props = JSX.IntrinsicElements["button"] &
  VariantPropsOf<typeof buttonProps>;

function Button(props: Props) {
  return <button {...buttonProps(props)} />;
}

function App() {
  return (
    <Button size="small" color="accent" onClick={console.log}>
      Click Me!
    </Button>
  );
}

Bonus: styled-components, but for static CSS 💅

Things can be taken even a step further, resulting in a styled-components like way of defining reusable components. Under the hood, this does basically the same as the example above, but also handles refs correctly:

import { styled, tw } from "classname-variants/react";

const Button = styled("button", {
  variants: {
    size: {
      small: tw`text-xs`,
      large: tw`text-base`,
    },
  },
});

Again, this is not limited to tailwind, so you could do the same with CSS modules:

import { styled } from "classname-variants/react";
import styles from "./styles.module.css";

const Button = styled("button", {
  variants: {
    size: {
      small: styles.small,
      large: styles.large,
    },
  },
});

Note You can also style other custom React components as long as they accept a className prop.

Styled components without variants

You can also use the styled function to create styled components without any variants at all:

import { styled } from "classname-variants/react";

const Button = styled(
  "button",
  "border-none rounded px-3 font-sans bg-green-600 text-white hover:bg-green-500"
);

Polymorphic components with "as"

If you want to keep all the variants you have defined for a component but want to render a different HTML tag or a different custom component, you can use the "as" prop to do so:

import { styled } from "classname-variants/react";

const Button = styled("button", {
  variants: {
    //...
  },
});

function App() {
  return (
    <div>
      <Button>I'm a button</Button>
      <Button as="a" href="/">
        I'm a link!
      </Button>
    </div>
  );
}

Tailwind IntelliSense

In order to get auto-completion for the CSS classes themselves, you can use the Tailwind CSS IntelliSense plugin for VS Code. In order to make it recognize the strings inside your variants-config, you have to somehow mark them and configure the plugin accordingly.

One way of doing so is by using tagged template literals:

import { variants, tw } from "classname-variants";

const button = variants({
  base: tw`px-5 py-2 text-white`,
  variants: {
    color: {
      neutral: tw`bg-slate-500 hover:bg-slate-400`,
      accent: tw`bg-teal-500 hover:bg-teal-400`,
    },
  },
});

You can then add the following line to your settings.json:

"tailwindCSS.experimental.classRegex": ["tw`(.+?)`"]

Note The tw helper function is just an alias for String.raw.

In order to get type coverage even for your Tailwind classes you can use a tool like tailwind-ts.

License

MIT

More Repositories

1

spin.js

A spinning activity indicator
CSS
9,297
star
2

node-dev

Zero-conf Node.js reloading
JavaScript
2,250
star
3

domino

Server-side DOM implementation based on Mozilla's dom.js
JavaScript
769
star
4

inbox-app

Google Inbox packaged as Electron app
JavaScript
271
star
5

typed-rpc

Lightweight JSON-RPC solution for TypeScript projects
TypeScript
128
star
6

express-jsdom

Server-side DOM for express.js
JavaScript
56
star
7

filewatcher

Wrapper around fs.watch that falls back to fs.watchFile
JavaScript
54
star
8

linger

Busy-indicator for the terminal
JavaScript
51
star
9

google-calendar-app

Google Calendar packaged as electron app
JavaScript
51
star
10

instant-server

Instant HTTP server with live-reload
JavaScript
46
star
11

instant

transparent live-reloading
JavaScript
45
star
12

gateway

Node.js middleware to execute CGI scripts
JavaScript
37
star
13

jshint.tmbundle

JSHint TextMate Bundle
JavaScript
36
star
14

form2json

Alternative decoder for form-urlencoded data
JavaScript
31
star
15

retrace

Use source-maps on the server to make browser stack traces more readable
JavaScript
31
star
16

sendevent

Connect middleware for server-sent-events with iframe fallback
JavaScript
26
star
17

tamper

Node.js middleware to capture and modify response bodies
JavaScript
22
star
18

type-assurance

Lightweight type guards and assertions
TypeScript
22
star
19

zepto-node

JavaScript
21
star
20

mkay

Lightweight Jade/HAML-like DOM builder for jQuery and Zepto.js
JavaScript
21
star
21

react-api-query

Hooks to use react-query with a typed API client
TypeScript
20
star
22

glitter

WebGL experiment that creates a glitter effect based on the device orientation.
JavaScript
15
star
23

uniqs

Tiny utility to de-duplicate lists
JavaScript
11
star
24

diffparser

Unified diff parser for Node and the browser
JavaScript
11
star
25

orca.wtf

JavaScript
11
star
26

npm-as-nom

Wrapper around npm that screams COOKIE!! every time you type nom instead
JavaScript
10
star
27

gmail-app

Gmail packaged as Electron app
CSS
10
star
28

googlemaps-react-primitives

Google Maps primitives for React
TypeScript
8
star
29

stacked

lightweight middleware infrastructure
JavaScript
6
star
30

configurify

A browserify transform to expose server-side configuration options
JavaScript
6
star
31

rework-palette

Rework plugin to resolve custom CSS color palettes
JavaScript
5
star
32

php-proxy-middleware

Node middleware to forward requests to a built-in PHP server
JavaScript
5
star
33

rework-clearfix

JavaScript
5
star
34

deep-match

Check if two values deeply match
JavaScript
4
star
35

mdi-json

Material Design Icons as JSON
JavaScript
4
star
36

rework-parent

rework plugin to add parent selector support
JavaScript
4
star
37

dynaform

A jQuery plugin to dynamically create forms
JavaScript
4
star
38

dombench

Benchmark tool for Node.js DOM implementations
JavaScript
4
star
39

with-server

Command line utility to start/stop a local server in order to execute end-to-end tests
JavaScript
3
star
40

simple-image-resize

TypeScript
3
star
41

materialize

Tiny utility to turn a list into an object
JavaScript
3
star
42

fgnass.github.com

My personal website
HTML
3
star
43

ssi-middleware

Express middleware to render Server Side Includes
JavaScript
2
star
44

friendly-webdriver

Thin wrapper around the official Selenium JavaScript bindings
JavaScript
2
star
45

dommy

A Document dummy that mocks just enough of the DOM API to render HTML.
JavaScript
2
star
46

unexpected-webdriver

selenium-webdriver plugin for the unexpected assertion libary
JavaScript
2
star
47

d1-level

An abstract-level database backed by Cloudflare D1
TypeScript
1
star
48

statdir

collect stats for all files in a directory
JavaScript
1
star
49

dirwatcher

recursively watch directories for modifications
JavaScript
1
star
50

redux-route

Routing for Redux
JavaScript
1
star
51

browser-resolve-sync

Node.js resolve algorithm with browser field support
JavaScript
1
star
52

fwatch

JavaScript
1
star