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.

Both %styled.<tag> and %cx accept an array of Css.rule values as an alternative to a plain CSS string. This is the Array API. It lets you compose styles from variables, conditional expressions, pattern matches, and function calls — things that are impossible inside a static string — while still producing fully scoped, emotion-injected CSS at runtime.

What is Css.rule?

Css.rule is a single CSS declaration, selector block, or at-rule. Concretely it covers:
  • A CSS property declaration — e.g. display: flex
  • A CSS selector — e.g. &:hover { color: red; }
  • A CSS at-rule — e.g. @media (max-width: 600px) { ... }
You create Css.rule values using:
  • %css("...") (ReScript) / [%css "..."] (Reason) — the inline CSS extension (see %css reference)
  • Any constructor from the CSS.* module (see Runtime: CSS)

Basic example

module Component = %styled.section([
  %css("display: flex"),
  %css("justify-content: center"),
])

let className = %cx([%css("display: flex;")])
Any expression that evaluates to Css.rule is valid inside the array.

Features

Composability

Reference shared style variables or helper functions defined elsewhere in your codebase. DRY out common patterns across components.

Conditionals

Use ternary expressions or if/else to apply styles based on props or environment values.

Pattern matching

Use switch to map a variant prop to a specific CSS declaration — type-safely.

bs-css migration

Bridge existing bs-css rules or inject unsupported properties via CSS.unsafe without rewriting everything at once.

Composability, conditionals, and function calls

module Button = %styled.button([
  buttonStyles,                                    // a variable reference
  anyRandomFunction(123),                          // a function call
  boolean ? %css("width: 100%;") : %css("width: auto"), // conditional
])

Pattern matching in dynamic components

Combine dynamic components with the Array API to use switch expressions that map typed props to CSS declarations:
module Align = %styled.div((~distribute=#Center, ~align=#Center) => [
  %css("display: flex"),
  %css("height: 100%"),
  %css("width: 100%;"),
  switch distribute {
  | #Start => %css("justify-content: flex-start")
  | #Center => %css("justify-content: center")
  | #End => %css("justify-content: flex-end")
  },
  switch align {
  | #Start => %css("align-items: flex-start")
  | #Center => %css("align-items: center")
  | #End => %css("align-items: flex-end")
  },
])

<Align distribute=#Start align=#Start />

Usage with dynamic components (function body)

When the component function needs to run arbitrary code before producing the array — for example, calling a theme function — wrap the function body in braces and end it with an array literal:
module Button = %styled.button((~variant) => {
  let color = Theme.button(~variant)

  [
    %css("background-color: $(color)"),
    %css("width: 100%;"),
    %css("display: inline-flex"),
  ]
})
The function is re-run on every render, so Theme.button(~variant) is called with fresh props each time.

Limitation: the last expression must be an array literal

Just as with string-form dynamic components, the last expression of the function body must be an array literal written directly in the source — it cannot be a variable reference or a function call that returns an array.
// 🔴 Cannot return a variable reference
let myStyles = [%css("display: flex")]
module X = %styled.div((~variant) => myStyles)
// 🔴 Cannot return a function call
module X = %styled.div((~variant) => buildStyles(variant))
The PPX needs to see the literal array node in the AST at compile time to instrument each element. Use a function body with let bindings and end with an explicit [...] / [|...|] literal.

Migration from bs-css and unsupported properties

The Array API is the recommended path for migrating from bs-css or for using CSS properties that styled-ppx’s parser does not yet support. Pass existing Css.rule values you already have, or use CSS.unsafe to inject a raw property string:
let block: Css.rule = %css("display: block")
let randomProperty = CSS.unsafe("-webkit-invented-property", "10px")
let picture = %cx([block, randomProperty])
You can do the same with %styled:
let randomProperty = CSS.unsafe("-webkit-invented-property", "10px")
module Picture = %styled.div([randomProperty])
When using CSS.unsafe, type safety is your responsibility. The property name and value are passed through to emotion without any validation by styled-ppx. Prefer filing an issue or opening a PR to add proper support for a missing property if you have the time.

Build docs developers (and LLMs) love