Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/davesnx/styled-ppx/llms.txt

Use this file to discover all available pages before exploring further.

styled-ppx includes a native OCaml runtime — styled-ppx.native — that implements emotion’s CSS hashing and injection algorithm entirely on the server. This makes it possible to use the same [%styled.div], [%cx], and related extensions in a native OCaml program or HTTP server, then extract all generated styles as a string or as a React <style> element for server-side rendering. This guide assumes you have opam and dune installed and a server or native executable project to integrate with.
1

Install styled-ppx via opam

opam install styled-ppx
The opam package provides two libraries relevant to native use:
  • styled-ppx — the PPX binary that transforms [%styled.div {|...|}], [%cx {|...|}], and related extensions at compile time.
  • styled-ppx.native — the native runtime library. It exposes the CSS module and implements emotion-compatible CSS storage, hashing, unique className generation, and autoprefixing — entirely in OCaml, with no JavaScript dependency.
2

Update your dune build files

Add styled-ppx to (preprocess (pps ...)) and styled-ppx.native to (libraries ...) for every stanza that uses the extensions.For a (library ...) stanza:
(library
  (name ...)
  (libraries
+  styled-ppx.native
   server-reason-react)
  (preprocess
   (pps
+   styled-ppx
    server-reason-react-ppx)))
For an (executable ...) stanza:
(executable
  (libraries
+  styled-ppx.native
   server-reason-react)
  (preprocess
   (pps
+   styled-ppx
    server-reason-react-ppx)))
server-reason-react and server-reason-react-ppx are optional. They are only required if you use styled components ([%styled.div {||}]). If you only need [%cx] for className generation, you can omit them.
3

Learn the native-only API

The CSS module exposed by styled-ppx.native provides two server-side utilities not available in the browser runtimes:
FunctionReturn typeDescription
CSS.get_stylesheet()stringReturns the full stylesheet as a raw CSS string containing all rules registered so far.
CSS.style_tagReact.elementReturns a <style> React element with all styles injected. Designed for use with server-reason-react.
CSS.get_string_style_hashes()stringReturns a space-separated list of all registered style hashes. Required for client-side hydration.
4

Write a plain native example

The simplest use case is a native OCaml program that generates classNames and prints the resulting stylesheet:
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;
  } */
Every call to [%cx] registers its rules in a global stylesheet registry. CSS.get_stylesheet() drains that registry and returns the full CSS string, which you can embed in an HTML <style> tag or write to a .css file.
5

Use CSS.style_tag with server-reason-react

When building an SSR application with server-reason-react, place <CSS.style_tag /> inside the <head> of your document component. It renders a <style> element containing all styles that have been registered up to that point:
/* 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 inject the stylesheet into the rendered HTML, add <CSS.style_tag /> to the document <head>:
module Page = {
  [@react.component]
  let make = () => {
    <html>
      <head>
        <CSS.style_tag />
      </head>
    </html>
  }
};

Advanced: the dynamic CSS / missing classNames problem

If your application uses dynamic CSS values — such as dynamic components or interpolation based on runtime values — you must render your application before collecting styles. If CSS.style_tag or CSS.get_stylesheet() is called before the components that produce dynamic classNames have been rendered, those classNames will be absent from the stylesheet.
Consider this example:
module App = {
  [@react.component]
  let make = (~value) => {
    let className = switch (value) {
      | Some(value) => [%cx "margin: $(value)"]
      | None => [%cx "margin: 0"]
    };
    <div className />
  }
};
The [%cx] calls inside the switch are only executed when React renders App. If CSS.style_tag is placed in the <head> and App is placed in the <body>, React renders the head first — at which point the dynamic classNames have not yet been registered. The solution: render your React application to a string first, then inject the result as dangerouslySetInnerHTML in the <body>. This ensures that all [%cx] calls in App are executed before styles are collected:
/* `App` is the entry component of your React application.
   `Document` is a static server-only wrapper. */
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>
  }
};

/* Example using Dream (https://github.com/aantron/dream) */
let some_server_side_handler = _request => {
  Dream.html(ReactDOM.renderToString(<Document />));
};
Because ReactDOM.renderToString(<App />) runs synchronously before the outer renderToString(<Document />) reaches the <head>, all dynamic classNames are already registered when <CSS.style_tag /> is evaluated.

Advanced: client-side hydration

If you need the browser’s React to hydrate a server-rendered page, the <style> tag must carry the correct data-emotion attributes so that emotion on the client can recognise and reuse the server-injected styles. Instead of CSS.style_tag, construct the element manually using CSS.get_string_style_hashes() and CSS.get_stylesheet():
React.createElement("style",
  [
    Bool("data-s", true),
    String("data-emotion", "css " ++ CSS.get_string_style_hashes()),
    DangerouslyInnerHtml(CSS.get_stylesheet())
  ], []
)
CSS.get_string_style_hashes() returns a space-separated list of all hashes registered in the current stylesheet — exactly the format that emotion’s client-side runtime expects in the data-emotion attribute. This allows the browser to skip re-injecting styles that were already rendered by the server.

Build docs developers (and LLMs) love