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.
Don't confuse interpolation from String Interpolation from ReScript (opens in a new tab).
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];