Skip to main content

Customization

Ancestor take advantage of Module Functors for customization of breakpoints, spacing, radius, etc.

Default setup

The customization interface has the following type signature:

Ancestor_Config.res
module type T = {
type breakpoints<'value>
type spacing
type radius
type colors
type zIndex

let spacing: spacing => Ancestor_Css.Length.t
let radius: radius => Ancestor_Css.Length.t
let unboxBreakpointValue: breakpoints<'value> => 'value
let sizeByBreakpoints: breakpoints<'value> => int
let css: string => string
}

And the default setup has the following values and types:

module DefaultConfig = {
type breakpoints<'a> = [#xs('a) | #sm('a) | #md('a) | #lg('a) | #xl('a)]
let sizeByBreakpoints = values =>
switch values {
| #xs(_) => 0
| #sm(_) => 475
| #md(_) => 920
| #lg(_) => 1280
| #xl(_) => 1920
}

let unboxBreakpointValue = values =>
switch values {
| #xs(v) => v
| #sm(v) => v
| #md(v) => v
| #lg(v) => v
| #xl(v) => v
}

type colors = Ancestor_Css.Color.t
let colors = v => v

type spacing = int
let spacing = spacing => #px(spacing * 8)

type radius = int
let radius = radius => #px(radius * 8)

type zIndex = int
let zIndex = v => v

let css = Ancestor_Emotion.css
}

Breakpoints

Default breakpoints

Ancestor's breakpoints are customizable. The default setup has the following values:

  • xs 0px → 475px
  • sm 475px → 920px
  • md 920px → 1280px
  • lg 1280px → 1440px
  • xl 1440px

Custom breakpoints

If you wish, you can customize only the breakpoints by overriding all types and values from the default setup:

 module AncestorCustom = Ancestor.Make({
open Ancestor.DefaultConfig

type breakpoints<'value> = [
| #small('value)
| #medium('value)
| #large('value)
]

let sizeByBreakpoints = values =>
switch values {
| #small(_) => 0
| #medium(_) => 600
| #large(_) => 1280
}

let unboxBreakpointValue = values =>
switch values {
| #small(v) => v
| #medium(v) => v
| #large(v) => v
}

type spacing = spacing
let spacing = spacing

type radius = radius
let radius = radius

type colors = colors
let colors = colors

type zIndex = zIndex
let zIndex = zIndex

let css = Ancestor.DefaultConfig.css
})

module App = {
open AncestorCustom

@react.component
let make = () => {
<Grid>
<Box columns=[#small(#12), #medium(#6)]> {"Your components here..."->React.string} </Box>
</Grid>
}
}

Beyond the type definition, you need to define two functions:

sizeByBreakpoints

  • Type: let sizeByBreakpoints: breakpoints<'value> => int
  • Description: A function that receives a breakpoint<'value> and returns an integer (the breakpoint value in px).

unboxBreakpointValue

  • Type: let unboxBreakpointValue: breakpoints<'value> => 'value
  • Description: A function that receives a breakpoint<'value>, "unbox" and returns its value.
info

All Ancestor's components properties are an array of breakpoints. If you want a property with the same value in all breakpoints, you need to provide the value for the lowest breakpoint.

tip

If you wish, you can create "aliases functions" to replace the variants that you defined in your custom setup. Instead of write display=[#xs(#flex)] you can write display=[xs(#flex)]. In some cases, it improves the code readability.

Spacing

The spacing api is fully customizable. By default, Ancestor uses int and a scale factor of 8px to keep the spacing consistent between the elements. You can customize the scale factor by providing a new value for the spacing function:

 module AncestorCustom = Ancestor.Make({
include Ancestor.DefaultConfig

let spacing = v => #px(v * 4)
})

Customizing the type of spacing props

You can also customize the type of the spacing properties. This feature is very useful when you need to use scale values like 1.25, 2.5, etc. Let's see how to use float instead of int:

 module AncestorCustom = Ancestor.Make({
open Ancestor.DefaultConfig

type breakpoints<'value> = breakpoints<'value>
let sizeByBreakpoints = sizeByBreakpoints
let unboxBreakpointValue = unboxBreakpointValue

type spacing = float
let spacing = v => #pxFloat(v *. 8.0)

type radius = radius
let radius = radius

type colors = colors
let colors = colors

type zIndex = zIndex
let zIndex = zIndex

let css = Ancestor.DefaultConfig.css
})

@react.component
let make = () => {
open AncestorCustom

<Box m=[#md(2.25)]> <Box p=[#xs(4.0), #md(3.0)] /> </Box>
}

Using design tokens

We can also define a set of spacing tokens using polymorphic variants. Sometimes the design team doesn't use scale values like 1 or 1.5, but they define a set of design tokens that represents a value in px. Let's see how to do this with Ancestor:

 module AncestorCustom = Ancestor.Make({
open Ancestor.DefaultConfig

type breakpoints<'value> = breakpoints<'value>
let sizeByBreakpoints = sizeByBreakpoints
let unboxBreakpointValue = unboxBreakpointValue

type spacing = [#xs | #md | #lg]
let spacing = v =>
switch v {
| #xs => #px(8)
| #md => #px(16)
| #lg => #px(24)
}

type radius = radius
let radius = radius

type colors = colors
let colors = colors

type zIndex = zIndex
let zIndex = zIndex

let css = Ancestor.DefaultConfig.css
})

@react.component
let make = () => {
open AncestorCustom

<Box m=[#md(#lg)]> <Box p=[#xs(#md), #md(#lg)] /> </Box>
}

Using CSS units

Sometimes, you just want to use CSS units like rem or px:

 module AncestorCustom = Ancestor.Make({
open Ancestor.DefaultConfig

type breakpoints<'value> = breakpoints<'value>
let unboxBreakpointValue = Ancestor.DefaultConfig.unboxBreakpointValue
let sizeByBreakpoints = Ancestor.DefaultConfig.sizeByBreakpoints
let css = Ancestor.DefaultConfig.css

type zIndex = zIndex
let zIndex = zIndex

type colors = colors
let colors = colors

type radius = radius
let radius = radius

type spacing = Ancestor_Css.Length.t
let spacing = v => v
})

@react.component
let make = () => {
open AncestorCustom

<Box m=[#xs(24->#px)]> <Box p=[#xs(32->#px)] /> </Box>
}

Border Radius

All of those customizations above, also works for the radius. You need just to replace the spacing type and value by radius. Let's see:

 module AncestorCustom = Ancestor.Make({
open Ancestor.DefaultConfig

type breakpoints<'value> = breakpoints<'value>
let unboxBreakpointValue = Ancestor.DefaultConfig.unboxBreakpointValue
let sizeByBreakpoints = Ancestor.DefaultConfig.sizeByBreakpoints
let css = Ancestor.DefaultConfig.css

type spacing = spacing
let spacing = spacing

type zIndex = zIndex
let zIndex = zIndex

type colors = colors
let colors = colors

type radius = Ancestor_Css.Length.t
let radius = v => v
})

@react.component
let make = () => {
open AncestorCustom

<Box borderRadius=[#xs(24->#px)]> <Box borderRadius=[#xs(32->#px)] /> </Box>
}

Colors

By default, Ancestor uses Ancestor_Css.Color.t as the type definition for the colors, which means that you're able to use css colors like #hex(...) or rgb(...). However, sometimes you have a well defined set of colors that you're going to use in your components. Let's see how to combine Ancestor and polyvariants to create type safe custom design tokens:

 module AncestorCustom = Ancestor.Make({
open Ancestor.DefaultConfig

type breakpoints<'value> = breakpoints<'value>
let unboxBreakpointValue = unboxBreakpointValue
let sizeByBreakpoints = sizeByBreakpoints
let css = css

type spacing = spacing
let spacing = spacing

type zIndex = zIndex
let zIndex = zIndex

type colors = [
| #primary100
| #secondary100
| #background
]

let colors = token =>
switch token {
| #primary100 => #hex("#000")
| #secondary100 => #hex("#cecece")
| #background => #hex("#fafafa")
}

type radius = radius
let radius = radius
})

@react.component
let make = () => {
open AncestorCustom

<Box bgColor=[#xs(#background)]>
<Typography color=[#xs(#primary100), #md(#secondary100)]>
{"Your text here..."->React.string}
</Typography>
</Box>
}

ZIndex

By default, Ancestor uses int as the type definition for the z-index. Managing z-index might become difficult sometimes. Here's an example of how combine Ancestor and polyvariants to create type safe tokens for zIndex:

 module AncestorCustom = Ancestor.Make({
open Ancestor.DefaultConfig

type breakpoints<'value> = breakpoints<'value>
let unboxBreakpointValue = unboxBreakpointValue
let sizeByBreakpoints = sizeByBreakpoints
let css = css

type spacing = spacing
let spacing = spacing

type zIndex = [
| #base
| #above
| #aboveAll
]

let zIndex = token =>
switch token {
| #base => 5
| #above => 15
| #aboveAll => 20
}

type colors = colors
let colors = colors

type radius = radius
let radius = radius
})

@react.component
let make = () => {
open AncestorCustom

<Box position=[#xs(#relative)]>
<Box zIndex=[#xs(#base)] position=[#xs(#absolute)] />
<Box zIndex=[#xs(#above)] position=[#xs(#absolute)] />
</Box>
}

CSS in JS

To generate styles Ancestor uses @emotion/css. If you wish, you can use another CSS in JS library that provides an equivalent function, like Goober or styled-components.


module Goober = {
@module("goober") external css: string => string = "css"
}

module AncestorCustom = Ancestor.Make({
include Ancestor.DefaultConfig

let css = Goober.css
})