CSS Modules

We use CSS Modules for our styles as the best way to include component-level CSS.

Basic Usage

CSS Modules can be used to import styles into a component. The imported object contains the class names as properties that can be used in the component.

Foo.module.css
.foo {
	color: red;
}
Foo.tsx
import classes from "./Foo.module.css"

export function Foo() {
	return <div className={classes.foo}>Hello</div>
}

Dynamic Classes

We use clsx to combine multiple classes together based on state.

Foo.module.css
.foo {
	color: red;
}
.special {
	color: green;
}
Foo.tsx
import classes from "./Foo.module.css"
import clsx from "clsx"

export function Foo() {
	const [flag, setFlag] = useState(false)
	return <div className={clsx(classes.foo, flag && classes.special)}>Hello</div>
}

Injecting styles

To "pass state" from JSX to CSS, we can use CSS variables.

Foo.module.css
.foo {
	color: var(--foo-color, red);
}
Foo.tsx
import classes from "./Foo.module.css"

export function Foo() {
	const [color, setColor] = useState("red")
	return (
		<div className={classes.foo} style={{ "--foo-color": color }}>
			Hello
		</div>
	)
}

Type-Safe Styles

A common issue with CSS Modules is that the class names are not type-safe. We use a tcm watcher to continuously generate hidden TypeScript files that insure the class names are correct. It is not possible to use a class name that does not exist and still have pnpm tsc pass.

This also provides autocompletion for class names in VSCode.

Foo.module.css
.foo {
	color: var(--foo-color, red);
}
Foo.tsx
import classes from "./Foo.module.css"
//     ^? const classes: { foo: string }