Design system in ReasonML

Rita Krutikova

Agenda

  • Why bother?
  • Design system
  • Design tokens
  • Polymorphic variants
  • Typography & Spacing
  • CSS-in-ReasonML
  • Components
  • ​Demo

Disclaimer

  • I am not a designer
  • I haven't studied design
  • But I have read a lot! 😉
  • Struggled a lot ... 😅
  • Created ugly stuff 💩

Correct me if I say something weird!

Why bother?...

  • Excitement! Fun idea, let's implement!

  • Setup, modelling data, api

  • Styling... Design... CSS... OH NO

Stages of a side project

Why bother?...

  • Font types
  • Font sizes
  • Spacing
  • Colors

Comic Sans, Times New Roman, Arial

3px, 15px, 23rem

red, green, black

  • Too many choices!

  • Too difficult to make a decision

  • No progress

  • CSS is not Reason 😨 

Goal

  • Not a flashy UI

  • Consistent and structured UI

  • Define base values - derive rest

  • Develop faster

  • Nice DX and fun coding (CSS-in-Reason!)

  • Safe refactoring with compiler help

 

Design system

  • Predefined styling values (design tokens)
    • Typography
    • Colors
    • Spacing
  • Reusable components built with design tokens
    • Button
    • Text field
    • Heading

 

Design tokens

  • Design system skeleton
  • Low level styling values
    • Colour - body background, body text, primary
    • Spacing - small, medium, large
    • Font family - body, heading
  • Components depend on them

Polymorphic variants! 😎

Normal variant

module Tokens = {

  type brandColor = Primary | Secondary;

  type textColor = Light | Dark;

  let colorToString =
    fun
    | Primary => "blue"
    | Secondary => "darkgray"
    | Light => "gray"
    | Dark => "black";
}

Normal variant

 module Tokens = {
  type space = Xs | Sm;
  type fontSize = Sm | Body;

  let spaceToPx = 
    fun 
    | Xs => 8
    | Sm => 16;
}
let spacePx = Tokens.spaceToPx(Tokens.Sm);

Normal variant

  • Constructors are tied to their type
  • Can't have a function that accepts a mix of constructors
  • Shouldn't have the same constructor within a scope multiple times
  • Constructors are tied to the scope of their module
  • Need to open the module or reference it to use constructors outside

 

Polymorphic variant

type brandColor = [ | `Primary | `Secondary];

type textColor = [ | `Light | `Dark];

let colorToString =
  fun
  | `Primary => "blue"
  | `Secondary => "darkgray"
  | `Light => "gray"
  | `Dark => "black";

Polymorphic variant

  type space = [ | `xs | `sm];
  
  type fontSize = [ | `sm | `base];

  let spaceToPx =
    fun
    | `xs => 8
    | `sm => 16;
let spacePx = Tokens.spaceToPx(`sm);

Polymorphic variant

  • Constructors exist on their own (not tied to a type)
  • Can use multiple constructors within the scope
  • No need to reference the module
  • Conciseness and convenience of use

  • Same constructors across types are allowed

  • Mix constructors 

Design tokens 

  type color = [
    | `Primary
    | `Error
    | `BodyBg
    | `BodyText
  ];

  type fontSize = [ | `sm | `base | `md | `lg];

  type spacing = [ | `xs | `sm | `md | `lg ];
  
  // line heights, transitions, shadows etc.

Token values

  let color =
    fun 
    | `Primary => "#1f3a48"
    | `Error => "#f44336"; // rest

  let fontSize = 
    fun 
    | `sm => 12
    | `base => 16; // rest

  let spacing =
    fun 
    | `xs => 4
    | `sm => 8
    | `md => 16;

Typography

Experimenting

  • Body
  • Heading
    • Font 
    • Line height

Typography


   let baseFontSizePx = 20;
   
   let lineHeight =
    fun
    | `body => 1.4
    | `heading => 1.2;

How do I define other font sizes? 🤔

Modular scale

  • Pick a font scale ratio (e.g. 1.2 or 1.618 etc)
  • Generate font-size values from base font-size and ratio
  fontSize = (ratio ^ factor) * baseFontSize

Modular scale

let baseFontSizePx = 20;

let ratio = 1.25;

let fontScale = factor =>
    ratio ** factor 
    *. (baseFontSizePx |> float_of_int);
    

Font scale

  let fontSize =
    fun
    | `sm => fontScale(-1)   // label
    | `base => fontScale(0)  // body
    | `md => fontScale(1)    // h3
    | `lg => fontScale(2)    // h2
    | `xl => fontScale(3)    // h1
    
    
 let lineHeight =
    fun
    | `body => 1.4
    | `heading => 1.2;
    

Spacing

  • Goal: visual consistency 😍
  • Use spacing scale (similar to font sizes)
  • Base line grid - base for the spacing scale
  • Base line grid is a factor of the base line height
  • Common base line grids: 4px, 6px, 8px

Spacing


   let baseFontSizePx = 20;
   
   let baseLineHeight = 1.4; // 20 * 1.4 = 28px
   
   let grid = 4;
   
   let space =
    fun
    | `xs => grid
    | `sm => grid * 2
    | `md => grid * 4
    | `lg => grid * 6;

CSS-in-Reason

  • bs-css - bindings to emotion
  • super powerful - selectors, media, keyframes etc.
    <div
      className={style([
        color(`hex("111")),
        marginBottom(`px(16)),
        fontSize(`px(16)),
        lineHeight(`abs(1.4)),
      ])}>
      {React.string("Welcome!")}
    </div>

Paragraph

  open Css;

  let make = children => {
    let className =
      style([
        color(`BodyText |> Theme.color),
        marginBottom(`sm |> Theme.space),
        fontSize(`base |> Theme.fontSize),
        lineHeight(`body |> Theme.lineHeight),
      ]);
      
    <p className> children </p>;
  };

Button

open Theme;

let btnStyles = size => {
  let (pd, font) = 
    switch (size) {
    | `Small => (space(`sm), fontSize(`sm))
    | `Medium => (space(`md), fontSize(`base))
    };

  Css.[padding(pd), fontSize(font)];
};
type size = [ | `Small | `Medium];

Button

  [@react.component]
  let make = (~size=`Medium, ~children) => {
    <button className=Css.style(btnStyles(size))> 
    	children 
    </button>;
  };
  
  <Button size=`Small onClick=...>
    {React.string("I am so small...")}
  </Button>

Components

  • TextField
  • Button (`Primary, `Secondary)

  • Heading (`h1, `h2, etc)
  • Paragraph
  • Link
  • Container
  • ...

Summary

  • Define design tokens - polymorphic variants
    • colors, font sizes, spacing etc
  • Set concrete values for design tokens
    • base values (body font size, line height, font scale)
    • derive the rest (font sizes, spacing etc.)
  • Use design tokens in components

Reason all the way!!!

Demo

Acknowledgment

Questions?

Built with

ReasonReact

Hasura GraphQL

Netlify

github.com/margaretkrutikova/re-cite

margaretkrutikova

@rita_krutikova

dev.to/margaretkrutikova

recite.netlify.com

Design system in ReasonML

By margaretkru

Design system in ReasonML

  • 151