Interpolation

Interpolation allows to use any identifier as a value inside CSS. "any identifier" refers to any ReScript variables or module accessors.

Useful for reusing variables, handling themes or conditionally apply 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) their interpolation is more dynamic and can be used everywhere.

💡

Don't confuse interpolation from String Interpolation from ReScript.

The rules for interpolation works the same way as CSS variables (var()var());

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

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, neither 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 in the %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 = CssJs.px(10)
}
 
margin: $(Size.small); // -> margin: 10px;
margin: $(Size.small) 0; // -> margin: 10px 0;
margin: $(Size.small) $(Size.small); // -> margin: 10px 10px;
module Size = {
  let small = CssJs.px(10)
}
 
margin: $(Size.small); // -> margin: 10px;
margin: $(Size.small) 0; // -> margin: 10px 0;
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

We introduced here the API from CssJs 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:

auto | 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 | calc([ | add | sub], t, t) | percent(float)`
auto | 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 | calc([ | add | sub], t, t) | percent(float)`

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

A note about polymorphism of CSS

There are plenty of properties on CSS that accept different types of values, the ones encountered challenging are animation, box-shadow/text-shadow, background, transition and transform to name a few. Not because 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 */
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, since there's a few possibilities of background valid. This is called overload in other languages and it's not available in ReScript to it's nature of a static typed language.

What if a property isn't supported

First, if you have the time, please open an issue. Most properties are trivial to add support for. If time isn't your best friend: There's a workaround for unsupported properties.

There's no way to add unsafe behaviour on CSS definitions. Prefer to keep improving the overall safety via requests/issues than allowing a method for unsafe to all. It will loose the purpose of styled-ppx.

The workaround is to use the Array API to generate %cx%cx calls, like this example:

let block: Css.Rule.t = %css("display: block")
let randomProperty = CssJs.unsafe("aspect-ratio", "21 / 9");
let picture = %cx([block, randomProperty]);
let block: Css.Rule.t = %css("display: block")
let randomProperty = CssJs.unsafe("aspect-ratio", "21 / 9");
let picture = %cx([block, randomProperty]);
let randomProperty = CssJs.unsafe("aspect-ratio", "21 / 9");
let picture = %styled.div([randomProperty]);
let randomProperty = CssJs.unsafe("aspect-ratio", "21 / 9");
let picture = %styled.div([randomProperty]);

Here the lack of safety will rely on your usage of CssJs.unsafeCssJs.unsafe.

For a general overview of the list take a look at support for CSS.

Not valid interpolation

Interpolation in ppxes 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) => { /* ... */ };
%styled.div(fn)
// 🔴 Can't pass a function
let fn = (~kind, ~big) => { /* ... */ };
%styled.div(fn)
// 🔴 Can't pass a variable reference
let value = "display: block"
%styled.div(value)
// 🔴 Can't pass a variable reference
let value = "display: block"
%styled.div(value)