Lesson 06: Styling - strvcom/frontend-academy-2022 GitHub Wiki

Speaker: Jan Hoffman

Resources


There are many CSS-in-JS solutions, the most popular are Tailwind CSS, Stitches, Emotion, for this course we are using Styled Components.

1. Initial Setup

  • install styled components: yarn add styled-components
  • add types: yarn add --dev @types/styled-components
  • update next.config.js for nicer classNames:
const nextConfig = {
  reactStrictMode: true,
  compiler: {
    styledComponents: true,
  },
}
  • if using VS Code, add vscode-styled-components extension to teach it to understand CSS inside template literals (strings).

2. Setup Theme Variables

Create variables for:

  • colors
  • typography
  • media queries
  • levels for z-indexes and box-shadows

Example of colors.ts:

import { palette } from './palette'

export const colors = {
  text: {
    base: palette.grey[100],
    dimmed: palette.grey[500],
    light: palette.grey[700],
    formLabel: palette.grey[800],
    tabs: palette.grey[600],
    inverted: palette.white,
    inactive: palette.grey[600],
    silent: palette.grey[850],
  },
  background: {
    light: palette.white,
    dimmed: palette.grey[900],
    dark: palette.grey[100],
    inactive: palette.grey[850],
  },
  accent: {
    primary: palette.green,
    primaryHover: palette.greenDarker,
    destructive: palette.pink,
    destructiveHover: palette.pinkDarker,
  },
}

Usage in a styled component:

export const CreateLink = styled.a`
  position: fixed;
  bottom: 0;
  right: 0;
  width: 5.6rem;
  height: 5.6rem;
  margin: 3.2rem;
  border-radius: 50%;
  color: ${colors.text.inverted};
  cursor: pointer;
  background-color: ${colors.background.dark};
`

3. Global Style

Create global styles using createGlobalStyle:

export const GlobalStyle = createGlobalStyle`
  *, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
  }

  html,
  body, #__next {
    padding: 0;
    height: 100%;
  }

We can then use this GlobalStyle as a regular component:

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <>
      <GlobalStyle />
      <HeadDefault />
      <Component {...pageProps} />
    </>
  )
}

Multiple global styles can be used in the app.

px vs. relative units, do not use html { font-size: 10px; }, use a relative unit instead (like html { font-size: 62.5%; }) to make sure our website reflects user's accessibility settings for text size.

4. First Styled Component

Styled Component definition in styled.ts:

  export const Container = styled.div`
    padding: 0 2rem;
    width: 100%;
    max-width: 120remrem;
    margin: 0 auto;
  `

Usage:

export const DashboardPage: NextPage = () => (
  <LayoutInternal>
    <Container>
      ...
    </Container>
  </LayoutInternal>
)

5. Responsive Styling

Use media queries to achieve responsive layouts. Alternatives to use instead of mqs like clamp() or grids.

Example of responsive padding:

Media query definition inside mq.ts:

export const ScreenSize = {
  medium: 768,
  large: 1200,
}

export const mq = {
  smallOnly: `@media (max-width: ${ScreenSize.medium / 16}em)`,
  medium: `@media (min-width: ${ScreenSize.medium / 16}em)`,
  large: `@media (min-width: ${ScreenSize.large / 16}em)`,
}

Usage inside Container:

export const Container = styled.div`
  margin: 0 auto;
  padding: 0 0.8rem;
  max-width: ${ScreenSize.large / 10}rem;
  box-sizing: content-box;

  ${mq.medium} {
    padding: 0 2rem;
  }

  ${mq.large} {
    padding: 0 4rem;
  }
`

6. Creating Variants

Passing props to a styled component to modify its CSS.

Example for Button component:

type ButtonProps = {
  size?: 'small' | 'medium'
  accent?: 'normal' | 'primary' | 'destructive'
}

export const Button = styled.button<ButtonProps>`
  --text-color: ${colors.text.inverted};
  --background-color: ${colors.background.dark};
  --background-color-hover: ${colors.background.dark};

  ${StyleReset}
  ${typography.label.large}
  padding: 0.8em 5.4em;
  color: var(--text-color);
  border-radius: 4px;
  transition: background-color 0.3s;
  background-color: var(--background-color);

  &:disabled {
    --text-color: ${colors.text.inactive};
    --background-color: ${colors.background.inactive};
  }

  &:not(:disabled) {
    cursor: pointer;

    &:hover,
    &:focus {
      background-color: var(--background-color-hover);
    }
  }

  ${(props) =>
    props.accent === 'primary' &&
    css`
      --background-color: ${colors.accent.primary};
      --background-color-hover: ${colors.accent.primaryHover};
    `}

  ${(props) =>
    props.accent === 'destructive' &&
    css`
      --background-color: ${colors.accent.destructive};
      --background-color-hover: ${colors.accent.destructiveHover};
    `}

  ${(props) =>
    props.size === 'small' &&
    css`
      ${typography.label.medium}
      padding: 0.3em 2em 0.2em;
    `}
`

Usage

export const LoginPage: NextPage = () => (
  <LayoutExternal>
    <FormWrapper>
      ...
        <Button
          type="submit"
          size="small"
          accent="primary"
        >
          Login
        </Button>
      ...
    </FormWrapper>
  </LayoutExternal>
)

7. Extending an existing component

using as and attrs():

export const H1 = styled.h1`
  ${AccessibleHidden}
`

export const H2 = styled(H1).attrs({ as: 'h2' })``
export const SubmitButton = styled(Button).attrs({
  type: 'submit',
  accent: 'primary',
})``

Additional styles can by passed to attrs between backticks.

Using styled component variable as a selector:

export const Description = styled.p`
  margin: 3rem 0;
`

export const Article = styled.article`
  [...]

  ${Description} {
    margin: 0;
  }
`

Partial styles

export const AccessibleHidden = css`
  opacity: 0.001;
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
`
export const H1 = styled.h1`
  ${AccessibleHidden}
`

Partial styles can be responsive:

const allHeadingsStyle = css`
  font-family: ${font.headings};
  font-weight: inherit;
`

export const typography = {
  heading: {
    h1: css`
      ${allHeadingsStyle}
      font-size: 3rem;

      ${mq.medium} {
        font-size: 4.5rem;
      }
    `,
    ...
  }
}

8. CSS Animations

Animations can be re-triggered by changing key prop on the component.

Example of using shake animation on inputs:

import styled, { keyframes } from 'styled-components'

const shake = keyframes`
  from { transform: none; }
  20%  { transform: translateX(-1.5rem); }
  40%  { transform: translateX(1.5rem); }
  60%  { transform: translateX(-1.5rem); }
  80%  { transform: translateX(1.5rem); }
  to   { transform: none; }
`

export const Label = styled.label`
  animation: 0.5s ${shake};
`

Libraries for animations that work well with React: Framer Motion, react-transition-group, Swiper.

⚠️ **GitHub.com Fallback** ⚠️