Get started / Basics
The following examples show how to create a base component and how to extend it with custom properties.
Select the component tag you wish by using it's intrinsic tag name. For example rc.div or rc.button. Via the interpolation you are able to pass classnames.
export const CustomButton = rc.button`
text-blue
px-3
py-2
`
<CustomButton>Hello World</CustomButton>
// renders:
// <button class="text-blue px-3 py-2">Hello World</button>
rc-specific properties with $ to not pass them to the created/extended component and to avoid conflicts with intrinsic properties (rc filters out props prefixed with $)interface CustomButtonProps {
$customState?: boolean
}
const CustomButton = rc.button<CustomButtonProps>`
text-blue
px-3
py-2
${(p) => (p.$customState ? "bg-black" : "")}
`<CustomButton $customState>Button</CustomButton>
// renders:
// <button class="text-blue px-3 py-2 bg-black">Button</button>
rc is passing intrinsic properties and you can use them in the interpolation string. For typescript we provide the type of IntrinsicElements to get them properly validated.
interface ButtonProps extends JSX.IntrinsicElements["button"] {
$customState?: "button"
}
const CustomButton = rc.button<ButtonProps>`
text-blue
custom
${(p) => (p.$customState" ? "bg-black" : "")}
${(p) => (p.type === "button" ? "opacity-60" : "")}
`<CustomButton type="button" $customState>Button</CustomButton>
// renders:
// <button type="button" class="text-blue custom opacity-60 bg-black"></button>
Need to define a component during render? Wrap the factory in useClassmate to memoize it between renders and avoid re-instantiating the element tree. Pass dependencies just like you would to React.useMemo if the factory relies on runtime values.
import rc, { useClassmate } from "react-classmate"
type WorkoutStatus = "completed" | "pending"
const WorkoutDay = ({ status }: { status: WorkoutStatus }) => {
const StyledDay = useClassmate(
() =>
rc.div.variants<{ $status: WorkoutStatus }>({
base: "rounded-md border p-4 text-sm",
variants: {
$status: {
completed: "border-green-400 bg-green-50",
pending: "border-yellow-400 bg-yellow-50",
},
},
}),
[status],
)
return <StyledDay $status={status}>Workout details</StyledDay>
}
Use .logic() before calling your template literal to run arbitrary JavaScript for every render. Whatever object you return is merged back into the props so you can derive variant values, data attributes, or additional $ props without wiring extra hooks.
import rc from "react-classmate"
type DayStatus = "completed" | "pending"
interface WorkoutProps {
workouts: unknown[]
allResolved: boolean
hasCompleted: boolean
hasSkipped: boolean
$status?: DayStatus
}
const deriveDayStatus = ({ workouts, allResolved, hasCompleted, hasSkipped }: WorkoutProps): DayStatus => {
if (workouts.length === 0) return "pending"
if (allResolved && hasCompleted && !hasSkipped) return "completed"
return "pending"
}
export const WorkoutDay = rc.div
.logic<WorkoutProps>((props) => {
const status = deriveDayStatus(props)
return {
$status: status,
["data-status"]: status,
}
})
.variants<WorkoutProps, { $status: DayStatus }>({
base: "rounded-md border p-4 text-sm transition-colors",
variants: {
$status: {
completed: "border-green-500 bg-green-50",
pending: "border-slate-300 bg-white",
},
},
})