Create Tailwind CSS React components like styled components with class names on multiple lines and conditional class rendering

  • By Mathias Gilson
  • Last update: Jan 2, 2023
  • Comments: 16

Tailwind-Styled-Component

Create tailwind css react components like styled components with classes name on multiple lines

NPM version

Before 😬

<div className=`flex ${primary ? "bg-indigo-600" : "bg-indigo-300"} inline-flex items-center border border-transparent text-xs font-medium rounded shadow-sm text-white hover:bg-indigo-700 focus:outline-none`>

After 🥳

<Button $primary={false}>

const Button = tw.div`
    ${(p) => (p.$primary ? "bg-indigo-600" : "bg-indigo-300")}

    flex
    inline-flex
    items-center
    border
    border-transparent
    text-xs
    font-medium
    rounded
    shadow-sm
    text-white

    hover:bg-indigo-700
    focus:outline-none
`

Features

♻️ Reusable

🧩 Extendable

💅 Compatible with Styled Components

⚡️ Use props like every React Component

🤯 Stop editing 400+ characters lines

🧘 Cleaner code in the render function

Install

Using npm

npm i -D tailwind-styled-components

Using yarn

yarn add -D tailwind-styled-components

⚠️ This extension requires TailwindCSS to be installed and configured on your project too. Install TailwindCSS

[Optional] Configure IntelliSense autocomplete on VSCode

First, install Tailwind CSS IntelliSense VSCode extension

https://marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss

Then add these user settings (How to edit VSCode settings?)

"tailwindCSS.includeLanguages": {
    "typescript": "javascript", // if you are using typescript
    "typescriptreact": "javascript"  // if you are using typescript with react
},
"editor.quickSuggestions": {
    "strings": true // forces VS Code to trigger completions when editing "string" content
},
"tailwindCSS.experimental.classRegex": [
    "tw`([^`]*)", // tw`...`
    "tw\\.[^`]+`([^`]*)`", // tw.xxx<xxx>`...`
    "tw\\(.*?\\).*?`([^`]*)" // tw(Component)<xxx>`...`
]

Usage

Import

import tw from "tailwind-styled-components"

Basic

Create a Tailwind Styled Component with Tailwind rules that you can render directly

const Container = tw.div`
    flex
    items-center
    justify-center
    flex-col
    w-full
    bg-indigo-600
`
render(
    <Container>
        <div>Use the Container as any other React Component</div>
    </Container>
)

Will be rendered as

<div class="flex items-center justify-center flex-col w-full bg-indigo-600">
    <div>Use the Container as any other React Component</div>
</div>

Conditional class names

Set tailwind class conditionally with the same syntax as styled components

interface ButtonProps {
   $primary: boolean
}

const Button = tw.button<ButtonProps>`
    flex
    ${(p) => (p.$primary ? "bg-indigo-600" : "bg-indigo-300")}
`

Tailwind Styled Components supports Transient Props

Prefix the props name with a dollar sign ($) to prevent forwarding them to the DOM element

<Button $primary={true} />

Will be rendered as

<button class="flex bg-indigo-600">
    <!-- children -->
</button>

and

<Button $primary={false} />

Will be rendered as

<button class="flex bg-indigo-300">
    <!-- children -->
</button>

Be sure to set the entire class name

 Do ${p => p.$primary ? "bg-indigo-600" : "bg-indigo-300"}

 Don't bg-indigo-${p => p.$primary ? "600" : "300"}


Extends

const DefaultContainer = tw.div`
    flex
    items-center
`
const RedContainer = tw(DefaultContainer)`
    bg-red-300
`

Will be rendered as

<div class="flex items-center bg-red-300">
    <!-- children -->
</div>

Careful it does not overrides parent classes

Extends Styled Component

Extend styled components

const StyledComponentWithCustomCss = styled.div`
    filter: blur(1px);
`

const  = tw(StyledComponentWithCustomCss)`
   flex
`

Css rule filter is not supported by default on TailwindCSS

Will be rendered as

<div class="flex" style="filter: blur(1px);">
    <!-- children -->
</div>

Example

import React from "react"
import tw from "tailwind-styled-components"
import styled from "styled-components"

// Create a <Title> react component that renders an <h1> which is
// indigo and sized at 1.125rem
interface TitleProps {
    $large: boolean;
}

const Title = tw.h1<TitleProps>`
  ${(p) => (p.$large ? "text-lg" : "text-base")}
  text-indigo-500
`

// Create a <SpecialBlueContainer> react component that renders a <section> with
// a special blue background color
const SpecialBlueContainer = styled.section`
    background-color: #0366d6;
`

// Create a <Container> react component that extends the SpecialBlueContainer to render
// a tailwind <section> with the special blue background and adds the flex classes
const Container = tw(SpecialBlueContainer)`
    flex
    items-center
    justify-center
    w-full
`

// Use them like any other React component – except they're styled!
render(
    <Container>
        <Title $large={true}>Hello World, this is my first tailwind styled component!</Title>
    </Container>
)

Github

https://github.com/MathiasGilson/Tailwind-Styled-Component

Comments(16)

  • 1

    Received `true` for a non-boolean attribute `block`

    It seems like props are passed as an attribute and when I pass boolean, It throws error on the console. I am using next.js with typescript.

    This is the error

    Warning: Received `true` for a non-boolean attribute `block`.
    
    If you want to write it to the DOM, pass a string instead: block="true" or block={value.toString()}.
    button
    functionTemplate/</<@webpack-internal:///./node_modules/tailwind-styled-components/dist/tailwind.js:34:101
    

    Component looks like this

    import tw from 'tailwind-styled-components';
    
    interface Props {
      layout?: 'primary' | 'outline';
      disabled?: boolean;
      block?: boolean;
      size?: 'small' | 'medium' | 'large';
    }
    
    const Button = tw.button`
      flex justify-center items-center border border-solid font-medium rounded-sm transition-all duration-300 ease-in-out
    
      ${(props: Props) =>
        props.layout === 'primary' &&
        'border-primary-400 bg-primary-400 text-white hover:bg-primary-500 hover:border-primary-600'}
      ${(props: Props) =>
        props.layout === 'outline' &&
        'border-gray-300 text-gray-700 hover:border-blue-500 hover:text-blue-500'}
    
      ${(props: Props) =>
        props.disabled && 'bg-gray-50 border-gray-200 text-gray-200'}
        
      ${(props: Props) => props.size === 'small' && 'px-2 py-2 text-xs'}
      ${(props: Props) => props.size === 'medium' && 'px-4 py-3 text-xs'}
      ${(props: Props) => props.size === 'large' && 'px-5 py-4 text-lg'}
      
      ${(props: Props) => props.block && 'w-full'}
    `;
    
    export default Button;
    

    The workaround is using number as boolean block ? 1 : 0

  • 2

    v2.1.4 FATAL ERROR: Reached heap limit Allocation failed

    Hello again. Thanks for the 2.1.2 version fix.

    Unfortunately, upgrading to 2.1.4 and running tsc presented an error on a whole different level:

    <--- Last few GCs --->
    
    [2053:0x5bd9060]    88488 ms: Scavenge 2027.0 (2073.3) -> 2023.9 (2074.5) MB, 19.1 / 0.0 ms  (average mu = 0.355, current mu = 0.179) allocation failure 
    [2053:0x5bd9060]    88515 ms: Scavenge 2028.2 (2074.5) -> 2025.9 (2077.8) MB, 11.2 / 0.0 ms  (average mu = 0.355, current mu = 0.179) allocation failure 
    [2053:0x5bd9060]    88542 ms: Scavenge 2031.4 (2077.8) -> 2028.8 (2096.0) MB, 10.2 / 0.0 ms  (average mu = 0.355, current mu = 0.179) allocation failure 
    
    
    <--- JS stacktrace --->
    
    FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
     1: 0xb00e10 node::Abort() [/usr/local/bin/node]
     2: 0xa1823b node::FatalError(char const*, char const*) [/usr/local/bin/node]
     3: 0xcee09e v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/usr/local/bin/node]
     4: 0xcee417 v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/usr/local/bin/node]
     5: 0xea65d5  [/usr/local/bin/node]
     6: 0xeb5cad v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/usr/local/bin/node]
     7: 0xeb89ae v8::internal::Heap::AllocateRawWithRetryOrFailSlowPath(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/usr/local/bin/node]
     8: 0xe79b12 v8::internal::Factory::AllocateRaw(int, v8::internal::AllocationType, v8::internal::AllocationAlignment) [/usr/local/bin/node]
     9: 0xe74[43](https://github.com/foroscom/foros-fe/runs/5145598143?check_suite_focus=true#step:5:43)c v8::internal::FactoryBase<v8::internal::Factory>::AllocateRawArray(int, v8::internal::AllocationType) [/usr/local/bin/node]
    10: 0xe7[45](https://github.com/foroscom/foros-fe/runs/5145598143?check_suite_focus=true#step:5:45)15 v8::internal::FactoryBase<v8::internal::Factory>::NewFixedArrayWithFiller(v8::internal::Handle<v8::internal::Map>, int, v8::internal::Handle<v8::internal::Oddball>, v8::internal::AllocationType) [/usr/local/bin/node]
    11: 0x10dd41e v8::internal::MaybeHandle<v8::internal::OrderedHashMap> v8::internal::OrderedHashTable<v8::internal::OrderedHashMap, 2>::Allocate<v8::internal::Isolate>(v8::internal::Isolate*, int, v8::internal::AllocationType) [/usr/local/bin/node]
    12: 0x10dd4d3 v8::internal::MaybeHandle<v8::internal::OrderedHashMap> v8::internal::OrderedHashTable<v8::internal::OrderedHashMap, 2>::Rehash<v8::internal::Isolate>(v8::internal::Isolate*, v8::internal::Handle<v8::internal::OrderedHashMap>, int) [/usr/local/bin/node]
    13: 0x11e9805 v8::internal::Runtime_MapGrow(int, unsigned long*, v8::internal::Isolate*) [/usr/local/bin/node]
    14: 0x15e7cf9  [/usr/local/bin/node]
    Aborted (core dumped)
    error Command failed with exit code 134.
    

    Not really sure where to begin debugging, since no other output is present. The JS itself works as expected, but tsc runs forever and then OOMs. Do you have any idea what might have caused it?

  • 3

    Input onChange and onFocus props are not working

    The props don't seems to be correctly propagated to the React Element.

    <Input onChange={console.log} onFocus={() => console.log(`focus`)} />
    
    const Input = tw.input``
    

    Check the example directory for a reproducible example

  • 4

    Failed to parse source map from '(...)/src/tailwind.tsx`

    Hi, first of all: Amazing Project! Thank you for building this!

    So, when I run npm start the console spits out: can't find src/ folder but still serves the react app correctly o.0

    WARNING in ./node_modules/tailwind-styled-components/dist/tailwind.js
    Module Warning (from ./node_modules/source-map-loader/dist/cjs.js):
    Failed to parse source map from '/Users/worker/development/hundertschaft-com/node_modules/tailwind-styled-components/src/tailwind.tsx' file: Error: ENOENT: no such file or directory, open '/Users/worker/development/hundertschaft-com/node_modules/tailwind-styled-components/src/tailwind.tsx'
     @ ./node_modules/tailwind-styled-components/dist/index.js 14:35-56
     @ ./src/App.tsx 7:0-44 9:19-24
     @ ./src/index.tsx 7:0-24 11:33-36
    
    3 warnings have detailed information that is not shown.
    Use 'stats.errorDetails: true' resp. '--stats-error-details' to show it.
    
    webpack 5.68.0 compiled with 3 warnings in 3036 ms
    No issues found.
    

    Not sure if simply publishing the src folder to npm would fix this.

    Reproduction

    • As Github Repo: https://github.com/D1no/reproduction-tailwind-styled-component
    • As Codesandbox: https://codesandbox.io/s/pensive-carson-y45qv?file=/src/App.tsx (resize the separator between browser and terminal in codesandbox to get back the full build log)

    Steps

    Thanks!

  • 5

    Typescript prop errors

    Running into some type issues but a basic button that has props passed through Button_tsx_—_template

    I'm using the default next.js typescript configuration

    {
      "compilerOptions": {
        "baseUrl": "src",
        "target": "es5",
        "lib": ["dom", "dom.iterable", "esnext"],
        "allowJs": true,
        "skipLibCheck": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "noEmit": true,
        "esModuleInterop": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "jsx": "preserve"
      },
      "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
      "exclude": ["node_modules"]
    }
    
  • 6

    Using props does not work

    Given the following code

    const TimelineCurrent = tw.div<{ position: number }>`
      absolute
      left-0
      top-[0.575rem]
      ${(p) => `w-[calc(${p.position}%-0.75rem)]`}
      h-[0.35rem]
    `;
    

    it renders the following HTML

    <div position="100" class="absolute left-0 top-[0.575rem] w-[calc(100%-0.75rem)] h-[0.35rem]"></div>
    

    But the w-[calc(100%-0.75rem)] part is not picked up by Tailwind JIT, so it has no effect. Am I doing something wrong here?

  • 7

    v2.1.1 Typescript error: Property '$as' is missing in type

    Hello. We have upgraded the package from 2.0.4 to 2.1.1 and received new typescript errors all over the codebase. Most of them indicate some kind of issue with the new $as prop. For example:

    src/components/Navigation/NavBar.tsx(225,12): error TS2769: No overload matches this call.
      Overload 1 of 2, '(props: { onClick?: MouseEventHandler<HTMLAnchorElement> | undefined; className?: string | undefined; color?: string | undefined; id?: string | undefined; ... 262 more ...; $active?: boolean | undefined; } & { ...; } & { ...; }): ReactElement<...>', gave the following error.
        Property '$as' is missing in type '{ children: TFunctionResult; onClick: () => void; }' but required in type '{ onClick?: MouseEventHandler<HTMLAnchorElement> | undefined; className?: string | undefined; color?: string | undefined; id?: string | undefined; ... 262 more ...; $active?: boolean | undefined; }'.
      Overload 2 of 2, '(props: StyledComponentPropsWithAs<TailwindComponent<Pick<Pick<ClassAttributes<HTMLAnchorElement> & AnchorHTMLAttributes<HTMLAnchorElement>, "key" | keyof AnchorHTMLAttributes<...>> & RefAttributes<...>, "key" | keyof AnchorHTMLAttributes<...>> & RefAttributes<...>>, ... 4 more ..., TailwindComponent<...>>): ReactElement<...>', gave the following error.
        Property '$as' is missing in type '{ children: TFunctionResult; onClick: () => void; }' but required in type '{ onClick?: MouseEventHandler<HTMLAnchorElement> | undefined; className?: string | undefined; color?: string | undefined; id?: string | undefined; ... 262 more ...; $active?: boolean | undefined; }'.
    

    The source code is as simple as follows:

    const LogInAnchor = tw.a`
      ...
    `
    const TwAnchor = tw(LogInAnchor)`
      ...
    `
    const Anchor = styled(TwAnchor)<{ $active?: boolean }>`
      ...
    `
    // later, error at the render site
    <Anchor>Logout</Anchor>
    

    By looking at the library's source code I couldn't understand why is the $as prop reported as missing, since it seems to be optional. Do you have any idea why this error occurs and how to fix it?

  • 8

    Tailwind Styled Components not working on Tailwind CSS v3

    I get the following error only when I try to use tailwind styled components:

    WARNING in ./node_modules/tailwind-styled-components/dist/domElements.js
    Module Warning (from ./node_modules/source-map-loader/dist/cjs.js):
    Failed to parse source map from '/home/amejia/code/projects/cryptopunks-clone/node_modules/tailwind-styled-components/src/domElements.ts' file: Error: ENOENT: no such file or directory, open '/home/amejia/code/projects/cryptopunks-clone/node_modules/tailwind-styled-components/src/domElements.ts'
     @ ./node_modules/tailwind-styled-components/dist/tailwind.js 15:38-62
     @ ./node_modules/tailwind-styled-components/dist/index.js 14:35-56
     @ ./src/App.js 6:0-44 21:16-22
     @ ./src/index.js 7:0-24 11:33-36
    
    WARNING in ./node_modules/tailwind-styled-components/dist/index.js
    Module Warning (from ./node_modules/source-map-loader/dist/cjs.js):
    Failed to parse source map from '/home/amejia/code/projects/cryptopunks-clone/node_modules/tailwind-styled-components/src/index.ts' file: Error: ENOENT: no such file or directory, open '/home/amejia/code/projects/cryptopunks-clone/node_modules/tailwind-styled-components/src/index.ts'
     @ ./src/App.js 6:0-44 21:16-22
     @ ./src/index.js 7:0-24 11:33-36
    
    WARNING in ./node_modules/tailwind-styled-components/dist/tailwind.js
    Module Warning (from ./node_modules/source-map-loader/dist/cjs.js):
    Failed to parse source map from '/home/amejia/code/projects/cryptopunks-clone/node_modules/tailwind-styled-components/src/tailwind.tsx' file: Error: ENOENT: no such file or directory, open '/home/amejia/code/projects/cryptopunks-clone/node_modules/tailwind-styled-components/src/tailwind.tsx'
     @ ./node_modules/tailwind-styled-components/dist/index.js 14:35-56
     @ ./src/App.js 6:0-44 21:16-22
     @ ./src/index.js 7:0-24 11:33-36
    
    3 warnings have detailed information that is not shown.
    Use 'stats.errorDetails: true' resp. '--stats-error-details' to show it.
    
    webpack 5.65.0 compiled with 3 warnings in 1682 ms
    

    I created my project following Tailwind CSS v3 documentation and Tailwind works fine in classNames, but whenever I try to use React Styled Components, although the styles are applied, the previous error pops up. bug

  • 9

    TypeScript Error: This expression is not callable

    I'm getting the following TypeScript error when I use this package in a .tsx file. TS2349: This expression is not callable. Not all constituents of type 'FunctionTemplate | undefined' are callable. Type 'undefined' has no call signatures.

    • tsc version: 4.0.5
    • tailwind-styled-components version: 1.0.6

    TypeScript configuration:

    {
      "compilerOptions": {
        "target": "es5",
        "lib": [
          "dom",
          "dom.iterable",
          "esnext"
        ],
        "allowJs": true,
        "skipLibCheck": true,
        "strict": true,
        "forceConsistentCasingInFileNames": true,
        "noEmit": true,
        "esModuleInterop": true,
        "module": "esnext",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "isolatedModules": true,
        "jsx": "preserve",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
      },
      "include": [
        "**/*.ts",
        "**/*.tsx"
      ],
      "exclude": [
        "./node_modules",
        "**/**/*.svg"
      ]
    }
    
  • 10

    Question / Feature Request: Ability to target children

    First off I'd like to thank everyone involved for this library!

    I've recently found myself trying to style components that come from other libraries and, while I can use tw(ThirdPartyComponent), it adds Tailwind classes only to the "container" and can't use CSS selectors to target any children.

    Would an API like this be possible?

    const MyComponent = tw.div`
      py-8
      relative
    
      ${(props, selector) => selector('.child-element')`
        px-4
        absolute
      `}
    `;
    

    Where selector would try to find any child matching the CSS selector passed.

  • 11

    Feature: using tailwind-merge library to override parent classes

    Hi! First of all, this is a great library as it makes working with Tailwind a lot easier, and I'm very thankful for that.

    Like the documentation explains, this library doesn't override parent classes, it just concatenates the different classes and the result will depend on each case. Which brings to the following suggestion: how possible would it be to switch the use of the current library tailwindcss-classnames to tailwind-merge (https://github.com/dcastil/tailwind-merge) as this other library already handles tailwind classes overriding?

    I understand that this might be a breaking change given the current state of the library (if it's a hard change, but maybe it could be configurable?). However I believe users would benefit a lot more if it were possible to override parent classes.

    Thanks!

  • 12

    Question: Does this library add the css in at runtime or compile time?

    Other similar libraries like twin.macro, convert the css-in-js to css at compile time. Wanted to know if this library does the same, or does it insert css at runtime like styled-components, since that is not mentioned anywhere in the README.

  • 13

    TypeError: default is not a function

    image

    I'm getting this in vitest jsdom tests. All the tests that import a file that contains a tw component fail with this error, regardless of tw(xxx) or tw.xxx.

  • 14

    An issue occurs when I customize font-size.

    There is an error that results in priority when customizing the naming of the font size.

    Normal

    |tailwind-styled-components Tag|Normarl Tag|Result| |---|---|---| |image|image|image|

    Customize

    |tailwind-styled-components Tag|Normarl Tag| |---|---| |image|image

    |Result|tailwind.config.js| |---|---| |image|image|

    Does anyone have the same issue as this?

  • 15

    TypeScript Error..

    module.style.ts file import tw from "tailwind-styled-components";

    import tw from "tailwind-styled-components";

    export const Container = tw.divbg-blue-600;

    export const Button = tw.buttonp-2;

    index.tsx file import React from "react"; import { Button, Container } from "./module.navbarStyles";

    type Props = {};

    const index = (props: Props) => { return ( ); };

    export default index;

    Then I got this type of Error. Type instantiation is excessively deep and possibly infinite.ts(2589). image

  • 16

    Polymorphic Component: "Type instantiation is excessively deep and possibly infinite"

    When using the $as prop of my StyledButton, typescript complains with the following error on StyledButton:

    Type instantiation is excessively deep and possibly infinite. ts(2589)
    

    Here's a small example:

    import { ComponentProps, ElementType, PropsWithChildren } from "react"
    import tw from "tailwind-styled-components"
    
    type ButtonOwnProps<E extends ElementType> = PropsWithChildren<{
      as?: E
      pill?: boolean
    }>
    
    type ButtonProps<E extends ElementType> = ButtonOwnProps<E> & ComponentProps<E>
    
    const StyledButton = tw.button<{ $size: ButtonSize; $style: ButtonStyle; $pill: boolean }>`
    px-3
    py-2
    inline-flex
    items-center
    font-medium
    text-base
    bg-blue-600
    text-white
    
    ${({ $pill }) => $pill ? "rounded-full" : "rounded-md"}
    `
    
    export const Button = <E extends ElementType = "button">({
      as,
      pill = false,
      children,
      ...props
    }: ButtonProps<E>) => (
      <StyledButton $as={as} $pill={pill} {...props}> // Type instantiation is excessively deep and possibly infinite. ts(2589)
        {children}
      </StyledButton>
    )