More ergnomic way to escape-hatch a wrapper React component (for using Babel macros, emotion, css styling, etc)

I’ve more or less settled on the following way for styling react components which lets me use babel macros, twin.macro, and the @emotion/react css prop. I’ve decided not to use bs-css and bs-css-emotion because it relies on @emotion/css which is less ergonomic.

Pros:

  • Can use twin.macro to convert Tailwind classes to emotion style objects (tailwind global CSS no longer needs to be imported)
  • Can use emotion/twin.macro babel macro features
  • Can write regular CSS, so that non-devs can edit CSS.

Cons:

  • No type safety on CSS stuff
  • Some boilerplate in the code to wrap the styled HTML element, and possibly runtime cost?
  • The emitted bs.js file technically still contains escape-hatched JSX code in it, though it still works because my pipeline still processes the JSX in .js files.

I wanted to share this in case anyone else wanted to go this route, but also to get feedback in case there is a more succinct way to do this escape hatch.

%%raw("import { css} from '@emotion/react'");
%%raw("import styled from '@emotion/styled'");
%%raw("import tw from 'twin.macro'");

// Use emotion's styled API to fully escape hatch the styled React component
module Root = {
  let el = %raw("styled.div`
      ${tw`border-4 border-solid border-purple-500`}
    `");
  @react.component
  let make = (~children) => %raw("<Root.el>{Props.children}</Root.el>");
};

// A second styled component in the same file
module Styled = {
  let el = %raw("styled.div`
      ${tw`border-4 border-solid border-green-500`}
      border-width: 1px;
    `");

   // Here "Styled" in the raw block refers to the output JS variable, and not the Rescript "Styled" symbol, which I feel may be dangerous?
  @react.component
  let make = (~children) => %raw("<Styled.el>{Props.children}</Styled.el>");
};

module Style = {
  let style = %raw("css`
      border: 1px solid black;
    `");

  // Here "Style" in the raw block refers to the output JS variable, and not the Rescript "Style" symbol, which I feel may be dangerous?
  @react.component
  let make = (~children) => %raw("<div css={Style.style}>{Props.children}</div>");
};

@genType
@react.component
let make = () => {
  <>
    <Root>{React.string("This box should have a purple border")}</Root>
    <StyledApi>{React.string("This box should have a green border")}</StyledApi>
    <CssProp>{React.string("This box should have a black border")}</CssProp>
    <h2>{React.string("Hi from ")}<span>{React.string("React")}</span></h2>
  </>;
};

Is there a more ergonomic way to create an escape-hatched react wrapper component that includes the children? Currently the major downside is that I lose the ease of just doing a “css” prop and being able to inline styles if this was Typescript. With this method I have to create a component module for each HTML element I want to style.

<div css={css`border: 1px solid black`}>inline styles in Typescript</div>

I think ideally I’d replace with the React.createElement with the JSX function from @emotion/react (which adds the CSS prop), and then some escape hatch feature to add an arbitrary untyped prop to JSX on the Rescript side:

// Hypothetical JSX prop escape hatch
<div [%raw css={css`border: 1px solid black`}]>inline styles in Typescript</div>
1 Like

Hi @notchris, not a full answer to your question, but would the React.cloneElement() function help with adding the css property?

Hi @notchris , when you say @emotion/react is more ergonomic than @emotion/css, is it related to what you said in the ‘pros’, or something else ?

Those pros are pros over using bs-css. The benefit of @emotion/react over @emotion/css is mainly being able to use twin.macro and free SSR support.

Yes possibly. I’ll have to play around with that. But is there a runtime cost?