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);
)
💡

Don't confuse interpolation from String Interpolation from ReScript.

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

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;

Type-safety

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

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.