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
- Font you want to use (https://fonts.google.com/)
- Font size that looks good on body text
- Line height that works (1.2 - 1.5)
- 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?
Design system in ReasonML
By margaretkru
Design system in ReasonML
- 151