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.small
Size.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.