Slide 1

Slide 1 text

Building a Theming System With React

Slide 2

Slide 2 text

Matteo Ronchi @cef62 -- github/cef62 Senior Software Engineer at WorkWave

Slide 3

Slide 3 text

Today's Web is all about Component-based Architectures

Slide 4

Slide 4 text

Why Components?

Slide 5

Slide 5 text

- Modular - Scalable - Extensible - Reusable - Encapsulated

Slide 6

Slide 6 text

Componentization enables Higher Order Components

Slide 7

Slide 7 text

HoCs allow abstraction of reusable augmentations

Slide 8

Slide 8 text

Componentization is a seductive software development practice. — Maarten Koning - Sep 29, 2006

Slide 9

Slide 9 text

Many Ways to Style Components

Slide 10

Slide 10 text

PLAIN CSS

Slide 11

Slide 11 text

Web pages load Cascading Style Sheets to learn how to render HTML elements .red-button { color: red; } Red

Slide 12

Slide 12 text

CSS-Modules

Slide 13

Slide 13 text

Allow per-component css rules greatly improving components portability .redButton { color: red; } import css from './styles.css' const comp = () => Red Red

Slide 14

Slide 14 text

Inline Styles

Slide 15

Slide 15 text

Component styling without using CSS Making it really easy to dynamically change the visual output based on a component's state const redButton { color: 'red' } const comp = () => Red

Slide 16

Slide 16 text

Runtime Generated CSS

Slide 17

Slide 17 text

In recent years CSS-in-JS continued to grow and evolve. The current state of the art is expressed by libraries as · Styled Components · Aphrodite · Styletron · Glamour

Slide 18

Slide 18 text

Those libraries dynamically define CSS rules using Javascript, JSON or CSS syntaxes. Generated CSS classes are injected at runtime into the web page.

Slide 19

Slide 19 text

Components Theming

Slide 20

Slide 20 text

In UI development theming is the definition of reusable sets of visual styles, aiming to create consistency across views

Slide 21

Slide 21 text

UI Theming can be done in so many different ways mostly depending on per-project requirements

Slide 22

Slide 22 text

Color Palettes the simplest way to keep consistency across views and components

Slide 23

Slide 23 text

@nice-blue: #5B83AD; @light-blue: #98C7F9; @small: 0.6rem; @big: 1.3rem; #header { color: @light-blue; font-size: @big; } #footer { color: @nice-blue; font-size: @small; }

Slide 24

Slide 24 text

Props Driven Styles greater customization and ease to create dynamic styles definition

Slide 25

Slide 25 text

const theme = { niceBlue: '#5B83AD', lightBlue: '#98C7F9', small: '0.6rem', normal: '1rem', } const bgStyle = (p) => p.isFooter ? p.theme.lightBlue : p.theme.niceBlue const fontStyle = (p) => p.isFooter ? p.theme.small : p.theme.normal // styled-components const Button = styled.div` backgroundColor: ${bgStyle}; border: 2px solid ${fontStyle}; `

Slide 26

Slide 26 text

Color Palettes and Props Driven Styles are great but we - at WorkWave - looked for something different

Slide 27

Slide 27 text

· change the whole theme at runtime · support WYSIWYG · decouple component UI definition from UI styling · easy implementable theme variations

Slide 28

Slide 28 text

· support theme variations per-component branches · be agnostic on how to create the final output · support component-based and atomic-css approaches

Slide 29

Slide 29 text

WorkWave Theme-Manager

Slide 30

Slide 30 text

The ThemeManager is responsible for sharing themes It's possible to subscribe to be notified of theme changes enabling a set of components to update consistently in a easy and transparent way

Slide 31

Slide 31 text

A Theme defines two main fields: styles a map of categories. Each category is a map of css classes in Javascript notation presets a map of presets, each preset is used to programmatically override the main theme definition

Slide 32

Slide 32 text

const source = { // Main theme styles: { Button: { button: { backgroundColor: 'lightpink', color: 'blue', padding: '0.6em', fontWeight: 800, }, }, }, presets: { // dark style variation dark: { Button: { button: { backgroundColor: 'violet', color: 'white', }, }, }, }, } const appTheme = theme(source)

Slide 33

Slide 33 text

const source = { // Main theme styles: { colors: { normal: { color: 'blue' }, confirm: { color: 'red' }, }, backgrounds: { normal: { backgroundColor: 'lightpink' }, confirm: { backgroundColor: 'lightgreen' }, }, }, presets: { // dark style variation dark: { colors: { confirm: { color: 'yellow' }, }, backgrounds: { confirm: { backgroundColor: 'darkgreen' }, }, }. }, } const appTheme = theme(source)

Slide 34

Slide 34 text

A Themed Component expects to receive a map of css classes as a prop knowing how to apply those css classes to its internal implementation

Slide 35

Slide 35 text

React Bindings

Slide 36

Slide 36 text

Slide 37

Slide 37 text

Makes available a themeManager instance to every descendant A themeManager instance can also be passed as a prop to every themed component

Slide 38

Slide 38 text

connectTheme()

Slide 39

Slide 39 text

A HoC connecting a React component to a themeManager instance connectTheme(config, mapThemeToProps, options)(DecoratedComponent)

Slide 40

Slide 40 text

A themed component declares which theme categories it's interested in, and a mapThemeToProps function const config = { types: ['Header'] } const mapThemeToProps = ({ Header: { wrapper, label } }) => ({ wrapper, label }) const ThemedHeader = connectTheme(config, mapThemeToProps)(Header)

Slide 41

Slide 41 text

The decorated component will receive a css prop containing all generated css classes const Header = ({ css, title }) =>
{title}

Slide 42

Slide 42 text

mapThemeToProps is where you are in control mapThemeToProps(theme, props) -> composed styles

Slide 43

Slide 43 text

const mapThemeToProps = (theme) => { const { colors, backgrounds } = theme return { wrapper: { ...backgrounds.normal }, normalBtn: { ...colors.normal, ...backgrounds.normal }, confirmBtn: { ...colors.confirm, ...backgrounds.confirm }, } } const DecoratedComponent = ({ css }) =>
Do Something Are You Sure?

Slide 44

Slide 44 text

const mapThemeToProps = (theme, props) => { const { colors, backgrounds, disabled } = theme const { canConfirm } = props const normalDisabled = canConfirm ? disabled.normal : {} const confirmDisabled = canConfirm ? disabled.normal : {} return { wrapper: { ...backgrounds.normal }, normalBtn: { ...colors.normal, ...backgrounds.normal, ...normalDisabled }, confirmBtn: { ...colors.confirm, ...backgrounds.confirm, ...confirmDisabled }, } } const DecoratedComponent = ({ css }) =>
Do Something Are You Sure?

Slide 45

Slide 45 text

The decorated component's interaction with the theme is customizable using the themeParams property

Slide 46

Slide 46 text

preset: a theme preset to be applied to the component branch themeName: changes the active theme for the component branch overrides: a local overrides to the style object passed to the mapThemeToProps function

Slide 47

Slide 47 text

const themeParams = { preset: 'dark', overrides: { colors: { normal: { color: 'white', fontWeight: 'bold', }, }, backgrounds: { normal: { backgroundColor: 'black', }, }, } } const Comp = () =>

Slide 48

Slide 48 text

Plugin Based Output Engine

Slide 49

Slide 49 text

The map of styles returned by mapThemeToProps is passed through a pluggable compiler The engine is configurable on per-component basis

Slide 50

Slide 50 text

Performances Matters Every themed component uses memoization and strict equality checks to ensure the compiler is invoked as little as possible

Slide 51

Slide 51 text

Room for improvements The code boilerplate required to create reusable variations of a themed component feels excessive Declaration of expected theme categories could be more flexible

Slide 52

Slide 52 text

Thanks! @cef62