Customization
Ancestor take advantage of Module Functors for customization of breakpoints, spacing, radius, etc.
Default setup
The customization interface has the following type signature:
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 → 475pxsm
475px → 920pxmd
920px → 1280pxlg
1280px → 1440pxxl
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 inpx
).
unboxBreakpointValue
- Type:
let unboxBreakpointValue: breakpoints<'value> => 'value
- Description: A function that receives a
breakpoint<'value>
, "unbox" and returns its value.
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.
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
})