Building a Theming System with React (Codemotion Amsterdam 2017)

Building a Theming System with React (Codemotion Amsterdam 2017)

In my career, I've created several component libraries and I've always found it challenging and rewarding at the same time. The engine we developed at WorkWave is the result of such attempts: it offers a great tradeoff between flexibility and ease of use. Styling with inline styles greatly simplifies the developer's work while the use of Aphrodite to output real classes enables the full spectrum of css features. I'll show you the choices we made, what problems have arisen while developing the components and how we solved them, hopefully helping you make better choices for your own libraries.

5171121655bc1a7cc89d16a7a3ce8885?s=128

Matteo Ronchi

May 17, 2017
Tweet

Transcript

  1. Building a Theming System With React

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

  3. Today's Web is all about Component-based Architectures

  4. Why Components?

  5. - Modular - Scalable - Extensible - Reusable - Encapsulated

  6. Componentization enables Higher Order Components

  7. HoCs allow abstraction of reusable augmentations

  8. Componentization is a seductive software development practice. — Maarten Koning

    - Sep 29, 2006
  9. Many Ways to Style Components

  10. PLAIN CSS

  11. Web pages load Cascading Style Sheets to learn how to

    render HTML elements .red-button { color: red; } <button class="red-button">Red</button>
  12. CSS-Modules

  13. Allow per-component css rules greatly improving components portability .redButton {

    color: red; } import css from './styles.css' const comp = () => <button className={css.redButton}>Red</button> <!-- HTML output --> <button class="red_button_7655465hgfhyoda4567">Red</button>
  14. Inline Styles

  15. 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 = () => <button style={redButton}>Red</button>
  16. Runtime Generated CSS

  17. 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
  18. Those libraries dynamically define CSS rules using Javascript, JSON or

    CSS syntaxes. Generated CSS classes are injected at runtime into the web page.
  19. Components Theming

  20. In UI development theming is the definition of reusable sets

    of visual styles, aiming to create consistency across views
  21. UI Theming can be done in so many different ways

    mostly depending on per-project requirements
  22. Color Palettes the simplest way to keep consistency across views

    and components
  23. @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; }
  24. Props Driven Styles greater customization and ease to create dynamic

    styles definition
  25. 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}; `
  26. Color Palettes and Props Driven Styles are great but we

    - at WorkWave - looked for something different
  27. · change the whole theme at runtime · support WYSIWYG

    · decouple component UI definition from UI styling · easy implementable theme variations
  28. · support theme variations per-component branches · be agnostic on

    how to create the final output · support component-based and atomic-css approaches
  29. WorkWave Theme-Manager

  30. 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
  31. 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
  32. 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)
  33. 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)
  34. 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
  35. React Bindings

  36. <ThemeProvider />

  37. Makes available a themeManager instance to every descendant A themeManager

    instance can also be passed as a prop to every themed component
  38. connectTheme()

  39. A HoC connecting a React component to a themeManager instance

    connectTheme(config, mapThemeToProps, options)(DecoratedComponent)
  40. 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)
  41. The decorated component will receive a css prop containing all

    generated css classes const Header = ({ css, title }) => <div className={css.wrapper}> <div className={css.label}>{title}</div> </div>
  42. mapThemeToProps is where you are in control mapThemeToProps(theme, props) ->

    composed styles
  43. const mapThemeToProps = (theme) => { const { colors, backgrounds

    } = theme return { wrapper: { ...backgrounds.normal }, normalBtn: { ...colors.normal, ...backgrounds.normal }, confirmBtn: { ...colors.confirm, ...backgrounds.confirm }, } } const DecoratedComponent = ({ css }) => <div className={css.wrapper}> <button className={css.normalBtn}>Do Something</button> <button className={css.confirmBtn}>Are You Sure?</button> </div>
  44. 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 }) => <div className={css.wrapper}> <button className={css.normalBtn}>Do Something</button> <button className={css.confirmBtn}>Are You Sure?</button> </div>
  45. The decorated component's interaction with the theme is customizable using

    the themeParams property
  46. 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
  47. const themeParams = { preset: 'dark', overrides: { colors: {

    normal: { color: 'white', fontWeight: 'bold', }, }, backgrounds: { normal: { backgroundColor: 'black', }, }, } } const Comp = () => <MyThemedComp themeParams={themeParams} />
  48. Plugin Based Output Engine

  49. The map of styles returned by mapThemeToProps is passed through

    a pluggable compiler The engine is configurable on per-component basis
  50. Performances Matters Every themed component uses memoization and strict equality

    checks to ensure the compiler is invoked as little as possible
  51. 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
  52. Thanks! @cef62