Theming. History, reasons, implementation

Introduction. Reasons for the appearance

When the web was just in its infancy, its sole purpose was to host content (hypertext pages) so that users on the World Wide Web had access to it. At that time, there could be no question of design, because why do we need design for pages with scientific publications, unless they will become more useful from this ( first site ). Times are changing and today on the World Wide Web it is not only scientific publications. Blogs, services, social networks and much, much more. Each site needs its own individuality, it needs to interest and attract users. Even scientific sites are gradually realizing this, because most scientists want not only to study certain aspects, but to convey them to people, thereby increasing their popularity and the value of their research (for example, 15 out of 15 scientific siteslist has been redesigned in the last 6 years). Ordinary people are not interested in a gray site with incomprehensible content. Science is becoming more accessible, and sites are being transformed into applications with a convenient and pleasant interface.





Since everyone has their own "convenience" - there is no clear definition and specific rules for the implementation of a service that is convenient for everyone. In recent years, such a concept as Theming has begun to be tied to this concept. It is about him that I want to talk about in this article.





, . , , . , , , . , , Β« Β» Β« Β». , , .





– . . 285 , – 218 [.], 2,2 [.] Β« Β»[.]. . , , . . , – .





.

. , . , , . , , , . , – , . , Β« Β». , – . , , – – , , .. , .





– . – , , , . . , , , .





, . , , . , 5-10 . , . , , . , IE, , ES6. , . , , .





JS , . , HTML CSS. , , . . , , , IE. , . css, 2015 . 2015 – JS, HTTP/2, WebAssembly, ReactJS. , .





css-:





, , 2012 . css – .





:





:root {
  var-header-color: #06c;
}
h1 { background-color: var(header-color); }
      
      



, , . , css- firefox 2015 . , 2016, google safari.





, , :





:root {
  --header-color: #06c;
}
h1 { background-color: var(--header-color); }
      
      



, , 2014 . – .





, . – . 2015 . , – .





, . , – , , . , , , , . , , , – , .





Css . , . stylus, 2011, sass less. , , css. . , js, css. js .





10 , . . HTML5, ES6,7,8,9,10. JS , . – react, vue angular, HTML , js. JS css, , β€œcss in js”, , ( , ). JS , .





, , , – . , – . , .





, , . . , . , , , , – .





– – . - . , ( , , ). , . , , – , . , – Avocode, Zeplin, Figma, Sketch. , , . , , . . – css , , margin- padding-. , . , . , , , , , .





– . , . , . .





. . .









, , – blue200. , , , . , css, , - [.]. , , css , . , , blue200 – - , - - – blue800. primary-color, blue200, blue800, , .





colors: {
  body: '#ECEFF1',
  antiBody: '#263238',
  shared: {
    primary: '#1565C0',
    secondary: '#EF6C00',
    error: '#C62828',
    default: '#9E9E9E',
    disabled: '#E0E0E0',
  },
},
      
      



(, , , ), :





colors: {
  ...
  text: {
    lvl1: '#263238',
    lvl3: '#546E7A',
    lvl5: '#78909C',
    lvl7: '#B0BEC5',
    lvl9: '#ECEFF1',
  },
},
      
      



, 2- .. – .





:





shared-primary-color



,





text-lvl1-color



.





, , ( ) .





, , .





.

, 3 – ( ), β€œcss in js”, . , , IE . 2 – css β€œcss in js”.





:





  1. (, , );





  2. , ( );





  3. ;





  4. ;





  5. ;





  6. .





. , .





– , , , PWA. , . , Β«theme_colorΒ» Β«background_colorΒ». - head .





Theme_color



– . Android. , 67%.





caniuse.com
caniuse.com

Background_color



– , . , :





caniuse.com
caniuse.com

, , , .





caniuse.com
caniuse.com

IE, Safari , , , . , IE Safari (5,87% 3,62% 2020).





.





1. dark light, .





Β« Β».





, .





.theme-light {
  --body-color: #ECEFF1;
  --antiBody-color: #263238;
  --shared-primary-color: #1565C0;
  --shared-secondary-color: #EF6C00;
  --shared-error-color: #C62828;
  --shared-default-color: #9E9E9E;
  --shared-disabled-color: #E0E0E0;
  --text-lvl1-color: #263238;
  --text-lvl3-color: #546E7A;
  --text-lvl5-color: #78909C;
  --text-lvl7-color: #B0BEC5;
  --text-lvl9-color: #ECEFF1;
}

.theme-dark {
	--body-color: #263238;
  --antiBody-color: #ECEFF1;
  --shared-primary-color: #90CAF9;
  --shared-secondary-color: #FFE0B2;
  --shared-error-color: #FFCDD2;
  --shared-default-color: #BDBDBD;
  --shared-disabled-color: #616161;
  --text-lvl1-color: #ECEFF1;
  --text-lvl3-color: #B0BEC5;
  --text-lvl5-color: #78909C;
  --text-lvl7-color: #546E7A;
  --text-lvl9-color: #263238;
}
      
      



, body.





2. , .





– , . , , .





2





2.1) css





, - .theme-auto







media :





@media (prefers-color-scheme: dark) {
	body.theme-auto {
		--background-color: #111;
		--text-color: #f3f3f3;
	}
}
@media (prefers-color-scheme: light) {
	body.theme-auto {
		--background-color: #f3f3f3;
    --text-color: #111;
	}
}
      
      



:













:





  • ( .theme-dark



    .theme-light



    )





  • , -





2.2) js





js – css. , , .





:





if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
	body.classlist.add('theme-dark')
} else {
	body.classlist.add('theme-light')
}
      
      



:





window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
    if (e.matches) {
        body.classlist.remove('theme-light')
        body.classlist.add('theme-dark')
    } else {
        body.classlist.remove('theme-dark')
        body.classlist.add('theme-light')
    }
});
      
      



:









:





  • (head body). .





3.





./button.css







.button {
  color: var(--text-lvl1-color);
  background: var(--shared-default-color);
  ...
  &:disabled {
    background: var(--shared-disabled-color);
  }
}
.button-primary {
	background: var(--shared-primary-color);
}
.button-secondary {
	background: var(--shared-secondary-color)
}
      
      



./appbar.css







.appbar {
	display: flex;
  align-items: center;
  padding: 8px 0;
  color: var(--text-lvl9-color);
  background-color: var(--shared-primary-color);
}
      
      



4.





, . , :





  • , :





body.classlist.remove('theme-light', 'theme-high')
      
      



  • :





body.classlist.add('theme-dark')
      
      



5. .





, . , : theme: 'light' | 'dark' | 'rose'







body. , :





const savedTheme = localStorage.getItem('theme')
if (['light', 'dark', 'rose'].includes(savedTheme)) {
	body.classlist.remove('theme-light', 'theme-dark', 'theme-rose')
	body.classList.add(`theme-${savedTheme}`)
}
      
      



, – , , , .





Css-in-js

, .





React + styled-components + typescript.





1. dark light, .





Β« Β».





, .





, Provider-.





./App.tsx







import { useState } from 'react'
import { ThemeProvider } from 'styled-components'
import themes from './theme'

const App = () => {
	const [theme, setTheme] = useState<'light' | 'dark'>('light')
	const onChangeTheme = (newTheme: 'light' | 'dark') => {
		setTheme(newTheme)
	}
	return (
		<ThemeProvider theme={themes[theme]}>
			// ...
		</ThemeProvide>
	)
}
      
      



2. , .





– , . , , .





:





useEffect(() => {
  if (window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
    onChangeTheme('dark')
  }
}, [])
      
      



:





useEffect(() => {
  window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
    if (e.matches) {
      onChangeTheme('dark')
    } else {
      onChangeTheme('light')
    }
  })
}, [])
      
      



3.





./src/components/atoms/Button/index.tsx



- git





import type { ButtonHTMLAttributes } from 'react'
import styled from 'styled-components'

interface StyledProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  fullWidth?: boolean;
  color?: 'primary' | 'secondary' | 'default'
}

const Button = styled.button<StyledProps>(({ fullWidth, color = 'default', theme }) => `
  color: ${theme.colors.text.lvl9};
  width: ${fullWidth ? '100%' : 'fit-content'};
  ...
  &:not(:disabled) {
    background: ${theme.colors.shared[color]};
    cursor: pointer;
    &:hover {
      opacity: 0.8;
    }
  }
  &:disabled {
    background: ${theme.colors.shared.disabled};
  }
`)

export interface Props extends StyledProps {
  loading?: boolean;
}

export default Button

      
      



./src/components/atoms/AppBar/index.tsx



- git





import styled from 'styled-components'

const AppBar = styled.header(({ theme }) => `
  display: flex;
  align-items: center;
  padding: 8px 0;
  color: ${theme.colors.text.lvl9};
  background-color: ${theme.colors.shared.primary};
`)

export default AppBar
      
      



4.





context api redux/mobx





./App.tsx



- git





import { useState } from 'react'
import { ThemeProvider } from 'styled-components'
import themes from './theme'

const App = () => {
  const [theme, setTheme] = useState<'light' | 'dark'>('light')
  const onChangeTheme = (newTheme: 'light' | 'dark') => {
    setTheme(newTheme)
  }
  return (
    <ThemeProvider theme={themes[theme]}>
    	<ThemeContext.Provider value={{ theme, onChangeTheme }}>
    		...
			</ThemeContext.Provider>
    </ThemeProvide>
	)
}
      
      



.src/components/molecules/Header/index.tsx



- git





import { useContext } from 'react'
import Grid from '../../atoms/Grid'
import Container from '../../atoms/Conrainer'
import Button from '../../atoms/Button'
import AppBar from '../../atoms/AppBar'
import ThemeContext from '../../../contexts/ThemeContext'

const Header: React.FC = () => {
  const { theme, onChangeTheme } = useContext(ThemeContext)
  return (
    <AppBar>
      <Container>
        <Grid container alignItems="center" justify="space-between" gap={1}>
          <h1>
            Themization
          </h1>
          <Button color="secondary" onClick={() => onChangeTheme(theme === 'light' ? 'dark' : 'light')}>
            set theme
          </Button>
        </Grid>
      </Container>
    </AppBar>
  )
}

export default Header

      
      



5. .





, . , : theme: 'light' | 'dark' | 'rose'







. , :





./App.tsx



- git





...

function App() {
  const [theme, setTheme] = useState<'light' | 'dark'>('light')
  const onChangeTheme = (newTheme: 'light' | 'dark') => {
    localStorage.setItem('theme', newTheme)
    setTheme(newTheme)
  }
  useEffect(() => {
    const savedTheme = localStorage?.getItem('theme') as 'light' | 'dark' | null
    if (savedTheme && Object.keys(themes).includes(savedTheme)) setTheme(savedTheme)
    else if (window.matchMedia?.('(prefers-color-scheme: dark)').matches) {
      onChangeTheme('dark')
    }
  }, [])
  useEffect(() => {
    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
      if (e.matches) {
        onChangeTheme('dark')
      } else {
        onChangeTheme('light')
      }
    })
  }, [])
  return (
  	...
  )
}
      
      











– css-in-js ( css ). api , .





, . , , , . , , , .





, . , , . , , -.





Google apple, , . , , github gitlab. , , , – , .








All Articles