Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building a Theming System with React

Building a Theming System with React

In my career I've created several component libraries, and 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.

Matteo Ronchi

March 24, 2017
Tweet

More Decks by Matteo Ronchi

Other Decks in Programming

Transcript

  1. BUILDING A THEMING
    SYSTEM WITH REACT

    View full-size slide

  2. MATTEO RONCHI
    @CEF62 GITHUB/CEF62
    SENIOR SOFTWARE ENGINEER AT WORKWAVE
    FEVR COMMUNITY LEADER

    View full-size slide

  3. TODAY'S WEB IS ALL ABOUT
    COMPONENT-BASED
    ARCHITECTURES

    View full-size slide

  4. WHY COMPONENTS?

    View full-size slide

  5. > Modular
    > Scalable
    > Extensible
    > Reusable
    > Encapsulated

    View full-size slide

  6. COMPONENTIZATION ENABLES
    HIGHER ORDER COMPONENTS

    View full-size slide

  7. HOCS ALLOW ABSTRACTION OF
    REUSABLE AUGMENTATIONS

    View full-size slide

  8. MANY WAYS TO
    STYLE COMPONENTS

    View full-size slide

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

    View full-size slide

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

    Red

    View full-size slide

  11. Inline Styles

    View full-size slide

  12. 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

    View full-size slide

  13. Runtime Generated CSS

    View full-size slide

  14. 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

    View full-size slide

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

    View full-size slide

  16. COMPONENTS THEMING

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  19. COLOR PALETTES
    THE SIMPLEST WAY TO KEEP CONSISTENCY ACROSS VIEWS AND COMPONENTS

    View full-size slide

  20. @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;
    }

    View full-size slide

  21. PROPS DRIVEN STYLES
    GREATER CUSTOMIZATION AND EASE TO CREATE DYNAMIC STYLES DEFINITION

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  26. WORKWAVE THEME-MANAGER

    View full-size slide

  27. The Theme-Manager 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

    View full-size slide

  28. 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

    View full-size slide

  29. 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)

    View full-size slide

  30. 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: {
    normal: { color: 'white' },
    confirm: { color: 'yellow' },
    },
    backgrounds: {
    normal: { backgroundColor: 'violet' },
    confirm: { backgroundColor: 'darkgreen' },
    },
    }.
    },
    }
    const appTheme = theme(source)

    View full-size slide

  31. 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.

    View full-size slide

  32. REACT BINDINGS

    View full-size slide

  33. Makes available a theme-Manager instance
    to every descendant
    A theme-Manager instance can also be passed
    as a prop to every themed component

    View full-size slide

  34. connectTheme()

    View full-size slide

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

    View full-size slide

  36. A themed component declares which
    theme categories it's interested in,
    and a map-Theme-To-Props function
    const config = { types: ['Header'] }
    const mapThemeToProps = ({ Header: { wrapper, label } }) => ({ wrapper, label })
    const themedHeader = connectTheme(config, mapThemeToProps)(Header)

    View full-size slide

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

    {title}

    View full-size slide

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

    View full-size slide

  39. 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?

    View full-size slide

  40. 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?

    View full-size slide

  41. The decorated component's interaction with the theme
    is customizable using the theme-Params prop

    View full-size slide

  42. preset: a theme preset to be applied to
    the component branch.
    theme-Name: changes the active theme
    for the component branch.
    overrides: a local overrides to the style object
    passed to the map-Theme-To-Props.

    View full-size slide

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

    View full-size slide

  44. PLUGIN BASED
    OUTPUT ENGINE

    View full-size slide

  45. The map of styles returned by
    map-Theme-To-Props is passed through a compiler
    We use aphrodite to generate CSS but the engine
    is configurable on per-component basis

    View full-size slide

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

    View full-size slide

  47. 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

    View full-size slide

  48. mronchi at workwave dot com

    View full-size slide

  49. THANKS!
    @CEF62

    View full-size slide