API Reference
Interpolation

Interpolation

Interpolation allows you to use any identifier as a value inside CSS, where "any identifier" refers to any ReScript variables or module accessors (opens in a new tab). This is useful for reusing variables, handling themes, or conditionally applying styles based on props at run-time.

module Component = %styled.div(`
  border: 1px solid $(Theme.black);
`)
module Component = [%styled.div {|
  border: 1px solid $(Theme.black);
|}];
module Component = %styled.div(`
  max-width: $(maxWidth);
`)
module Component = [%styled.div {|
  max-width: $(maxWidth);
|}];

Interpolation works inside values, media-queries and selectors. It doesn't work for entire properties or any interpolation, which is slightly different from SASS/Less and other JavaScript-based solutions (such as styled-components or emotion) as their interpolation is more dynamic and can be used everywhere.

💡

The rules for interpolation works the same way as CSS variables (opens in a new tab) (var());

styled-ppx forces you to be more rigid but with the promise of being type-safe, which is the same way as ReScript does ❤️. The dynamism from JavaScript-based solutions comes with the cost of being unsafe.

Here's an example: `margin-${whatever}: 10px` is valid in JavaScript, while isn't valid in styled-ppx. As explained above, this interpolation can't be applied to entire properties or half-properties.

The solution is simple, you would handle all properties based on the dynamic value:

let margin = direction =>
  switch direction {
  | Left => %css("margin-left: 10px;")
  | Right => %css("margin-right: 10px;")
  | Top => %css("margin-top: 10px;")
  | Bottom => %css("margin-bottom: 10px;")
  }
let margin = direction =>
  switch (direction) {
  | Left => [%css "margin-left: 10px;"]
  | Right => [%css "margin-right: 10px;"]
  | Top => [%css "margin-top: 10px;"]
  | Bottom => [%css "margin-bottom: 10px;"]
  };

If you aren't familiar with %css [%css] extension, take a look at the %css [%css] section.

Example

Any value from any property can be interpolated. It relies on the position of the interpolation to guess which value you are trying to interpolate.

module Size = {
  let small = CSS.px(10)
}
 
%cx("margin: $(Size.small)") // -> margin: 10px;
%cx("margin: $(Size.small) 0") // -> margin: 10px 0;
%cx("margin: $(Size.small) $(Size.small)") // -> margin: 10px 10px;
module Size = {
  let small = CSS.px(10);
};
 
[%cx "margin: $(Size.small)"]; // -> margin: 10px;
[%cx "margin: $(Size.small) 0"]; // -> margin: 10px 0;
[%cx "margin: $(Size.small) $(Size.small)"]; // margin: 10px 10px;

Features

  • Type-safety via type holes.
  • Support for shorthand properties and interpolate on a value.

Type-safety

The example above introduces the API from CSS to define the value of margin. We expect you to use it to make sure the values are interpoilated with the right type. In the example above margin expects one of:

type length = [
  | #ch(float)
  | #em(float)
  | #ex(float)
  | #rem(float)
  | #vh(float)
  | #vw(float)
  | #vmin(float)
  | #vmax(float)
  | #px(int)
  | #pxFloat(float)
  | #cm(float)
  | #mm(float)
  | #inch(float)
  | #pc(float)
  | #pt(int)
  | #zero
  | #percent(float)
]
type length = [
  | `ch(float)
  | `em(float)
  | `ex(float)
  | `rem(float)
  | `vh(float)
  | `vw(float)
  | `vmin(float)
  | `vmax(float)
  | `px(int)
  | `pxFloat(float)
  | `cm(float)
  | `mm(float)
  | `inch(float)
  | `pc(float)
  | `pt(int)
  | `zero
  | `percent(float)
];

Since Size.small Size.small is #px(int) `px(int), the type-checker would allow it.

A note about the polymorphism of CSS

There are plenty of properties in CSS that accept different types of values. Some of the most challenging ones are animation, box-shadow/text-shadow, background, transition and transform, to name a few. These properties are not challenging because they are shorthand properties, but because they have values that are positional and optional at the same time.

Let's look at background.

background: #fff; /* The background is white */
background: url(img.png); /* The background is an image */
background: #fff url(img.png); /* The background is white with an image */
background: url(img.png) no-repeat; /* The background is a non-repeating image */

In this case, to interpolate the background's value like: background: $(variable1) $(variable2) the type-checker can't know the type of $(variable1) and (variable2) ahead of time, because there are numerous possibilities for a valid background CSS property. This is called an overload (opens in a new tab) in other languages and it's not available in ReScript due to it's nature of being a static typed language.

What if a property isn't supported

First, if you have the time, please open an issue (opens in a new tab). Most properties are trivial to add support for. If time isn't your best friend then there's a workaround.

There is no way to add unsafe behaviour to CSS definitions. We prefer to keep improving the overall safety of styled-ppx via requests/issues rather than allowing a method for unsafe functionality - doing so would negate the whole purpose of styled-ppx.

The workaround is to use the Array API to generate %cx [%cx] calls. For example:

let block: Css.rule = %css("display: block")
let randomProperty = CSS.unsafe("-webkit-invented-property", "10px");
let picture = %cx([block, randomProperty]);
let block: Css.rule = [%css "display: block"];
let randomProperty = CSS.unsafe("-webkit-invented-property", "10px");
let picture = [%cx [|block, randomProperty|]];
let randomProperty = CSS.unsafe("-webkit-invented-property", "10px");
let picture = %styled.div([randomProperty]);
let randomProperty = CSS.unsafe("-webkit-invented-property", "10px");
let picture = [%styled.div [|randomProperty|]];

Here the safety will rely on your usage of CSS.unsafe CSS.unsafe correctly.

For a general overview of the list take a look at support for CSS (opens in a new tab).

Not valid interpolation

Interpolation in a ppx is a little limited, which makes a few "use-cases" not possible, for example: abstract a function or a variable reference.

// 🔴 Can't pass a function
let fn = (~kind, ~big) => { /* ... */ };
module X = %styled.div(fn)
/* 🔴 Can't pass a function*/
let fn = (~kind, ~big) => /* ... */
module X = [%styled.div fn];
// 🔴 Can't pass a variable reference
let value = "display: block"
module X = %styled.div(value)
/* 🔴 Can't pass a variable reference*/
let value = "display: block";
module X = [%styled.div value];