Introduction
ClazzX is a small typesafe utility library for composing HTML classes. Unlike Vanilla Extract, Stitches, or CVA (all fantastic options) ClazzX takes a different approach, using state rather than variants to compose styles.
The classic approach is to create a series of variants that can be selected:
className={myButton({size: "small", intent="primary"})}
// AND
<Button size="small" intent="primary" />
In comparison, ClazzX models combinations of styles based on state.
className={MyButton.c({small: true, primary: true})}
// AND
<Button small primary />
This simplifies the logic of tying the styling of a component to complex state. Instead of a series of nested ternary operators, simple logical operators can be used to clearly define the appropriate styles.
className={MyButton.c({
primary: true,
loading: isLoading,
error: isError
})}
//AND
<Button primary loading={isLoading} error={isError}>
// Versus the traditional variant approach
className={myButton({
intent: isLoading ? "loading"
: isError ? "error"
: "primary"
})}
// AND
<Button intent={isLoading ? "loading" : isError ? "error" : "primary"}>
Installation
// Deno
import { ClazzX, clx } from "https://deno.land/x/clazzx/mod.ts"
// NPM
npm i clazzx
// Yarn
yarn add clazzx
Getting Started
A basic component
import { ClazzX } from 'clazzx'
class Input extends ClazzX {
// the base class will always be applied
base = "border rounded-md font-sans"
small = "px-2 py-1 text-sm"
md = "px-3 py-2 text-base"
lg = "px-4 py-3 text-lg"
// you can use both strings and string arrays freely
primary = [
"bg-primary",
"border-primary-dark",
"text-primary-content"
]
secondary = [
"bg-secondary",
"border-secondary-dark",
"text-secondary-content"
]
}
function MyCustomComponent(){
return <input className={Input.c({ md: true, secondary: true })}/>
}
Default Classes
You can set default classes that are applied if nothing is passed to the classes method.
default = [this.md, this.secondary, "hover:scale-105"]
Compounds
If you want to conditionally apply styles when two or more states are true, you can create compound states.
compounds = [{
state: ["primary", "large"],
classes: "shadow-md"
}]
Ordering
Classes are applied in the order of the states.
className={btn.classes({rounded: true, square: true})}
// className="border-md border-none"
<Button primary secondary small medium>
// className="bg-primary bg-secondary text-sm text-md"
The order of classes works like this:
base
- …order based on state
||
default classes - Compounds
StyleProps
StyleProps is an exported type that gives you access to the parameters of the style class. You can use this to strongly type components.
function MyButton({...props}: StyleProps<MyStyleClass>){}
Clx
clx
is a variadic utility function that composes a string based on it’s truthy inputs.
const a = clx(["bg-green-300", "text-md"])
// "bg-green-300 text-md"
const b = clx("bg-red-300", false && "text-red-700")
// "bg-red-300"
const c = clx(["p-4", false && "mt4"], "text-black", true && "font-bold")
// "p-4 text-black font-bold"
It can be imported or used as a static method on ClazzX
.
Accessing fields
Each ClazzX instance exposes a static get()
method to access the fields and methods of the class.
Examples
React Link
import { Style, StyleProps } from "clazzx"
class LinkStyles extends ClazzX {
base: "font-link no-underline transition duration-200"
primary: "text-link hover:text-link-hover"
secondary: "text-secondary hover:text-secondary-hover"
active: "text-active hover:text-active-hover underline"
default = this.primary
}
interface Link {
children: ReactNode
props: StyleProps<LinkStyles> & HTMLAttributes<HTMLAnchorElement>
}
function Link({children, ...props}: Link){
return <a {...props} className={LinkStyles.c({...props})}>{children}</a>
}
function App(){
const router = useRouter()
// ...
return (
// ...
<Link href="/about" secondary active={router.isCurrentPage}>
My Link
</Link>
// ...
)
}
With Globals
class GlobalStyles extends ClazzX {
padding = {
sm: "p-2",
md: "p-4",
lg: "p-6",
dynamic: (i: number) => {
`p-[${i*2}px]`
}
}
}
class Input extends GlobalStyles {
sm = clx(this.padding.sm, "rounded-sm")
md = clx(this.padding.md, "rounded-md")
lg = clx(this.padding.lg, "rounded-lg")
warning = "outline-red"
compounds = [{
state: ["sm", "warning"],
classes: this.padding.dynamic(8)
}]
}