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. Web pages load Cascading Style Sheets to learn how to

    render HTML elements .red-button { color: red; } <button class="red-button">Red</button>
  2. 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>
  3. 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>
  4. 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
  5. Those libraries dynamically define CSS rules using Javascript, JSON or

    CSS syntaxes. Generated CSS classes are injected at runtime into the web page.
  6. In UI development theming is the definition of reusable sets

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

    mostly depending on per-project requirements
  8. @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; }
  9. 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}; `
  10. Color Palettes and Props Driven Styles are great but we

    - at WorkWave - looked for something different
  11. > change the whole theme at runtime > support WYSIWYG

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

    how to create the final output > support component-based and atomic-css approaches
  13. 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
  14. 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
  15. 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)
  16. 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)
  17. 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.
  18. Makes available a theme-Manager instance to every descendant A theme-Manager

    instance can also be passed as a prop to every themed component
  19. A HoC connecting a React component to a themeManager instance

    connectTheme(config, mapThemeToProps, options)(DecoratedComponent)
  20. 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)
  21. 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>
  22. 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>
  23. 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>
  24. 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.
  25. const themeParams = { preset: 'dark', overrides: { colors: {

    normal: { color: 'white', fontWeight: 'bold', }, }, backgrounds: { normal: { backgroundColor: 'black', }, }, } } const Comp = () => <MyThemedComp themeParams={themeParams} />
  26. 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
  27. Performances Matters Every themed component uses memoization and strict equality

    checks to ensure the compiler is invoked as little as possible
  28. 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