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>