OCaml

This guide assumes that you’ll use opam and dune and have a server or native process running.

Install

opam install styled-ppx

Packages available

  • styled-ppx is the ppx to transform [%styled.div ""] and [%cx ""]
  • styled-ppx.native is the library with the CSS bindings, and the implementation of emotion.sh on the server: capable of storing CSS, hashing it, generating a unique classnames, and autoprefixing

Usage

Add styled-ppx under dune’s preprocess pps for any library or executable; and add styled-ppx.native as a library. Regardless of being a library or executable:

(library
  (name ...)
  (libraries
+  styled-ppx.native
   server-reason-react)
  (preprocess
   (pps
+   styled-ppx
    server-reason-react-ppx)))
(executable
  (libraries
+  styled-ppx.native
   server-reason-react)
  (preprocess
   (pps
+   styled-ppx
    server-reason-react-ppx)))

Note: server-reason-react and server-reason-react-ppx are optional, and only needed if you use styled components ([%styled.div {||}]).

API

  • CSS.get_stylesheet returns a string with all styles
  • CSS.style_tag returns a <style /> React element, with all styles. This is designed to be used with server-reason-react

Example

let className = [%cx {|
  display: flex;
  width: 100%;
  height: 100%;
  justify-content: center;
  align-items: center
|}];
 
let stylesheet: string = CSS.get_stylesheet();
 
print_endline(className);
/* .css-1xuw4bg */
 
print_endline(stylesheet);
/*
  .css-1xuw4bg {
    display: flex;
    width: 100%;
    height: 100%;
    justify-content: center;
    align-items: center;
  } */

Example with server-reason-react

/* This is a server-reason-react module with those styles encoded as a unique className */
module Link = [%styled.a (~color=CSS.hex("4299E1")) => {|
  font-size: 1.875rem;
  line-height: 1.5;
  text-decoration: none;
  margin: 0px;
  padding: 10px 0px;
  color: $(color);
|}];
 
/* This is a unique className pointing to those styles */
let layout = [%cx {|
  display: flex;
  width: 100%;
  height: 100%;
  justify-content: center;
  align-items: center
|}];
 
/* Later in a component */
let app =
  <div className=layout>
    <Link
      color={CSS.hex("333333")}
      href="https://sancho.dev"
      rel="noopener noreferrer">
      {React.string("sancho.dev")}
    </Link>
  </div>;

To generate the CSS on the server, you would need to use <CSS.style_tag />. This component will render a <style> tag with all the generated styles.

Given a Page component to simulate a real-world scenario:

module Page = {
  [@react.component]
  let make = () => {
    <html>
      <head>
        <CSS.style_tag />
      </head>
    </html>
  }
}

A note on missing classNames

If you’re using dynamic CSS values, such as dynamic-components or interpolation based on runtime values, you would need to evaluate your app before extracting all the CSS. Let me explain it with a simple example:

module App = {
  [@react.component]
  let make = (~value) => {
    let className = switch (value) {
      | Some(value) => [%cx "margin: $(value)"]
      | None => [%cx "margin: 0"]
    };
    <div className />
  }
};

This component (App) needs to be rendered before the CSS.style_tag otherwise the [%cx] calls won’t be added to the stylesheet, since they aren’t being executed yet.

CSS.style_tag returns a <style /> with all styles injected, and you can think of CSS’s stylesheet as a global registry for styles. If we place CSS.style_tag in the <head> and your App in the <body>, when we are rendering the head (happens before) the global registry won’t have the dynamic classNames until App is rendered, which happens later, when React renders the <body />.

To solve this, we recommend: render the React application first as a string, and then inject the result as dangerouslySetInnerHTML in the <body />. This way we ensure that the execution of the App happens before collecting all classNames.

/* `App` here is the entry component of your React application with the interactivity from React, while the `Document` component is running on the server and is static. */
module Document = {
  [@react.component]
  let make = () => {
    let app = ReactDOM.renderToString(<App />);
 
    <html>
      <head>
        <CSS.style_tag />
      </head>
      <body>
        <div id="root" dangerouslySetInnerHTML={"__html": app} />
      </body>
    </html>
  }
};
 
/* Let's assume we are using dream (from https://github.com/aantron/dream) */
let some_server_side_handler = _request => {
  /* Here we render the entire HTML */
  Dream.html(ReactDOM.renderToString(<Document />));
};

Advanced

If you don’t want to render the stylesheet directly, you can use CSS.get_stylesheet to obtain the stylesheet as a string. In this case, the hydration with the client won’t be supported.

To make sure hydration works, you would need the following <style/> tag:

React.createElement("style",
  [
    Bool("data-s", true),
    String("data-emotion", "css " ++ CSS.get_string_style_hashes()),
    DangerouslyInnerHtml(CSS.get_stylesheet())
 ], []
)