Spotted Arrow

2018-10-12

Let’s Build a Simon Game in ReasonReact Pt. 2 Typed CSS

UPDATE 03/22/2020 : This post has been updated to the latest ReasonML and JavaScript ecosystem versions.

The advent of SPAs (single page applications) and CSS-in-JS have transformed how applications are architected and built. Instead of applications being separated in terms of concerns, i.e. HTML, CSS, and JS, they are now separated based on components. A component is a piece of functionality that comes packaged with HTML, CSS, and JS in a single file. This file, or module, can be passed around and reused throughout the application and render without issue within a larger context. These atomic components can be composed together to create larger components and we all know how much we like composition. In this article, you will learn how to add styling in the spirit of CSS-in-JS using typed CSS with the help of bs-css.

One of the main benefits of working within the Reason ecosystem is the FFI (foreign function interface) it uses to bind to other libraries. It makes it easier to create wrappers around common libraries like express, graphql, and even glamorous in comparison to other languages where wrapping may be considered bad practice. The bs-css library is a wrapper around glamor, a CSS-in-JS library that allows you to create reusable components. But in this case, you will also get the benefits of typed CSS, not that plain ole’ CSS where you’re allowed to pass in visibility: show to see what happens 😄.

Write Some CSS 🎨

You are creating the Simon Game so all you need are four boxes. The following are the colors clock-wise from top-left to bottom-left: green, red, blue, yellow. Pretty simple. All in all, you will need some flexboxes and some colors to render. Let’s get started. Above the component let’s create the Style module and open up the helper functions provided by Css.

module Styles = {
  open Css;
}

This will make it easier to utilize the functions you will need instead of prefixing them with the Css namespace.

First, let’s create a style called container which will house your boxes which contain many a box.

module Styles = {
  open Css;
  let container =
    style([
      display(`flex),
      justifyContent(`center),
      alignItems(`center)
    ]);
};

Here you are using the let keyword to declare a variable assignment. It’s assigned to the function style which takes a list of CSS properties and values. Each CSS property is a function which accepts a polymorphic variant type. These polymorphic variant types are nothing special in this context, they are valid values for the corresponding CSS property. If you are using reason along with vscode check out this plugin so you can get amazing editor intelligence.

If you have been following since part 1, then you should see some random numbers on the screen.

Wrap those values with a div whose styles are those for the container value you just created.

<div className=Styles.container>
   /\* Stuff will go here later \*/
</div>,

If you look at your app you should see the content render in the center of the page horizontally but not in the middle of the page vertically. You can change that by making the minimum height of the div to be 100%. How would you do that?

Well, you know you need a function called minHeight to accept a value. Because you are working with a typed language you need to use a function that also declares the measurement. In this case vh will work. The vh stands for view height. Mind you, vh takes a float, not an int.

module Styles = {
  open Css;
  let container =
    style([
      display(`flex),
      justifyContent(`center),
      alignItems(`center),
      minHeight(`vh(100.0)),
    ]);
};

Voilà! The content should now render in the center of the page, both horizontally and vertically.

Write Some More CSS 👨‍🎨

Now that the content is centered let’s create the boxes container. First, create a boxes style which will wrap your many a box. It will declare a display of flex. You will also want the boxes to wrap when there is not enough space for all of them to fit. To make this simpler, let’s go with a defined width and height in pixels of boxes. This will make it easy to fit a box in accordingly.

module Styles = {
  open Css;
  let container =
    style([
      display(`flex),
      justifyContent(`center),
      alignItems(`center),
      minHeight(`vh(100.0)),
    ]);
  let boxes =
    style([
      display(`flex),
      flexWrap(`wrap),
      maxWidth(`px(500)),
      maxHeight(`px(500)),
    ]);
};

That’s too easy 😅.

<div className=Styles.container>
  <div className=Styles.boxes>
    /\* Stuff will go here later \*/
  </div>
</div>,

Next, create the actual box styles. The box style will be different because I want to pass in the color of the box when I declare the styles for className. Because you are using reason, that just means creating the style as a function which accepts a string which will be converted into a hex value. This is how that would look.

let box = color =>
  style([
    backgroundColor(`hex(color)),
    minHeight(`px(250)),
    minWidth(`px(250)),
  ]);

You are making each box 250 pixels so that each box fills up half of the available space. This will push box 3 and 4 down to fill in the next row.

I have some cool colors I am using which I created over at coolors.co. Make your own if you want!

let green = "07f767";
let red = "f95e59";
let blue = "00bcea";
let yellow = "f4ed7c";

Lastly, declare some div‘s and give them their corresponding color.

<div className={Styles.box(green)} />
<div className={Styles.box(red)} />
<div className={Styles.box(blue)} />
<div className={Styles.box(yellow)} />

Refactor 👷‍♀️

I am not satisfied with this solution, though. Instead of passing a reference to a string, let’s create a tagged union of Green, Red, Blue, Yellow. When you pass the tag the function knows how to apply the correct color to the div.

First, declare the tagged union type outside of the Styles module. Chances are you will need this tagged union later.

type bgColor =
  | Green
  | Red
  | Blue
  | Yellow;

Then you can update the box style to accept a bgColor type and change the backgroundColor based on the type provided. Remember to return a style at the end.

let box = (color: bgColor) => {
  let baseStyle = [minHeight(`px(250)), minWidth(`px(250))];

  let bgColor =
    switch (color) {
    | Green => backgroundColor(`hex("07f767"))
    | Red => backgroundColor(`hex("f95e59"))
    | Blue => backgroundColor(`hex("00bcea"))
    | Yellow => backgroundColor(`hex("f4ed7c"))
   };

  style(\[bgColor, ...baseStyle\]);
};

Lastly, pass the tag to the declaration.

<div className={Styles.box(Green)} />
<div className={Styles.box(Red)} />
<div className={Styles.box(Blue)} />
<div className={Styles.box(Yellow)} />

This doesn’t change a whole hell of a lot, maybe it makes the code harder to maintain, but let’s just see where it takes us.

Summary 📝

In this article, you learned how to write type-safe CSS in ReasonReact using bs-css. I think it’s a really awesome experience to write type-safe CSS and see errors at compile time instead of at run-time. It’s also great to get editor intelligence to see what values are valid instead of depending on your memory. RIP visibility: show ✌️. If you liked what I wrote consider buying me a coffee.