Skip to content

Commit b8d0574

Browse files
authored
Merge pull request #269 from rescript-association/rr-style-docs
rescript-react style docs
2 parents 301c12d + 4d86472 commit b8d0574

File tree

4 files changed

+210
-0
lines changed

4 files changed

+210
-0
lines changed

data/sidebar_react_latest.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"arrays-and-keys",
1212
"refs-and-the-dom",
1313
"context",
14+
"styling",
1415
"router"
1516
],
1617
"Hooks & State Management": [

pages/docs/react/latest/styling.mdx

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
---
2+
title: Styling
3+
description: "Styling in ReScript & React"
4+
canonical: "/docs/react/latest/styling"
5+
---
6+
7+
# Styling
8+
9+
React comes with builtin support for inline styles, but there are also a number of third party libraries for styling React components. You might be comfortable with a specific setup, like:
10+
11+
- Global CSS / CSS modules
12+
- CSS utility libraries (`tailwindcss`, `tachyons`, `bootstrap` etc.)
13+
- CSS-in-JS (`styled-components`, `emotion`, etc.)
14+
15+
If they work in JS then they almost certainly work in ReScript. In the next few sections, we've shared some ideas for working with popular libraries. If you're interested in working with one you don't see here, search the [package index](https://rescript-lang.org/packages) or post in [the forum](https://forum.rescript-lang.org).
16+
17+
18+
## Inline Styles
19+
20+
This is the most basic form of styling, coming straight from the 90ies. You can apply a `style` attribute to any DOM element with our `ReactDOM.Style.make` API:
21+
22+
```res
23+
<div style={ReactDOM.Style.make(~color="#444444", ~fontSize="68px", ())} />
24+
```
25+
26+
It's a [labeled](/docs/manual/latest/function#labeled-arguments) (therefore typed) function call that maps to the familiar style object `{color: '#444444', fontSize: '68px'}`. For every CSS attribute in the CSS specfication, there is a camelCased label in our `make` function.
27+
28+
**Note** that `make` returns an opaque `ReactDOM.Style.t` type that you can't read into. We also expose a `ReactDOM.Style.combine` that takes in two `style`s and combine them.
29+
30+
### Escape Hatch: `unsafeAddProp`
31+
32+
The above `Style.make` API will safely type check every style field! However, we might have missed some more esoteric fields. If that's the case, the type system will tell you that the field you're trying to add doesn't exist. To remediate this, we're exposing a `ReactDOM.Style.unsafeAddProp` to dangerously add a field to a style:
33+
34+
```res
35+
let style =
36+
ReactDOM.Style.make(
37+
~color="red",
38+
~padding="10px",
39+
(),
40+
)->ReactDOM.Style.unsafeAddProp("-webkit-animation-name", "moveit")
41+
```
42+
43+
## Global CSS
44+
45+
Use a `%%raw` expression to import CSS files within your ReScript / React component code:
46+
47+
```rescript
48+
// in a CommonJS setup
49+
%%raw("require('./styles/main.css')")
50+
51+
// or with ES6
52+
%%raw("import './styles/main.css'")
53+
```
54+
55+
## CSS Modules
56+
57+
[CSS modules](https://github.com/css-modules/css-modules) can be imported like any other JS module. The imported value is a JS object, with attributes equivalent to each classname defined in the CSS file.
58+
59+
As an example, let's say we have a CSS module like this:
60+
61+
```css
62+
/* styles.module.css */
63+
64+
.root {
65+
color: red
66+
}
67+
```
68+
69+
We now need to create a module binding that imports our styles as a JS object:
70+
71+
```res
72+
// {..} means we are handling a JS object with an unknown
73+
// set of attributes
74+
@module external styles: {..} = "./styles.module.css"
75+
76+
// Use the obj["key"] syntax to access any classname within our object
77+
let app = <div className={styles["root"]} />
78+
```
79+
80+
**Note:** `{..}` is an open [JS object type](/docs/manual/latest/object#type-declaration), which means the type checker will not type check correct classname usage. If you want to enforce compiler errors, replace `{..}` with a concrete JS object type, such as `{"root": string}`.
81+
82+
83+
## CSS Utility Libraries
84+
85+
### Tailwind
86+
87+
CSS utility libraries like [TailwindCSS](https://tailwindcss.com) usually require some globally imported CSS.
88+
89+
First, create your TailwindCSS main entrypoint file:
90+
91+
```css
92+
/* main.css */
93+
94+
@tailwind base;
95+
@tailwind components;
96+
@tailwind utilities;
97+
```
98+
99+
Then, import your `main.css` file in your ReScript / React application:
100+
101+
```res
102+
// src/App.res
103+
104+
%%raw("import './main.css'")
105+
```
106+
107+
Utilize ReScript's pattern matching and string interpolations to combine different classnames:
108+
109+
```res
110+
@react.component
111+
let make = (~active: bool) => {
112+
let activeClass = if active {
113+
"text-green-600"
114+
}
115+
else {
116+
"text-red-600"
117+
}
118+
119+
<div className={`border-1 border-black ${activeClass}`}>
120+
{React.string("Hello World")}
121+
</div>
122+
}
123+
```
124+
125+
When using the [Tailwind VSCode plugin](https://tailwindcss.com/docs/intellisense), make sure to include ReScript as a target language to get autocompletion:
126+
127+
- Open your VSCode settings
128+
- Search for `Tailwind CSS: Include Languages`
129+
- Add a new item with following settings:
130+
- Item: `rescript`
131+
- Value: `html`
132+
133+
134+
> **Hint:** `rescript-lang.org` actually uses TailwindCSS under the hood! Check out our [codebase](https://github.com/rescript-association/rescript-lang.org) to get some more inspiration on usage patterns.
135+
136+
137+
## CSS-in-JS
138+
139+
There's no way we could recommend a definitive CSS-in-JS workflow, since there are many different approaches on how to bind to CSS-in-JS libraries (going from simple to very advanced).
140+
141+
For demonstration purposes, let's create some simple bindings to e.g. [`emotion`](https://emotion.sh/docs/introduction) (as described [here](https://github.com/bloodyowl/rescript-react-starter-kit/blob/eca7055c59ba578b2d1994fc928d8f541a423e74/src/shared/Emotion.res)):
142+
143+
```res
144+
// src/Emotion.res
145+
146+
@module("@emotion/css") external css: {..} => string = "css"
147+
@module("@emotion/css") external rawCss: string => string = "css"
148+
@module("@emotion/css") external keyframes: {..} => string = "css"
149+
@module("@emotion/css") external cx: array<string> => string = "cx"
150+
151+
@module("@emotion/css") external injectGlobal: string => unit = "injectGlobal"
152+
```
153+
154+
This will give you straight-forward access to `emotion`'s apis. Here's how you'd use them in your app code:
155+
156+
```res
157+
let container = Emotion.css({
158+
"color": "#fff",
159+
"backgroundColor": "red"
160+
})
161+
162+
let app = <div className={container} />
163+
```
164+
165+
You can also use submodules to organize your styles more easily:
166+
167+
```res
168+
module Styles = {
169+
open Emotion
170+
let container = css({
171+
"color": "#fff",
172+
"backgroundColor": "red"
173+
})
174+
// your other declarations
175+
}
176+
177+
let app = <div className={Styles.container} />
178+
```
179+
180+
Please note that this approach will not check for invalid css attribute names. If you e.g. want to make sure that only valid CSS attributes are being passed, you could define your `css` function like this as well:
181+
182+
```res
183+
@module("@emotion/css") external css: React.Style.t => string = "css"
184+
185+
// Usage is slightly different (and probably less ergonomic)
186+
let container = ReactDOM.Style.make(~padding="20px", ())->css;
187+
188+
let app = <div
189+
className={container}
190+
/>
191+
```
192+
193+
Here we used the already existing `React.Style.t` type to enforce valid CSS attribute names.
194+
Last but not least, you can also bind to functions that let you use raw CSS directly:
195+
196+
```res
197+
let container = Emotion.rawCss(`
198+
color: #fff;
199+
background-color: red;
200+
`)
201+
202+
let app = <div className={container} />
203+
```
204+
205+
Please keep in mind that there's a spectrum on how type-safe an API can be (while being more / less complex to handle), so choose a solution that fits to your team's needs.

src/common/App.js

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/common/App.res

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
%%raw(`
1010
let hljs = require('highlight.js/lib/core');
1111
let js = require('highlight.js/lib/languages/javascript');
12+
let css = require('highlight.js/lib/languages/css');
1213
let ocaml = require('highlight.js/lib/languages/ocaml');
1314
let reason = require('plugins/reason-highlightjs');
1415
let rescript = require('plugins/rescript-highlightjs');
@@ -21,6 +22,7 @@
2122
hljs.registerLanguage('reason', reason);
2223
hljs.registerLanguage('rescript', rescript);
2324
hljs.registerLanguage('javascript', js);
25+
hljs.registerLanguage('css', css);
2426
hljs.registerLanguage('ts', js);
2527
hljs.registerLanguage('ocaml', ocaml);
2628
hljs.registerLanguage('sh', bash);

0 commit comments

Comments
 (0)