|
| 1 | +--- |
| 2 | +title: Styling |
| 3 | +description: "Styling in ReScript & React" |
| 4 | +canonical: "/docs/react/latest/styling" |
| 5 | +--- |
| 6 | + |
| 7 | +# Styling |
| 8 | + |
| 9 | +There are many different approaches on how to do styling in React, such as: |
| 10 | + |
| 11 | +- Inline Styles (`style=...`) |
| 12 | +- Global CSS / CSS modules |
| 13 | +- CSS utility libraries (`tailwindcss`, `tachyons`, etc.) |
| 14 | +- CSS-in-JS (such as `styled-components`, `emotion`, etc.) |
| 15 | + |
| 16 | +## Inline Styles |
| 17 | + |
| 18 | +You can apply a `style` attribute to any DOM element with our `ReactDOM.Style.make` API: |
| 19 | + |
| 20 | +```rescript |
| 21 | +<div style=( |
| 22 | + ReactDOM.Style.make(~color="#444444", ~fontSize="68px", ()) |
| 23 | +)/> |
| 24 | +``` |
| 25 | + |
| 26 | +It's a labeled (typed!) function call that maps to the familiar style object `{color: '#444444', fontSize: '68px'}`. |
| 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 | +```reason |
| 35 | +let myStyle = ReactDOM.Style.make(~color="#444444", ~fontSize="68px", ()); |
| 36 | +
|
| 37 | +## Global CSS |
| 38 | +
|
| 39 | +Use a `%%raw()` expression to import CSS files within your ReScript / React component code: |
| 40 | +
|
| 41 | +```rescript |
| 42 | +%%raw("require('./styles/main.css')") |
| 43 | +
|
| 44 | +// or with ES6 |
| 45 | +%%raw("import './styles/main.css'") |
| 46 | +``` |
| 47 | + |
| 48 | +## CSS Modules |
| 49 | + |
| 50 | +CSS modules can be imported like any other JS module. The imported value is a JS object, with each key mapping to a classname within the imported CSS file. |
| 51 | + |
| 52 | +As an example, let's say we have a CSS module like this: |
| 53 | + |
| 54 | +```css |
| 55 | +/* styles.module.css */ |
| 56 | + |
| 57 | +.root { |
| 58 | + color: red |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +We now need to create a module binding that imports our styles: |
| 63 | + |
| 64 | +```res |
| 65 | +// {..} means we are handling a JS object with an unknown |
| 66 | +// set of attributes |
| 67 | +@module external styles: {..} = "./styles.module.css" |
| 68 | +
|
| 69 | +let app = <div className={styles["root"]} /> |
| 70 | +``` |
| 71 | + |
| 72 | +**Note:** `{..}` is an open JS object type, 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}`. |
| 73 | + |
| 74 | + |
| 75 | +## CSS Utility Libraries |
| 76 | + |
| 77 | +### Tailwind |
| 78 | + |
| 79 | + |
| 80 | +CSS utility libraries like [TailwindCSS](https://tailwindcss.com) usually require some globally imported CSS. |
| 81 | + |
| 82 | +First, create your TailwindCSS main entrypoint file: |
| 83 | + |
| 84 | +```css |
| 85 | +/* main.css */ |
| 86 | + |
| 87 | +@tailwind base; |
| 88 | +@tailwind components; |
| 89 | +@tailwind utilities; |
| 90 | +``` |
| 91 | + |
| 92 | +Then, import your `main.css` file in your ReScript / React application: |
| 93 | + |
| 94 | +```res |
| 95 | +// src/App.res |
| 96 | +
|
| 97 | +%%raw("import './main.css'") |
| 98 | +``` |
| 99 | + |
| 100 | +Utilize ReScript's pattern matching and string interpolations to combine different classnames: |
| 101 | + |
| 102 | +```res |
| 103 | +@react.component |
| 104 | +let make = (~active: bool) => { |
| 105 | + let activeClass = if active { |
| 106 | + "text-green-600" |
| 107 | + } |
| 108 | + else { |
| 109 | + "text-red-600" |
| 110 | + } |
| 111 | +
|
| 112 | + <div className={`border-1 border-black ${activeClass}`}> |
| 113 | + {React.string("Hello World")} |
| 114 | + </div> |
| 115 | +} |
| 116 | +``` |
| 117 | + |
| 118 | +When using the [Tailwind VSCode plugin](https://tailwindcss.com/docs/intellisense), make sure to include ReScript as a target language to get autocompletion: |
| 119 | + |
| 120 | +- Open the VSCode settings |
| 121 | +- Search for `Tailwind CSS: Include Languages` |
| 122 | +- Add a new item with following settings: |
| 123 | + - Item: `rescript` |
| 124 | + - Value: `html` |
| 125 | + |
| 126 | + |
| 127 | +> **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. |
| 128 | +
|
| 129 | + |
| 130 | +## CSS-in-JS |
| 131 | + |
| 132 | +Currently there are no official recommendations for CSS-in-JS yet due to the wildly different approaches on how to bind to CSS-in-JS (going from simple to very advanced). |
| 133 | + |
| 134 | +The most minimalistic approach is to create 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)): |
| 135 | + |
| 136 | +```res |
| 137 | +// src/Emotion.res |
| 138 | +
|
| 139 | +@module("@emotion/css") external css: {..} => string = "css" |
| 140 | +@module("@emotion/css") external keyframes: {..} => string = "css" |
| 141 | +@module("@emotion/css") external cx: array<string> => string = "cx" |
| 142 | +
|
| 143 | +@module("@emotion/css") external injectGlobal: string => unit = "injectGlobal" |
| 144 | +``` |
| 145 | + |
| 146 | +This will give you straight-forward access to the `emotion` apis. Here's how you'd use them in your app code: |
| 147 | + |
| 148 | +```res |
| 149 | +let app = <div |
| 150 | + className={Emotion.css({ |
| 151 | + "color": "#fff", |
| 152 | + "backgroundColor": "red" |
| 153 | + })} |
| 154 | +/> |
| 155 | +``` |
| 156 | + |
| 157 | +Please note that this approach will not be type-checking for valid 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: |
| 158 | + |
| 159 | +```res |
| 160 | +@module("@emotion/css") external css: React.Style.t => string = "css" |
| 161 | +
|
| 162 | +// Usage is slightly different (and probably less ergonomic) |
| 163 | +let app = <div |
| 164 | + className={ReactDOM.Style.make(~padding="20px", ())->css} |
| 165 | +/> |
| 166 | +``` |
| 167 | + |
| 168 | +In the example above we used the already existing `React.Style.t` type to enforce valid CSS attribute names. There's a spectrum on how type-safe an API might be, so choose a solution that fits your team's needs. |
| 169 | + |
| 170 | +For more CSS-in-JS ideas, you can also check out related discussions on our [forum](https://forum.rescript-lang.org), or start a new topic. |
0 commit comments