Dynamic Styling Systems

with React

@TejasKumar_

ReactJS Developer

@kh_mugh

Design Systems

@kh_mugh

Implementation

@kh_mugh

Component library

Style guide

etc.

@kh_mugh

Theming

@kh_mugh

If it affects color, it’s related to theming. Otherwise, it’s not.

// most VIKING way
<h1 className={props.theme === 'DARK' ? 'DARK-color' : 'LIGHT-color'}>ReactConf Rocks</h1>

// we can do better
<h1 className={`${props.theme}-color`}>ReactConf Rocks</h1>

// maybe this too 🤔
<h1 className={getClassName(props.theme, 'color')}>ReactConf Rocks</h1>


@media (prefers-color-scheme: light) { // light, dark and no-preference
    html {
        // light or dark styles
    }
}

This all works, until we add styling complexity

Component

Themes

Styles

Page

DEMO

Comp Theme

Page Styles

Related only to the component

Only changes color theme

Changes "appearance" of shapes (i.e. borders, shadows, gradients etc.)

Related to the page

Can affect component themes

pageComponents.map((comp, idx) => {
  const Component = COMPONENTS[comp.id]
  return (
    <ThemeProvider
      key={idx}
      theme={{
        palette,
        pageStyleName,
        componentThemeName: comp.themeName
      }}
    >
      <Component data={comp.data} />
    </ThemeProvider>
  )
})
const Container = styled.div`
  background: ${props => {
    if(props.theme.componentThemeName === 'DARK') {
      if(props.theme.pageStyleName === 'ROUND') {
        return `
          ...OVERRIDE SOME STYLES
        `
      }
      return `
        ...DARK STYLES
      `
    }
    ...ALL OTHER THEMES
  }};
`
index.js // interface file
Component.js // actual component UI code
Component.meta.js // meta information about components validations, default props etc.
Component.theme.js // THEMEEEE
Component.styled.js // CSS-in-JS with Theme usage
Component.reducer.js // component reducer
Component.actions.js // component actions
Component.styles.sass // static styles for components

// will be added
Component.spec.js // UI test file
Component.stories.js // storybook file
// Component.theme.js
import { hexToRgb } from '../../core/lib/utils'
export default {
  DARK: {
    border: () => 'none',
    shadow: () => 'none',
    buttonShadow: () => 'none',
    raisedShadow: () => 'none',
    heading: p => p.accentWhite,
    paragraph: p => p.accentWhite,
    background: p => `rgba(${hexToRgb(p.tertiaryDark)}, 1)`,
    lightBackground: p => p.tertiaryExtraLight
  },
  LIGHT: {
    heading: p => p.tertiaryDark,
    paragraph: p => p.tertiaryNormal,
    background: p => `rgba(${hexToRgb(p.tertiaryLight)}, 1)`,
    lightBackground: p => p.tertiaryExtraLight
  },
  EXTRALIGHT: {
    heading: p => p.tertiaryDark,
    paragraph: p => p.tertiaryNormal,
    background: p => `rgba(${hexToRgb(p.tertiaryExtraLight)}, 1)`,
    lightBackground: p => p.tertiaryExtraLight
  }
}
// Component.styled.js
import styled from '@emotion/styled' or 'styled-components' 

import THEME from './Component.theme'
import { styleProvider } from '../../core/lib/utils'

const getStyleForProp = styleProvider(THEME)

const Container = styled.div`
  padding: 80px 0;
  background-color: ${getStyleForProp('background')};
`

const Title = styled.h4`
  color: ${getStyleForProp('heading')};
`

const Description = styled.p`
  color: ${getStyleForProp('paragraph')};
  line-height: 22px;
  text-align: left;
  margin-bottom: 30px;
  @media (max-width: 992px) {
    text-align: center;
  }
`

const Card = styled.div`
  box-shadow: ${getStyleForProp('raisedShadow')};
  border: ${getStyleForProp('border')};
  border-radius: ${getStyleForProp('borderRadius')};
`

Now we have defined theme, time for defining the page styles

Page styles differ from themes with only `keys` of style names

// page.styles.js

export default {
  ROUNDED: {
    borderRadius: () => `10px`,
    cardRadius: () => `10px`,
    ...otherRoundedStyles,
  },
  CORNERED: {
    borderRadius: () => `5px`,
    cardRadius: () => `5px`,
    ...otherCorneredStyles,
  },
  SHARP: {
    border: p => `1px solid ${p.primaryDark}`,
    borderRadius: () => `none`,
    cardRadius: () => `none`,
    ...otherSharpStyles,
  }
}
// Component.styled.js
import PAGE_STYLES from '../page.styles.js'
const getStyleForProp = styleProvider(THEME)

const Container = styled.div`
  background-color: ${getStyleForProp('background')};
`

// styleProvider.js

// themeData - THEME
// styleProp - 'background'
// { componentTheme } - component props which are auto curried

const styleProvider = themeData => styleProp => componentProps => {
  const { theme } = componentProps
  // get selected theme name for component, and selected page style name
  const { componentThemeName, pageStyleName, palette } = theme

  const stylesFromTheme = themeData[componentThemeName] // componentThemeName - DARK, LIGHT or EXTRALIGHT
  const stylesFromPage = PAGE_STYLES[pageStyleName] // pageStyleName - ROUND, CORNDERED or SHARP
 
  const styles = {
    ...stylesFromPage,
    ...stylesFromTheme,
  }

  const styleGetter = styles[styleProp]
  return styleGetter ? styleGetter(palette) : ''
}

The best part is that we can override global page styles from any theme

{
    // from global page styles
    borderRadius: () => `10px`,
    buttonRadius: () => `10px`,
    inputRadius: () => `10px`,
    buttonBackground: (p: Palette) =>
      `linear-gradient(-180deg, ${p.primaryLight} 0%, ${p.primaryDark} 96%)`,
    buttonBackgroundBlue: (p: Palette) =>
      `linear-gradient(-180deg, ${p.secondaryLight} 0%, ${
        p.secondaryDark
      } 96%)`,
    buttonBackgroundWhite: (p: Palette) =>
      `linear-gradient(-180deg, ${p.tertiaryExtraLight} 0%, ${
        p.tertiaryLight
      } 96%)`,
    buttonShadow: p => `0 3px 16px 0 rgba(${hexToRgb(p.primaryDark)}, 0.5)`,
    buttonShadowBlue: p =>
      `0 3px 16px 0 rgba(${hexToRgb(p.secondaryDark)}, 0.5)`,
    shadow: (p: Palette) =>
      `0 3px 10px 0 rgba(${hexToRgb(p.tertiaryDark)},0.08)`,
    raisedShadow: (p: Palette) =>
      `0 9px 22px 0 rgba(${hexToRgb(p.tertiaryDark)},0.12)`,
    
    // from component theme
    shadow: () => 'none',
    buttonShadow: () => 'none',
    raisedShadow: () => 'none',
    heading: p => p.accentWhite,
    paragraph: p => p.accentWhite,
    background: p => `rgba(${hexToRgb(p.tertiaryDark)}, 1)`,
    lightBackground: p => p.tertiaryExtraLight
}

Brent Jackson

@jxnblk

 

Wrap component with according theme context

Declare it's own theme file for every component with styles that will be affected from theme and page styles

Create page styles object with global styles

The main focus was to create a system with minimal and descriptive API's so our UI engineers can work on that without large learning curve

🤔
Alternative ways

Basically, we could have achieved all this with CSS variables

Or when using Emtotion's `css` utility leverage the same thing only with classNames, not components
(theme is visible via callback)

Framework/Library agnostic

Thank you!

@kh_mugh

deck

By Khachatur Mughnetsyan