{"id":22956762,"url":"https://github.com/richard-unterberg/react-styled-classnames","last_synced_at":"2025-08-13T03:32:42.747Z","repository":{"id":258281765,"uuid":"874615792","full_name":"richard-unterberg/react-styled-classnames","owner":"richard-unterberg","description":"like styled-components for classnames","archived":false,"fork":false,"pushed_at":"2024-12-13T09:54:06.000Z","size":72,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-12-13T09:55:11.663Z","etag":null,"topics":["classnames","clsx","dynamic","react","styled-components","tailwindcss","typescript","unocss","utility-css","utility-first"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/react-styled-classnames","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/richard-unterberg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-10-18T06:41:47.000Z","updated_at":"2024-12-13T09:54:10.000Z","dependencies_parsed_at":"2024-12-13T09:55:14.586Z","dependency_job_id":null,"html_url":"https://github.com/richard-unterberg/react-styled-classnames","commit_stats":null,"previous_names":["richard-unterberg/react-dynamic-style","richard-unterberg/react-dynamic-classname","richard-unterberg/react-styled-classnames"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richard-unterberg%2Freact-styled-classnames","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richard-unterberg%2Freact-styled-classnames/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richard-unterberg%2Freact-styled-classnames/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/richard-unterberg%2Freact-styled-classnames/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/richard-unterberg","download_url":"https://codeload.github.com/richard-unterberg/react-styled-classnames/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":229524517,"owners_count":18086546,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["classnames","clsx","dynamic","react","styled-components","tailwindcss","typescript","unocss","utility-css","utility-first"],"created_at":"2024-12-14T17:11:31.256Z","updated_at":"2024-12-14T17:11:31.726Z","avatar_url":"https://github.com/richard-unterberg.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-styled-classnames\n\nA utility-first CSS tool for managing component class names with the simplicity of styled-components, designed for use with utility-first CSS libraries like UnoCSS and Tailwind:\n\n```ts\nconst SomeButton = rsc.button\u003cButtonProps\u003e`\n  text-lg\n  mt-5\n  ${p =\u003e p.$isActive ? 'bg-blue-300 text-white' : 'bg-blue-400 text-blue-200'}\n  ${p =\u003e p.$isLoading ? 'opacity-90 pointer-events-none' : ''}\n`\n```\n\n## Contents\n\n- [The \"issue\"](#the-issue)\n- [Features](#features)\n- [Getting started](#getting-started)\n- [Basic usage](#basic-usage)\n- [Usage with props](#usage-with-props)\n- [Extend components with `rsc.extend`](#extend-components-with-rscextend)\n  - [Use rsc for creating base component](#use-rsc-for-creating-base-component)\n  - [Using element tag props and validation](#using-element-tag-props-and-validation)\n- [Version 1 Users](#version-1-users)\n\n## The \"issue\"\n\nWhen working with utility-first libraries like [uno.css](https://unocss.dev/) or [tailwind](https://tailwindcss.com/), it's common to define utility classes directly in your React components. Which often leads to this kind of boilerplate code:\n\n```tsx\ninterface SomeButtonProps extends React.ButtonHTMLAttributes\u003cHTMLButtonElement\u003e {\n  isLoading: boolean\n  isActive: boolean\n  className?: string\n}\n\nconst SomeButton = ({ isLoading, isActive, children, className, ...props } : SomeButtonProps) =\u003e {\n  const activeClass = useMemo(\n    () =\u003e (isActive ? 'bg-blue-400 text-white' : 'bg-blue-400 text-blue-200'),\n    [isActive],\n  )\n  const loadingClass = useMemo(() =\u003e (isLoading ? 'opacity-90 pointer-events-none' : ''), [isLoading])\n\n  return (\n    \u003cbutton\n      className={`text-lg mt-5 py-2 px-5 min-h-24 inline-flex transition-all z-10 ${someConfig.transitionDurationEaseClass} ${activeClass} ${loadingClass} ${className || ''}`}\n      {...props}\n    \u003e\n      {children}\n    \u003c/button\u003e\n  )\n}\n```\n\n### The tool let you write this instead:\n\n```tsx\ninterface SomeButtonProps {\n  $isActive?: boolean\n  $isLoading?: boolean\n}\n\nconst SomeButton = rsc.button\u003cSomeButtonProps\u003e`\n  text-lg\n  mt-5\n  py-2\n  px-5\n  min-h-24\n  inline-flex\n  z-10\n  transition-all\n  ${someConfig.transitionDurationEaseClass}\n  ${p =\u003e p.$isActive ? 'bg-blue-400 text-white' : 'bg-blue-400 text-blue-200'}\n  ${p =\u003e p.$isLoading ? 'opacity-90 pointer-events-none' : 'my-custom-class'}\n`\n```\n\n## Features\n\n- Dynamic class names: Define dynamic styles based on props, feels like styled-components.\n- React, no other dependencies: Works with any React component, no need for styled-components or tailwind\n- Extend everything: Easily extend any React component with rsc.extend.\n- Utility-first CSS support: Works seamlessly with libraries like UnoCSS and Tailwind.\n- TypeScript: Autocompletion and strict type-checking in your IDE.\n- SSR compatibility: Compatible with SSR frameworks like [Vike](https://vike.dev/) and [Next.js](https://nextjs.org/).\n\n### re-inventing the wheel?\n\nWhile [twin.macro](https://github.com/ben-rogerson/twin.macro) requires styled-components, and [tailwind-styled-components](https://github.com/MathiasGilson/tailwind-styled-component) isn’t fully compatible with [Vike](https://vike.dev/) and requires tailwind, `react-styled-classnames` is lightweight and tailored for flexibility and SSR.\n\n## Getting started\n\nLet's assume you have installed React (\u003e v17) and a utility-first library (uno.css / tailwind / shed / basscss) beforehand.\n\n```bash\nnpm i react-styled-classnames --save-dev\n# or\nyarn add react-styled-classnames --dev\n```\n\n## Basic usage\n\n```tsx\nimport { rsc } from 'react-styled-classnames'\n\n// IDE autocompletion and type-checking for utility class names\nconst Container = rsc.div`\n  text-lg\n  mt-5\n  py-2\n  px-5\n  min-h-24\n  inline-flex\n  z-10\n`\n```\n\n## Usage with props\n\n```tsx\ninterface ButtonProps {\n  $isActive?: boolean\n  $isLoading?: boolean\n}\n\nconst SomeButton = rsc.button\u003cButtonProps\u003e`\n  text-lg\n  mt-5\n  ${p =\u003e p.$isActive ? 'bg-blue-400 text-white' : 'bg-blue-400 text-blue-200'}\n  ${p =\u003e p.$isLoading ? 'opacity-90 pointer-events-none' : ''}\n`\n```\n\n### Prefix incoming props with `$`\n\n**Note how 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.\n\n*This pattern should also avoid conflicts with reserved prop names.*\n\n## Extend components with `rsc.extend`\n\nWith `rsc.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.\n\n```tsx\nimport { ArrowBigDown } from 'lucide-react'\nimport { rsc } from 'react-styled-classnames'\n\nconst StyledLucideArrow = rsc.extend(ArrowBigDown)`\n  md:-right-4.5\n  right-1\n  slide-in-r-20\n`\n\n// note how we can pass props which are only accessible on a Lucid Component\nexport default () =\u003e \u003cStyledLucideArrow stroke=\"3\" /\u003e\n```\n\nNow we can define a base component and 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 on the way.\n\n```tsx\nimport { rsc } from 'react-styled-classnames'\n\ninterface StyledSliderItemBaseProps {\n  $active: boolean\n}\n\nconst StyledSliderItemBase = rsc.button\u003cStyledSliderItemBaseProps\u003e`\n    absolute\n    h-full\n    w-full\n    left-0\n    top-0\n    ${p =\u003e (p.$active ? 'animate-in fade-in' : 'animate-out fade-out')}\n`\n\ninterface NewStyledSliderItemProps extends StyledSliderItemBaseProps {\n  $secondBool: boolean\n}\n\nconst NewStyledSliderItemWithNewProps = rsc.extend(StyledSliderItemBase)\u003cNewStyledSliderItemProps\u003e`\n    rounded-lg\n    text-lg\n    ${p =\u003e (p.$active ? 'bg-blue' : 'bg-red')}\n    ${p =\u003e (p.$secondBool ? 'text-underline' : 'some-class-here')}\n  `\n\nexport default () =\u003e \u003cNewStyledSliderItemWithNewProps $active $secondBool={false} /\u003e\n```\n\n## Example usage of `rsc.extend`\n\n### Use rsc for creating base component\n\nExtend a component directly by passing the component and the tag name.\n\n```tsx\nimport { rsc } from 'react-styled-classnames'\n\nconst BaseButton = rsc.extend(rsc.button``)`\n  text-lg\n  mt-5\n`\n```\n\n*Saw this the first time in Material UI's `styled` function, where you can pass the mui-component.*\n\n### Using element tag props and validation\n\nBy passing the component and the tag name, we can validate the component to accept tag related props.\nThis is useful if you wanna rely on the props for a specific element without the `$` prefix.\n\n```tsx\nimport { rsc } from 'react-dynamic-classnames'\n\n// mimic basic button type\ntype ButtonType = 'submit' | 'reset' | 'button' | undefined\n\n// extend to pass $isActive prop if needed\ninterface ExtendedButtonProps {\n  $isActive?: boolean\n}\n// note how we pass \"button\" as the second argument to correctly validate the props\nconst ExtendedButton = rsc.extend(rsc.button``, 'button')\u003cExtendedButtonProps\u003e`\n  some-class\n  ${p =\u003e {\n    if (p.type === 'submit') {\n      return 'font-bold'\n    }\n    if (p.type === 'reset') {\n      return 'font-italic'\n    }\n    return 'font-normal'\n  }}\n`\n\nexport default () =\u003e (\n  \u003cExtendedButton $isActive type=\"submit\"\u003e\n    Submit\n  \u003c/ExtendedButton\u003e\n)\n```\n\n## Version 1 Users\n\nIf you liked the V1 version with `dc` and `restyle` and the object based pattern, it's still available in this package until the next major release.\n\nSee: [V1 Documentation](\n  https://github.com/richard-unterberg/react-styled-classnames/tree/master/src/v1)\n\n### V1 Examples\n\n```tsx\n// append \"/v1\" to the import path\nimport { dc, restyle } from 'react-styled-classnames/v1'\n\n// V1 object pattern example\nconst Button = dc.button\u003cContainerProps\u003e({\n  // required: base class\n  base: `\n    text-lg\n    mt-5\n    py-2\n    px-5\n    min-h-24\n    inline-flex\n    z-10\n    transition-all\n    ${someConfig.transitionDurationEaseClass}\n  `,\n  // optional: dynamic classes\n  classes: ({ $isActive, $isLoading }) =\u003e [\n    $isActive ? 'bg-blue-400 text-white' : 'bg-blue-400 text-blue-200',\n    $isLoading ? 'opacity-90 pointer-events-none' : '',\n  ],\n  // optional: css object with or without props\n  css: ({ $isActive }) =\u003e ({\n    boxShadow: `0 0 0 1px rgba(255, 255, 255, ${$isActive ? 0.7 : 0.2})`,\n  }),\n})\n\n// V1 restyle example (now rsc.extend)\nexport const RestyledButton = restyle(\n  Button,\n  `\n  md:-right-4.5\n  right-1\n  slide-in-r-20\n`,\n)\n```\n\n## Inspiration\n- [tailwind-styled-components](https://github.com/MathiasGilson/tailwind-styled-component)\n- [twin.macro](https://github.com/ben-rogerson/twin.macro)\n- [cva](https://github.com/joe-bell/cva)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frichard-unterberg%2Freact-styled-classnames","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frichard-unterberg%2Freact-styled-classnames","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frichard-unterberg%2Freact-styled-classnames/lists"}