This is an example project for you to reference when setting up a new web app using React and/or CSS-in-JS. Alternatively, you can follow the installation steps below on your own project.
This is to help engineers and designers setup style related tools, and follow a frontend convention for the project. This makes the project more maintainable in the future.
To use this project as a starting point for your React web app, simply clone this repo and run:
-
npm install
-
npm start
To setup a new project yourself, follow these instructions:
Emotion is a library designed for writing css styles with JavaScript. Explore the Emotion Docs to learn more.
npm install -S @emotion/core @emotion/styled emotion-theming
Whether it's via SMACCS, ITCSS, RSCSS, Atomic Design or something else, every new project should follow a consistent folder structure.
For this example, we've picked a mix of Atomic Design and ITCSS. Atomic for our component/page scoped styles, and ITCSS for our global styles.
ITCSS suggests a sane way to separate global styles based on specificity, reach, and explicitness. For more info on ITCSS, please read this. We're only going to be using 2 principles of ITCSS, settings
, and elements
since Atomic deals with the rest.
This will the folder where all global style references and declarations live. Eg: global variables, functions, framework styles, etc.
- Start be removing
app.css
andindex.css
imports fromapp.js
andindex.js
, and then delete both CSS files from the root directory. - Create a
styles
folder inside thesrc
directory.
-
Create the
settings
folder inside of oursrc/styles
directory. Thesettings
folder contains fonts, themes, functions related to styles, etc. -
Create a
theme.js
file that lives inside thestyles/settings
folder. -
Create a
utils.js
partial that lives inside thestyles/settings
folder. ITCSS puts this in it's owntools
folder, however we're going to combine that with settings so we can import just 1 file (via anindex.js
file) when we're working at the component level.- Note: if you want to bring in your own font declarations, we recommend creating a
fonts.js
file in this folder, and declaring your font-family there. We also suggest creating a 'font-face' and serving it from the project rather than Google fonts when possible.
- Note: if you want to bring in your own font declarations, we recommend creating a
-
Create an
index.js
file that has the following:
export * from './fonts'
export * from './theme'
export * from './utils'
This lets us easily import anything that lives inside the styles/settings
folder like: import {Theme} from './styles/settings'
-
Create the
elements
folder inside of oursrc/styles
directory. Theelements
folder contains our base elements styles. -
Create a
base.js
file that lives inside thestyles/elements
folder. -
We need to import css from Emotion:
import { css } from '@emotion/core' export const Base = css` body { font-family: PT Sans; font-size: 1rem; font-weight: 400; line-height: 1.5; color: #212529; text-align: left; } `
- Note: Remember that here we only want to add styles on bare HTML elements like
body, h1, p
, etc.
- Note: Remember that here we only want to add styles on bare HTML elements like
-
Create a
global.js
file inside thesrc/styles
folder. This will be imported intoApp.jsx
and will contain all our global styles.import { css } from '@emotion/core' import { Base } from './elements/base' // You can also import your fonts and add it above ${Base} export const GlobalStyles = css` ${Base} `
Reset does exactly what it's name implies. It resets various browser css styles and sets a few styles in order to get your environment to a good starting point.
-
Run
npm install minireset.css
to add Minireset. -
Import Minireset into the previously created
global.js
file.import { css } from '@emotion/core' import 'minireset.css' ...
Normalize makes browsers render all elements more consistently and in line with modern standards.
-
Run
npm install modern-normalize
to add Modern Normalize. -
Import Modern Normalize into the previously created
global.js
file.import { css } from '@emotion/core' import 'minireset.css/minireset.css' import 'modern-normalize/modern-normalize.css' ...
The theme.jsx
file is where you will specify all of your colors, fonts, breakpoints, etc.
-
Start by creating a
Theme
constant.const Theme = {}
-
Add variables as needed to
Theme
.const Theme = { colors: { primary: '#e22d60', secondary: #000000, }, fonts: { primary: 'sans-serif', secondary: 'serif', }, }
-
Next define our breakpoints. We've created a handy function for you to use in other components:
const Xs = '500px' const Sm = '740px' const Md = '900px' const Lg = '1080px' const Xl = '1200px' const Xxl = '1540px' const Theme = { ... breakpoints: [Sm, Md, Lg, Xl, Xxl], ... } const breakpoints = [ { xs: Xs }, { sm: Sm }, { md: Md }, { lg: Lg }, { xl: Xl }, { xxl: Xxl }, ] export const Mq = breakpoints.reduce((acc, breakpoint) => { const entry = Object.entries(breakpoint)[0] acc = { ...acc, [entry[0]]: `@media (min-width: ${entry[1]})` } return acc }, {})
We have to add the breakpoints array to the Theme object in order for Rebass to work.
In order to make the Theme
and GlobalStyles
apply to the entire app you need to import them into our App
.
-
In
App.jsx
import Global from@emotion/core
and importGlobalStyles
from./styles/global
.import React, { Component } from 'react' import { Global } from '@emotion/core' import { GlobalStyles } from './styles/global' class App extends Component { render() { return ( <> <Global styles={GlobalStyles} /> </> ) } } export default App
-
Now import
ThemeProvider
fromemotion-theming
, andTheme
from./styles/settings
. This will allow us to use our theme variables in other components without needing to import theTheme
.import React, { Component } from 'react' import { Global } from '@emotion/core' import { ThemeProvider } from 'emotion-theming' import { Theme } from './styles/settings' import { GlobalStyles } from './styles/global' class App extends Component { render() { return ( <ThemeProvider theme={Theme}> <Global styles={GlobalStyles} /> </ThemeProvider> ) } } export default App
Rebass is a component system that provides some basic UI elements and powerful flexbox based positioning.
-
Run
npm i @rebass/emotion @emotion/core @emotion/styled
to install Rebass.Note: Check out the section below to see some of the ways Rebass can be used or read through the documentation here.
Polished is a toolset of Sass-like functions (eg: lighten, darken, etc.) that are built for CSS-in-JS usage.
-
Run
npm i polished
to install it.Note: Please check out the documentation here if you intend to use Polished.
Follow these instruction to setup and create an atom, molecule, organism, template, or page. You can see a working example of each on src/pages/atomic-page
.
Atomic Design's ideologies categorize styles via atoms
, molecules
, organisms
, templates
, and pages
. For detailed info on what these categorizes really mean, please read this.
-
Create each category folder in the
src
directory (atoms
,molecules
,organisms
,templates
, andpages
). -
Note: Depending on your needs 'templates' and 'pages' can be redundant. If this is the case, remove or do not add templates to your project.
We're going to create a Button
atom that has custom styles using CSS-in-JS.
-
Create a new file called
button.jsx
inside thesrc/atoms
directory.import React from 'react' import styled from '@emotion/styled' const BaseButton = styled.button` padding: 0.6rem 2rem; font-size: 1.2rem; background-color: ${props => props.theme.colors.brandPrimary}; border-color: ${props => props.theme.colors.brandPrimary}; border-radius: 0.25rem; color: ${props => props.theme.colors.white}; transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; cursor: pointer; ` export function Button(props) { return <BaseButton>{props.title}</BaseButton> }
-
Next replace the contents of
App.jsx
(you can delete theApp.test.js
,logo.svg
, files too):import React, { Component } from 'react' import { Button } from './atoms/button' class App extends Component { render() { return <Button title="Atom Test" /> } } export default App
-
Go to
http://localhost:3000/
and you should see your new button rendered on the page!
Now we're going to add component level styles to our new Button
component.
import React from 'react'
export function Button(props) {
return <button>{props.title}</button>
}
-
At the top of the
button.jsx
file, import thestyled
:import React from 'react' import styled from '@emotion/styled' ...
-
Using
styled
, we can create a CSS-in-JS const that declares the styles for the element, as well as what the element actually is.const BaseButton = styled.button` padding: 0.6rem 2rem; font-size: 1.2rem; background-color: ${props => props.theme.colors.brandPrimary}; border-color: ${props => props.theme.colors.brandPrimary}; border-radius: 0.25rem; color: ${props => props.theme.colors.white}; transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; cursor: pointer; `
- Note: By using
props
we're able to access our Theme properties.
- Note: By using
-
We then update our
Button
component to useBaseButton
's styles:... export function Button(props) { return ( <BaseButton> {props.title} </BaseButton> ) }
We're now going to create an EmailSignup
molecule that is made up of atoms
.
-
Create a new file called
email-signup.jsx
inside thesrc/molecules
directory.import React from 'react' export function EmailSignup(props) { return <div className="email-signup" /> }
-
Next import the necessary
atoms
and add them to the markup.import React from 'react' import styled from '@emotion/styled' import { Input } from '../atoms/input' import { Button } from '../atoms/button' const FormContainer = styled.div` display: flex; max-width: 500px; ` const EmailInput = styled(Input)` height: calc(1.8em + 1rem + 2px); border-right: none; border-top-right-radius: 0; border-bottom-right-radius: 0; ` const SubmitButton = styled(Button)` border-left: none; border-top-left-radius: 0; border-bottom-left-radius: 0; ` export function EmailSignup(props) { return ( <FormContainer> <EmailInput placeholder={props.placeholder} /> <SubmitButton title={props.title} /> </FormContainer> ) }
You can now use Button
and EmailSignup
directly on any new page
you create, but we are going go one step further and create a SignupSection
.
-
Create a new file called
signup-section.jsx
inside thesrc/organism
directory.import React from 'react' export function SignupSection(props) { return <div className="signup-section" /> }
-
Similar to creating a
molecule
you will now import the necessarymolecules
oratoms
.import React from 'react' import { EmailSignup } from '../molecules/email-signup' export function SignupSection(props) { return ( <div className="signup-section"> <h2>{props.headline}</h2> <p>{props.cta_copy}</p> <EmailSignup placeholder={props.placeholder} title={props.title} /> </div> ) }
-
Next we will add structure to our
molecule
by importingFlex
andBox
from@rebass/emotion
.import React from 'react' import styled from '@emotion/styled' import { Box, Flex } from '@rebass/emotion' import { Container } from '../atoms/container' import { Paragraph, H2 } from '../atoms/text' import { EmailSignup } from '../molecules/email-signup' const SignUpContainer = styled.div` background: ${props => props.theme.colors.darkGray}; padding: 7% 0; ` export function SignupSection(props) { return ( <SignUpContainer> <Container> <Flex flexWrap="wrap"> <Box width={[1, 1, 1/2, 3 / 5]} p={3}> <H2 whiteText>{props.headline}</H2> <Paragraph whiteText>{props.cta_copy}</Paragraph> </Box> <Box width={[1, 1, 1/2, 2 / 5]} p={3}> <EmailSignup placeholder={props.placeholder} title={props.title} /> </Box> </Flex> </Container> </SignUpContainer> ) }