Usage with CSS Modules

Usage with CSS Modules

CSS Modules is a way to write CSS that is scoped locally by default. It uses the same pattern as React components, where CSS is written as JS, but outputs plain CSS files. If you don't want to use Tailwind, this guide is perfect for you.

Creating a simple Button

tw-classed works excellently with CSS Modules. You can use the classes function to generate class names, and toggle them using props.

For this guide some Typescript knowledge is assumed.

Lets create some CSS Modules for our Button.

// Button.module.scss
.button {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem 1rem;
  border-radius: 0.25rem;
  border: 1px solid transparent;
  font-size: 1rem;
  font-weight: 500;
  &.primary {
    background-color: #3182ce;
    color: #fff;
    border-color: #2b6cb0;
  &.secondary {
    background-color: #cbd5e0;
    color: #2d3748;
    border-color: #a0aec0;
  &.danger {
    background-color: #e53e3e;
    color: #fff;
    border-color: #c53030;

Then apply the styles to our Button component.

import classes from "./Button.module.scss";
import { classed } from "@tw-classed/react";
const Button = classed("button", classes.button, {
  variants: {
    color: {
      primary: classes.primary,
      secondary: classes.secondary,
      danger: classes.danger,
  defaultVariants: {
    color: "primary",
export type ButtonProps = React.ComponentProps<typeof Button>;

Now we can use the Button component like this:

<Button color="primary">Primary</Button>
<Button color="secondary">Secondary</Button>
<Button color="danger">Danger</Button>

Creating a simple button without tw-classed

If you don't want to use tw-classed, you can still use CSS Modules. You can use the classnames package to toggle classes.

import classes from "./Button.module.scss";
import cn from "classnames";
export interface ButtonProps {
  color?: "primary" | "secondary" | "danger";
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
  ({ color, ...props }, ref) => {
    return (
        className={cn(classes.button, {
          [classes.primary]: color === "primary",
          [classes.secondary]: color === "secondary",
          [classes.danger]: color === "danger",

You can see how this is a lot more verbose than using tw-classed. You have to manually toggle the classes, and you have to manually add the base class. If you add another variant, you have to add another class name to the cn function and also manually update the ButtonProps interface.

Additionally, there is no as prop. This button will always be a button element. There is no way to simply grab the styles of the button and apply them to a different element like an a element. This is where tw-classed shines.

Remapping the button to an a element

tw-classed allows you to remap the button element to an a element. This is useful if you want to use the styles of a button, but you want to use an a element instead.

// Link.tsx
// ...
import { Button } from "./Button";
export const Link = classed("a", Button);

Thats it! Now you can use the Link component like this:

<Link href="/home" color="primary">

Or you can map it in using the as prop

import { Button } from "./Button";
() => (
  <Button as="a" href="/home" color="primary">