Twin.macro: Q: Why is emotion necessary with CRA?

Created on 7 May 2020  ยท  16Comments  ยท  Source: ben-rogerson/twin.macro

The top of the readme there is the following simple way which is absolutely amazing at a first sight.

import tw from 'twin.macro'

const default () => <input tw="border hover:border-black" />

Furthermore, there is also this sentence which would give me the impression that without conditionals I can use macro only.

For features like conditional styling and vanilla css, Twin works with styling libraries like ๐Ÿ‘ฉโ€๐ŸŽค emotion or ๐Ÿ’… styled-components

Unfortunately, when I try that in a clean CRA project, the output I get is like this and that's where the magical feeling is kinda lost.

<input css="[object Object]" />

To make that work I have to include this ceremony in each file that needs some styling.

/** @jsx jsx */
import { jsx } from '@emotion/core'
import tw from 'twin.macro'

Is there some better way I am simply not seeing?

discussion

Most helpful comment

Thanks for the recommendation, but I am not a big fan of abstracting every little piece into a separate component. Ultimately, it creates a bigger mess than it solves imo. Of course, it makes sense for some frequently used parts, no argument there.

Consider this example I found somewhere around here. If I would be doing tw.div for each one of them then I am kinda losing a big benefit of TW - coming up with clever names for styled pieces of markup. So yea, in cases like that I would rather have two unused imports than bunch of styled components.

    <div tw="max-w-md mx-auto flex p-6 bg-gray-100 mt-10 rounded-lg shadow-xl">
      <div tw="ml-6 pt-1">
        <h1 tw="text-2xl text-blue-700 leading-tight">
          Tailwind and Create React App
        </h1>
        <p tw="text-base text-gray-700 leading-normal">
          Building apps together
        </p>
      </div>
    </div>

Maybe it's even better to have common parts in TW config, I am not that much experienced with TW yet to decide on that.

Feel free to close this unless you want to use this issue to track docs improvement.

All 16 comments

You can use styled-components instead which has less 'ceremony'. Just follow the instructions for styled-components then you can do this

import tw from "twin.macro"

const RedButton = tw.button`rounded bg-red-400 text-white`

Note: the above syntax does work with emotion as well but you don't need to include the /** @jsx jsx */ with styled-components which gives it a cleaner API IMO.

With the above import you can also do this

const RedButton = <button tw="rounded bg-red-400 text-white"/>

If that's more your speed.

You only ever need to include styled-components if you want to do something like this

import Styled from "styled-components"
import tw from "twin.macro"

const Button = Styled(button)`
    ${tw`rounded text-white`}

    font-size: 13pt; // any css here
`

using this syntax you can even do some conditionals etc

Hope that helps :)

Thanks for the fast response

I followed instructions for styled-components which are pretty much the same as for emotion except "styled": "styled-components" for babel macros config. However, I am still getting the css="[object Object]" on the output. I am not sure how could do any difference.

I tried to reproduce in CodeSandbox but I am getting some cryptic error there. I suppose it's a problem of CS, when I download and run it locally, there is no such error, but the transformation is still not happening. But at least you can have a look if I am doing some configuration mistake there.

https://codesandbox.io/s/floral-dew-hgs66?file=/src/App.js

Alright, I know what's been missing now. I need both these imports. It should be definitely mentioned somewhere.

import tw from 'twin.macro'
import { css } from 'styled-components/macro'

It's one less line than with emotion but still feels somewhat weird to import two things that are not actually used in the file. Why can't Twin add SC import based on macro config?

I am trying to weigh the options here. If I would go with the standard path of building Tailwind and running with PurgeCSS, I would get almost the same result with almost none ceremony (no unused imports). Just have to be using verbose className instead. Conditionals would work with clsx and similar, but anytime I would want styling outside of Tailwind feature scope (eg. keyframes for now) I would need either use plain CSS files or some CSS in JS solution anyway.

The clear benefit of Twin is purge out of the box and also checking for the existence of specified classes (and suggesting from existing ones). I guess it's a win in the long run.

Alright, I know what's been missing now. I need both these imports. It should be definitely mentioned somewhere.

import tw from 'twin.macro'
import { css } from 'styled-components/macro'

~~You don't need to import { css } from styled-components/macro. I'm not sure why you're running into that issue.

Please see this repo: https://github.com/rbutera/greater-gatsby/ where I have a number of components which do not import anything from styled components. Maybe it will help if you see how that project is configured. Let me know if we should update our documentation on twin.macro

For example, footer.tsx

Ah. I've just seen that to use the tw prop you need to import 'styled-components/macro'. That's interesting. I never personally use twin.macro in that way so I gave you an erroneous answer there. Apologies!

My personal recommendation, seeing as you asked "Is there some better way I am simply not seeing?" I would recommend foregoing the tw prop when you don't want to add the macro import and using the tw macro as is
(ie.

tw.button`bg-red-400`

if you're styling a standard HTML element, like a <button> for example

or this syntax:

tw(CustomButton)`bg-red-400`

if you're using a custom component

or the normal css-in-js styled-components API

Styled(CustomButton)`${tw`bg-red-400`}`  // note: you have to import Styled for this one

if you want to add additional css properties.

The benefit of that is that you keep your component styling separate from your custom component logic.

You can even reuse the same component logic with multiple styles. This is a very common / comfortable pattern in projects using CSS-in-JS.

Thanks for the recommendation, but I am not a big fan of abstracting every little piece into a separate component. Ultimately, it creates a bigger mess than it solves imo. Of course, it makes sense for some frequently used parts, no argument there.

Consider this example I found somewhere around here. If I would be doing tw.div for each one of them then I am kinda losing a big benefit of TW - coming up with clever names for styled pieces of markup. So yea, in cases like that I would rather have two unused imports than bunch of styled components.

    <div tw="max-w-md mx-auto flex p-6 bg-gray-100 mt-10 rounded-lg shadow-xl">
      <div tw="ml-6 pt-1">
        <h1 tw="text-2xl text-blue-700 leading-tight">
          Tailwind and Create React App
        </h1>
        <p tw="text-base text-gray-700 leading-normal">
          Building apps together
        </p>
      </div>
    </div>

Maybe it's even better to have common parts in TW config, I am not that much experienced with TW yet to decide on that.

Feel free to close this unless you want to use this issue to track docs improvement.

Hey Fredy, thanks for letting us know about the docs issue. I'll adjust the docs.

Regarding Emotion and CRA, their Babel macro can't add a css prop for you so I demonstrated using the jsx pragma replacement in the starter.

You don't need to use the jsx pragma in CRA.
The pain is that CRA doesn't let you extend their babel config by default so we'll need to add the css prop with customize-cra.

I've created a codesandbox running without the jsx pragma but the codesandbox isn't working correctly so you'll need to export as zip and run it locally (which does work).

Here's how you can use Emotion with CRA without the jsx pragma:

  1. npm i -D @emotion/babel-preset-css-prop customize-cra react-app-rewired

  2. Replace the scripts in package.json with:

    "scripts": {
        "start": "react-app-rewired start",
        "build": "react-app-rewired build",
        "test": "react-app-rewired test --env=jsdom",
        "eject": "react-app-rewired eject",
        "format": "prettier --write \"**/*.{js,jsx,json,md}\""
      },
    
  3. Add a config-overrides.js in your root containing:

    const { override, addBabelPreset } = require('customize-cra')
    module.exports = override(addBabelPreset('@emotion/babel-preset-css-prop'))
    

Q: Why is emotion necessary with CRA?

Twin only performs the Tailwind class-to-css-object conversion.
This means you can add tw directly within a style prop without any other packages:

import tw from 'twin.macro'
<input style={tw`bg-black`} />

But that won't work for all css features, like hover, &, etc.

When you use tw prop, Twin performs the conversion and then "swaps itself out" for a css prop from css-in-js frameworks like emotion or styled-components.

Thanks for the recommendation, but I am not a big fan of abstracting every little piece into a separate component. Ultimately, it creates a bigger mess than it solves imo. Of course, it makes sense for some frequently used parts, no argument there.

+1 to this. But even for commonly used components it would make more sense to me to just create a separate file and use the tw prop in combination with the css prop for conditional / unsupported styles. The styled-components way doesn't feel very tailwind-like to me. You basically are disrupted in your flow of getting idea on how elements are styled because you move it to another part of your file.

It would be cool if you could consider making this the recommended way to use this library.

@ben-rogerson Thanks a lot for detailed explanations, makes total sense now.

I wonder if you are aware of some differences in the implementation of css prop with emotion and styled-components. Is emotion _superior_ in some way or is essentially the same?

@impulse I really like that Twin accommodates the two different styling techniques.
Many people enjoy keeping their jsx and styling separate and we both don't know enough of their situation to start recommending one over the other. Also, I haven't seen any huge trends towards using one over the other :)

@FredyC I'm not aware of the internals of either package so I can't comment on performance. I also find the feature set between Emotion and Styled-components is extremely similar.
But if you're choosing to use the tw and css props within CRA then I can totally recommend a path!

First up, I don't have much against Babel plugins except that I'd rather use a Babel macro if it's available - this post explains the rationale. So here's some advice for those of a similar opinion:

In late 2018 styled-components added the css prop to the main package which means you only need to import 'styled-components/macro' and you have the css prop. Then when you need a little more functionality, you just change that same import to import { css } from 'styled-components/macro' and you've also got access to the css function. For example:

import { css } from 'styled-components/macro'

<div css={[
    tw`text-white`,
    css`
        & + & {
            ${tw`bg-purple-500`}
        }
    `
]}
>

To do the same thing in Emotion, you'll need to use either their jsx pragma (which eeks me) or their babel-preset-css-prop package combined with customize-cra (or Crako etc.

Here's the imports needed for a css prop feature between the two options:

// Styled components
import { css } from 'styled-components/macro'

// Emotion
/** @jsx jsx */
import { jsx, css } from '@emotion/core'

With Emotion, once you're setup with customize-cra things are fairly nice - but you shouldn't need to do this in the first place. I want an editable Babel config in CRA by default dammit, and I shouldn't need to install packages that hook into the CRA internals to get it done ๐Ÿ˜†

Ouch, just spent 10 minutes of head-scratching why I am again getting infamous css="[object Object]" when I already saw it working. Then out of the misery, I tried swapping imports and boom.

Only this works, no other way around ๐Ÿคฏ

import "styled-components/macro";
import tw from "twin.macro";

However, when I import css, the order suddenly doesn't matter.

import tw from "twin.macro";
import { css } from "styled-components/macro";

That is an eerily fragile rule. I am almost scared to use that in the project. I am sure there are the technical reasons why it behaves like that, but I wonder if TW could do something about it?

Could TW ensure the import for SC/Emotion (based on config) so it's more straightforward? Or it's technically impossible for a macro to introduce another macro to file? I assume the latter. In that case, can we at least get a warning when tw prop is used without anything to transform it?

I remember hitting this at one stage too.
I'll take a look at what's possible when I'm working on the macro next ๐Ÿ‘

I wonder why to close this? I don't see any docs update nor attempt to improve situation outlined in my last comment? Maybe I am just looking at the wrong place?

The convo moved off-topic so I'll split this off as a separate issue.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kieran-osgood picture kieran-osgood  ยท  5Comments

denvash picture denvash  ยท  6Comments

luknl picture luknl  ยท  7Comments

vincent-vieira picture vincent-vieira  ยท  7Comments

owaiswiz picture owaiswiz  ยท  3Comments