Gathering detailed insights and metrics for react-classmate
Gathering detailed insights and metrics for react-classmate
Gathering detailed insights and metrics for react-classmate
Gathering detailed insights and metrics for react-classmate
A react tool to separate class name logic, create variants and manage styles.
npm install react-classmate
Typescript
Module System
Node Version
NPM Version
TypeScript (95.49%)
JavaScript (4.51%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
1 Stars
318 Commits
4 Branches
1 Contributors
Updated on Jun 28, 2025
Latest Version
1.1.8
Package Id
react-classmate@1.1.8
Unpacked Size
41.35 kB
Size
11.63 kB
File Count
5
NPM Version
11.4.1
Node Version
22.12.0
Published on
Jun 28, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
1
1
26
1
A tool for managing React component class names, variants and styles with the simplicity of styled-components and cva. Designed and tested for use with utility-first CSS libraries and SSR/SSG frameworks.
1const SomeButton = ({ isLoading, ...props }) => { 2 const activeClass = isLoading 3 ? "bg-blue-400 text-white" 4 : "bg-blue-800 text-blue-200"; 5 6 return ( 7 <button 8 {...props} 9 className={`transition-all mt-5 border-1 md:text-lg text-normal ${someConfig.transitionDurationEaseClass} ${activeClass} ${props.className || ""}`} 10 > 11 {props.children} 12 </button> 13 ); 14};
1const ButtonBase = rc.button` 2 text-normal 3 md:text-lg 4 mt-5 5 border-1 6 transition-all 7 ${someConfig.transitionDurationEaseClass} 8 ${(p) => (p.$isLoading ? "opacity-90 pointer-events-none" : "")} 9`;
rc.extend
Make sure you have installed React (> 16.8.0) in your project.
1npm i react-classmate 2# or 3yarn add react-classmate
Create a component by calling rc
with a tag name and a template literal string.
1import rc from "react-classmate"; 2 3const Container = rc.div` 4 py-2 5 px-5 6 min-h-24 7`; 8// transforms to: <div className="py-2 px-5 min-h-24" />
Additional Information: See "Base usage" documentation
Pass props to the component and use them in the template literal string and in the component prop validation.
1// hey typescript 2interface ButtonProps { 3 $isActive?: boolean; 4 $isLoading?: boolean; 5} 6const SomeButton = rc.button<ButtonProps>` 7 text-lg 8 mt-5 9 ${(p) => (p.$isActive ? "bg-blue-400 text-white" : "bg-blue-400 text-blue-200")} 10 ${(p) => (p.$isLoading ? "opacity-90 pointer-events-none" : "")} 11`; 12// transforms to <button className="text-lg mt-5 bg-blue-400 text-white opacity-90 pointer-events-none" />
$
we prefix the props incoming to dc with a $
sign. This is a important convention to distinguish dynamic props from the ones we pass to the component.
This pattern should also avoid conflicts with reserved prop names.
Create variants by passing an object to the variants
key like in cva.
The key should match the prop name and the value should be a function that returns a string. You could also re-use the props in the function.
1interface AlertProps { 2 $severity: "info" | "warning" | "error"; 3 $isActive?: boolean; 4} 5const Alert = rc.div.variants<AlertProps>({ 6 // optional 7 base: (p) => ` 8 ${p.isActive ? "custom-active" : "custom-inactive"} 9 p-4 10 rounded-md 11 `, 12 // required 13 variants: { 14 $severity: { 15 warning: "bg-yellow-100 text-yellow-800", 16 info: (p) => 17 `bg-blue-100 text-blue-800 ${p.$isActive ? "shadow-lg" : ""}`, 18 error: (p) => 19 `bg-red-100 text-red-800 ${p.$isActive ? "ring ring-red-500" : ""}`, 20 }, 21 }, 22 // optional - used if no variant was found 23 defaultVariant: { 24 $severity: "info", 25 }, 26}); 27 28export default () => <Alert $severity="info" $isActive />; 29// outputs: <div className="custom-active p-4 rounded-md bg-blue-100 text-blue-800 shadow-lg" />
Additional Information: See "Variants" documentation
As seen above, we also pass AlertProps
to the variants, which can cause loose types. If you want to separate the base props from the variants, you can pass a second type to the variants
function so that only those props are available in the variants.
1interface AlertProps { 2 $isActive?: boolean; 3} 4interface AlertVariants { 5 $severity: "info" | "warning" | "error"; 6} 7const Alert = rc.div.variants<AlertProps, AlertVariants>({ 8 base: `p-4 rounded-md`, 9 variants: { 10 // in here there are only the keys from AlertVariants available 11 $severity: { 12 // you can use the props from AlertProps here again 13 warning: "bg-yellow-100 text-yellow-800", 14 info: (p) => 15 `bg-blue-100 text-blue-800 ${p.$isActive ? "shadow-lg" : ""}`, 16 error: (p) => 17 `bg-red-100 text-red-800 ${p.$isActive ? "ring ring-red-500" : ""}`, 18 }, 19 }, 20 // optional - used if no variant was found 21 defaultVariant: { 22 $severity: "info", 23 }, 24});
Extend a component directly by passing the component and the tag name.
1import MyOtherComponent from "./MyOtherComponent"; // () => <button className="text-lg mt-5" /> 2import rc from "react-classmate"; 3 4const Container = rc.extend(MyOtherComponent)` 5 py-2 6 px-5 7 min-h-24 8`; 9// transforms to: <button className="text-lg mt-5 py-2 px-5 min-h-24" />
Additional Information: "Extend" documentation
You can use CSS styles in the template literal string with the style
function. This function takes an object with CSS properties and returns a string. We can use the props from before.
1// Base: 2const StyledButton = rc.button<{ $isDisabled: boolean }>` 3 text-blue 4 ${(p) => 5 p.style({ 6 boxShadow: "0 0 5px rgba(0, 0, 0, 0.1)", 7 cursor: p.$isDisabled ? "not-allowed" : "pointer", 8 })} 9`; 10export default () => <StyledButton $isDisabled />; 11// outputs: <button className="text-blue" style="box-shadow: 0 0 5px rgba(0, 0, 0, 0.1); cursor: not-allowed;" />
1// Extended: 2const BaseButton = rc.button<{ $isActive?: boolean }>` 3 ${(p) => 4 p.style({ 5 backgroundColor: p.$isActive ? "green" : "red", 6 })} 7`; 8const ExtendedButton = rc.extend(BaseButton)<{ $isLoading?: boolean }>` 9 ${(p) => 10 p.style({ 11 opacity: p.$isLoading ? 0.5 : 1, 12 pointerEvents: p.$isLoading ? "none" : "auto", 13 })} 14`; 15export default () => <ExtendedButton $isActive $isLoading />; 16// outputs: <button className="bg-red" style="opacity: 0.5; pointer-events: none;" />
rc.extend
With rc.extend
, you can build upon any base React component, adding new styles and even supporting additional props. This makes it easy to create reusable component variations without duplicating logic.
1import { ArrowBigDown } from "lucide-react"; 2import rc from "react-classmate"; 3 4const StyledLucideArrow = rc.extend(ArrowBigDown)` 5 md:-right-4.5 6 right-1 7 slide-in-r-20 8`; 9 10// ts: we can pass only props which are accessible on a `lucid-react` Component 11export default () => <StyledLucideArrow stroke="3" />;
⚠️ Having problems by extending third party components, see: Extending other lib components
Now we can define a base component, extend it with additional styles and classes, and pass properties. You can pass the types to the extend
function to get autocompletion and type checking.
1import rc from "react-classmate"; 2 3interface StyledSliderItemBaseProps { 4 $active: boolean; 5} 6const StyledSliderItemBase = rc.button<StyledSliderItemBaseProps>` 7 absolute 8 h-full 9 w-full 10 left-0 11 top-0 12 ${(p) => (p.$active ? "animate-in fade-in" : "animate-out fade-out")} 13`; 14 15interface NewStyledSliderItemProps extends StyledSliderItemBaseProps { 16 $secondBool: boolean; 17} 18const NewStyledSliderItemWithNewProps = rc.extend( 19 StyledSliderItemBase, 20)<NewStyledSliderItemProps>` 21 rounded-lg 22 text-lg 23 ${(p) => (p.$active ? "bg-blue" : "bg-red")} 24 ${(p) => (p.$secondBool ? "text-underline" : "some-class-here")} 25`; 26 27export default () => ( 28 <NewStyledSliderItemWithNewProps $active $secondBool={false} /> 29); 30// outputs: <button className="absolute h-full w-full left-0 top-0 animate-in fade-in rounded-lg text-lg bg-blue" />
1const BaseButton = rc.extend(rc.button``)` 2 text-lg 3 mt-5 4`;
1interface ButtonProps extends InputHTMLAttributes<HTMLInputElement> { 2 $severity: "info" | "warning" | "error"; 3 $isActive?: boolean; 4} 5 6const Alert = rc.input.variants<ButtonProps>({ 7 base: "p-4", 8 variants: { 9 $severity: { 10 info: (p) => 11 `bg-blue-100 text-blue-800 ${p.$isActive ? "shadow-lg" : ""}`, 12 }, 13 }, 14}); 15 16const ExtendedButton = rc.extend(Alert)<{ $test: boolean }>` 17 ${(p) => (p.$test ? "bg-green-100 text-green-800" : "")} 18`; 19 20export default () => <ExtendedButton $severity="info" $test />; 21// outputs: <input className="p-4 bg-blue-100 text-blue-800 shadow-lg bg-green-100 text-green-800" />
By passing the component, we can validate the component to accept tag related props.
This is useful if you wanna rely on the props for a specific element without the $
prefix.
1// if you pass rc component it's types are validated 2const ExtendedButton = rc.extend(rc.button``)` 3 some-class 4 ${(p) => (p.type === "submit" ? "font-normal" : "font-bold")} 5`; 6 7// infers the type of the input element + add new props 8const MyInput = ({ ...props }: HTMLAttributes<HTMLInputElement>) => ( 9 <input {...props} /> 10); 11const StyledDiv = rc.extend(MyInput)<{ $trigger?: boolean }>` 12 bg-white 13 ${(p) => (p.$trigger ? "!border-error" : "")} 14 ${(p) => (p.type === "submit" ? "font-normal" : "font-bold")} 15`;
any
as InputUnfortunately we cannot infer the type directly of the component if it's any
or loosely typed. But we can use a intermediate step to pass the type to the extend
function.
1import { ComponentProps } from 'react' 2import { MapContainer } from 'react-leaflet' 3import { Field, FieldConfig } from 'formik' 4import rc, { RcBaseComponent } from 'react-classmate' 5 6// we need to cast the type to ComponentProps 7type StyledMapContainerType = ComponentProps<typeof MapContainer> 8const StyledMapContainer: RcBaseComponent<StyledMapContainerType> = rc.extend(MapContainer)` 9 absolute 10 h-full 11 w-full 12 text-white 13 outline-0 14` 15 16export const Component = () => <StyledMapContainer bounds={...} /> 17 18// or with Formik 19 20import { Field, FieldConfig } from 'formik' 21 22type FieldComponentProps = ComponentProps<'input'> & FieldConfig 23const FieldComponent = ({ ...props }: FieldComponentProps) => <Field {...props} /> 24 25const StyledField = rc.extend(FieldComponent)<{ $error: boolean }>` 26 theme-form-field 27 w-full 28 .... 29 ${p => (p.$error ? '!border-error' : '')} 30` 31 32export const Component = () => <StyledField placeholder="placeholder" as="select" name="name" $error />
⚠️ This is a workaround! This is a bug - we should be able to pass the types directly in the interface in which we pass $error
. Contributions welcome.
If you are using CommonJS, you can import the library like this:
1const rc = require("react-classmate").default; 2 3// or 4 5const { default: rc } = require("react-classmate");
React-classmate uses tailwind-merge under the hood to merge class names. The last class name will always win, so you can use it to override classes.
rc.raw()
and rc.raw.variants()
for only using rc
syntax for classnames (output as string)rc.extend
.default
)
-- Means we need to remove the named export in the ts file to not duplicate IDE import suggestions:
--- Change postbuild script to remove named esm exportNo vulnerabilities found.
No security vulnerabilities found.