ben-rogerson / twin.macro

🦹‍♂️ Twin blends the magic of Tailwind with the flexibility of css-in-js (emotion, styled-components, solid-styled-components, stitches and goober) at build time.
MIT License
7.92k stars 183 forks source link

"tw" property fails to override variant styling in Stitches #731

Closed idrm closed 2 years ago

idrm commented 2 years ago

The tw property fails to override a (Stitches) component's styling when that styling is inside a variant definition. E.g. the following will render the text in red instead of blue:

const StyledDiv = styled.div({
  variants: {
    xyz: {
      true: tw`text-red-500`,
    },
  }
})

<StyledDiv tw="text-blue-500" xyz>Hello world</StyledDiv>

Oddly enough, it works when I try to override using the css property instead, e.g.

<StyledDiv css={{...tw`text-blue-500`}} xyz>Hello world</StyledDiv>

Here's a replication of the issue in Sandbox (using the the current twin.macro + next.js + Stitches twin.examples repo as a starting point).

I haven't yet investigated the underlying cause, but I suspect it's something to do with CSS precedence rules.

ben-rogerson commented 2 years ago

Thanks for the great bug replication.

Here's the conversion data - perhaps you can guide me on what needs to be changed to get this feature working in stitches?

// in

import tw, { styled } from "twin.macro";

const StyledDiv = styled.div({
  variants: {
    xyz: {
      true: tw`text-red-500`,
    },
  },
});

const IndexPage = () => (
  <StyledDiv tw="text-blue-500" xyz>
    Hello world
  </StyledDiv>
);
// out

import { styled as _styled } from "stitches.config.js";

const StyledDiv = _styled("div", {
  variants: {
    xyz: {
      true: {
        "--tw-text-opacity": "1",
        "color": "rgba(239, 68, 68, var(--tw-text-opacity))"
      }
    }
  }
});

const _TwComponent = _styled(StyledDiv, {
  "--tw-text-opacity": "1",
  "color": "rgba(59, 130, 246, var(--tw-text-opacity))"
});

const IndexPage = () => /*#__PURE__*/React.createElement(_TwComponent, {
  xyz: true,
  "data-tw": "text-blue-500"
}, "Hello world");
idrm commented 2 years ago

The generated component code is correct, but it appears that the order of the generated CSS class rules (inside the head element) affects the final result. You can see the xyz=true class definition show up after that of the tw="text-blue-500" class, as well as the xyz=true class taking precedence in the computed styles section of Chrome's dev tools in the screenshot below (taken from the Sandbox demo I linked to previously): image

idrm commented 2 years ago

I modified the Sandbox demo to include a default green background styling in the component definition, as well as a yellow background override where the component is rendered. That worked as expected. The override seems to fail only in the case of variant styles.

import tw, { styled } from "twin.macro";

const StyledDiv = styled.div({
  ...tw`bg-green-500`,
  variants: {
    xyz: {
      true: tw`text-red-500`
    }
  }
});

const IndexPage = () => (
  <StyledDiv tw="text-blue-500 bg-yellow-500" xyz>
    Hello world
  </StyledDiv>
);
idrm commented 2 years ago

After digging inside the twin.macro source code, I believe the issue stems from how the tw attribute is handled and the resulting order of CSS classes. If I set the moveTwPropToStyled config option to false (and as long as I don't mix the tw and css props inside the same element) I get the proper order of class rules and the desired style override. Here's a CS demonstrating that.

idrm commented 2 years ago

It's possible the issue lies with the following bug in Stitches: stitchesjs/stitches#1060

Their reproduction demo shows the same CSS rule order behavior.

idrm commented 2 years ago

I'm closing this issue since it lies with how Stitches operates.