React | NextJS

13.2 Styling and Themeing

Module Still Under Development

# Tailwind

For a basic introduction to Tailwind, please refer back to the notes in MAD9013 (opens new window).

Tailwind is the current best practice for NextJS. The other styling approaches in this module are good for React SPAs.

The notes on this page will specifically connect Tailwind with React and NextJS.

Here is the NextJS reference to using Tailwind with NextJS (opens new window)

When you use npx create-react-app@latest you will be prompted about wanting to use Tailwind. If you say yes then you can skip these set up steps. If you have an existing project that doesn't have Tailwind already then you can follow these instructions to add it to your project.

To add Tailwind for your NextJS web app, run the npm commands in the terminal to generate both the tailwind.config.js and postcss.config.js files.

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
1
2

Once these files have been created, you will need to update the contents of the tailwind.config.js file with the names of files that will be using tailwind.

/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    './app/**/*.{js,ts,jsx,tsx,mdx}', // Note the addition of the `app` directory.
    './pages/**/*.{js,ts,jsx,tsx,mdx}', //for older versions of NextJS that use page instead of app router
    './components/**/*.{js,ts,jsx,tsx,mdx}',

    // Or if using `src` directory:
    './src/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

Next, you should update the top of your globals.css file inside /app/. Add the tailwind imports at the top of the file:

@tailwind base;
@tailwind components;
@tailwind utilities;
1
2
3

Make sure that you are importing the globals.css file into your root layout.jsx file so tailwind styles can be used across your whole application.

With this configuration and set up completed, you can now use the Tailwind classes through your pages and components.

export default function Page() {
  return <h1 className="text-3xl font-bold underline">Styled using Tailwind classes</h1>;
}
1
2
3

The Tailwind site documentation (opens new window) has a easily searched list of styles.

Some of the items that you will use most often have to do with font-sizes, padding, margin, and color.

# Font-size

To set font-sizes for any element you can use the class names: text-xs, text-sm, text-base, text-lg, text-xl, text-2xl, or text-3xl. Each of these classes represent a font-size plus a line-height. If you want to set a specific line-height with any font-size, just add /[ ] after the text-* part. Inside the [] you can put the override value for the line-height. Eg: text-sm/[4rem] will use the text-sm font-size and then a line-height of 4rem will be used.

If you want to change the font-size based on a breakpoint you can prefix each size with a breakpoint label. Eg: text-sm md:text-base lg:text-lg would apply text-sm as the font-size up to the medium breakpoint where it will start to use text-base and then at the large screen breakpoint switch to text-lg.

font-size reference (opens new window).

These sizes all have default values. If you want, you can override them in the tailwind.config.js file at the root of the project. Inside the theme section, add an extend property if it doesn't exist. Inside that you can add a fontSize object and set values for all the sizes.

module.exports = {
  theme: {
    extend: {
      fontSize: {
        sm: '0.8rem',
        base: '1rem',
        xl: '1.25rem',
        '2xl': '1.563rem',
        '3xl': '1.953rem',
        '4xl': '2.441rem',
        '5xl': '3.052rem',
      },
    },
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

If you add settings in the tailwind.config.js file inside of the theme property you are basically creating a new version of the setting. So, fontSize would replace the default fontSize entirely.

If you place the property inside of theme.extend, then you are adding values to the default. If the property name inside theme.extend.fontSize matches, then you are overriding the default. If it doesn't exist in the default then you are adding it as a new value.

If you want to create a new set of sizes, the https://type-scale.com (opens new window) website is great.

# Padding and Margin

Padding and Margin classNames start with of p- or m- followed by a number. The number refers to a value from the default spacing scale in tailwind. If you want to override those values for your site, edit your tailwind.config.js file and add a spacing property to the theme.

module.exports = {
  theme: {
    spacing: {
      1: '8px',
      2: '12px',
      3: '16px',
      4: '24px',
      5: '32px',
      6: '48px',
    },
  },
};
1
2
3
4
5
6
7
8
9
10
11
12

You can add one of those numbers after the p- or m- to apply the distance to all four sides. If you want to only apply the spacing to the top and bottom use py- or my- followed by the number. Left and right can be set with the classNames px- or my- and the number.

If you want to set the four sides use pt-*, pr-*, pb-*, pl-* for top, right, bottom, and left. Same for margin just m instead of p.

# Colours

For the colours we use a prefix of text- or bg- to indicate if we are setting the colour for the text or the background.

After the prefix use the name of the colour. Then finish with the brightness level number of 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, or 950. Here are a few examples:

text-amber-400
text-blue-100
bg-zinc-50
bg-slate-950
1
2
3
4

colour reference page (opens new window). You can override any colours you want or override the existing values. See the Colour reference page for the details on overriding. The process is just like the sizing and spacing with the edits to the tailwind.config.js file.

# Hovering Changes

For any className in Tailwind, you can prefix it with hover: and it becomes a class that only gets applied when the user hovers over the element. active: and focus: are also available.

# Breakpoints

If you want any className to only apply in specific breakpoints then you can add one of the breakpoint prefixes: sm, md, lg, xl, or 2xl.

Prefix min width CSS
sm 640px @media (min-width: 640px) { ... }
md 768px @media (min-width: 768px) { ... }
lg 1024px @media (min-width: 1024px) { ... }
xl 1280px @media (min-width: 1280px) { ... }
2xl 1536px @media (min-width: 1536px) { ... }

Breakpoint reference (opens new window)

If you want to change these sizes, use the tailwind.config.js file.

module.exports = {
  theme: {
    screens: {
      sm: '480px',
      md: '768px',
      lg: '976px',
      xl: '1440px',
    },
  },
};
1
2
3
4
5
6
7
8
9
10

# Dark Mode

Dark Mode reference (opens new window)

# Styled Components

# Set-Up

Styled-Components website (opens new window)

Styled-Components is an NPM module that can be installed and added to your project. It will let you create styled web component-like elements that you can use and reuse as part of your React application.

Styled-components work with tagged template literals to achieve the following:

  • Tracking of components that use styles and automatically injecting only what is needed. This reduces the amount of code needed to be loaded and allows for improved code splitting.
  • Unique generation of class names and injection of styles into the generated DOM.
  • Binding of styles directly to components to make it easier to know if CSS can be removed or is unused.
  • Styling that can be altered via props or a global theme.
  • Binding of styles directly to components makes maintenance easier.

We can add styled-components via npm or yarn.

npm install styled-components
1

if you are using yarn as your package manager, you should add the resolutions object to your package.json file. This will help avoid conflicts between different versions of styled-components if they are ever added or updated.

{
  "resolutions": {
    "styled-components": "^5"
  }
}
1
2
3
4
5

# Creating Styled-Components

When we are building a React App by writing JSX, we are writing what looks like HTML. However, none of it actually is. What you are writing are React Elements. This is what we are creating with styled-components too.

import styled from 'styled-components';
//we need to import the styled object from styled-components to be able to create elements

const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: orangered;
`;
1
2
3
4
5
6
7
8

The above snippet uses a tagged template-literal to create an h1 element with that specific CSS attached to the h1 element.

Notice that the styling inside the template literal is actually written like CSS. It is NOT the JavaScript style properties that we would put into a style attribute object.

Now you can use this new Title element in your JSX.

export default function MyComponent(props) {
  return (
    <header>
      <Title>Some Words</Title>
    </header>
  );
}
1
2
3
4
5
6
7

The new styled-components can be created inside the file for each Component or you can create another file where you create the static styled components and then import them as needed.

# Dynamic Styled-Components

If you want, you can create dynamically styled components. We can integrate props, the values or existence of props into the styles for newly created styled-components.

import styled from 'styled-components';

const Title = styled.h1`
  /* some dynamic props */
  font-size: ${(props) => (props.secondary ? '1.2rem' : '3rem')};
  font-weight: ${(props) => (props.secondary ? '300' : '100')};
  color: ${(props) => (props.secondary ? props.color : '#222')};
  /* and some static ones */
  padding: 0.25rem 1rem;
`;

export default function MyComponent(props) {
  return (
    <header>
      <Title>Some Words</Title>
      <Title secondary color="#999">
        Some Words
      </Title>
    </header>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

In the example above, the <Title> element that has the secondary prop attribute will have the style values shown below. The color attribute's value will be used as the CSS color if the secondary attribute exists.

font-size: 1.2rem;
font-weight: 300;
color: #999;
padding: 0.25rem 1rem;
1
2
3
4

The version of <Title> without the secondary prop attribute will have the alternate values.

# Extending Styled-Components

If you want to make a minor change to an existing styled-component, then we can use the styled() method directly to extend the component.

import styled from 'styled-components';

//the base component
const Button = styled.button`
  color: red;
  font-size: 1rem;
  border: 1px solid red;
  padding: 0.2rem 1rem;
  border-radius: 0px;
`;

//extend the base component
const RoundedButton = styled(Button)`
  padding: 0.25rem 1.5rem;
  border-radius: 1rem;
  font-weight: 100;
`;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

The extended version RoundedButton has all the properties of the original Button, with new properties added and matching properties overwritten.

# Pre-Processor Type Styling

If you remember your work with Sass, you will remember how you can nest style definitions inside each other to build on the parent styling.

We can do the same thing with the template literal strings that we use when creating our styled-components.

const Anchor = styled.a`
  color: cornflowerblue;
  background-color: white;

  &:hover {
    color: white;
    background-color: cornflowerblue;
  }

  &.someClass {
    font-weight: bold;
  }
`;

//target checkbox inputs by their type attribute
const Input = styled.input.attrs({ type: 'checkbox' })`
  font-size: 2rem;
  accent-color: gold;
`;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# Styled Animation

styled-components comes with a keyframe method that allows us to create styled components that include animations.

Here is a basic example of a Loader with text that fades in and out once per second.

//Loader.js
import styled, { keyframes } from 'styled-components';

const pulse = keyframes`
  0%{
    opacity: 0.1;
  }
  100%{
    opacity: 1;
  }
`;

const H1 = styled.h1`
  color: red;
  font-size: 2rem;
  text-align: center;
  animation: 1s ${pulse} infinite alternate;
`;

export default function Loader() {
  return <H1>Loading...</H1>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Notice that keyframes is imported inside {}. styled is the only default import from styled-components.

The keyframes tagged template literal creates the CSS animation and a unique name that can then be used inside of a styled component that you create. The animation becomes part of that component.

Finally, just add your styled component to the JSX, like you would any other JSX element.

# Theming

The process of Theming a React App means creating a global set of default styles and components that can be inherited and extended. We can define styles for any existing React elements plus new styled-component elements. These styles can then be inherited and used throughout the application.

# Creating a Theme

When creating a Theme, we are creating styles and properties that will be shared and accessed from any styled component in our app. These are shared properties that hold elements of our Design System. The properties that we define can then be read from inside of styled components, animations, and the global styles.

We need a place to hold our theme and inject it into our app. Create a file called Theme.js and create a JavaScript const that holds an object with ANY properties that you want. The values of the properties can be strings, numbers, arrays, or objects. The names of the properties are what we will be using to access those values. So, use meaning names.

const theme = {
  name: 'Simon',
  colors: {
    darkBg: '#222',
    lightBg: '#444',
    brightTxt: '#FFF',
    dullTxt: '#DDD',
  },
  fonts: ['Roboto', 'sans-serif'],
  fontSizes: {
    xsmall: '0.8rem',
    small: '1em',
    medium: '1.5em',
    large: '3em',
    xlarge: '4.5rem',
  },
  fontWeights: {
    light: '100',
    normal: '300',
    bold: '500',
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# ThemeProviders

The styled-components ThemeProvider uses the React Context API to create a shared object that holds style properties.

So, with NextJS, this means that these themes have to be added on client side components, in order to use the providers, if the providers use hooks.

Every time you create a styled component, it will automatically be passed the theme object through props.

If you want to use any of the theme properties in normal React components then we can use the useTheme hook to access the theme object.

We will add our ThemeProvider to our Theme.js file that already has our properties. Add the following above the const theme.

import { ThemeProvider } from 'styled-components';
//don't forget the { }
import React from 'react';

export default function Theme({ children }) {
  return <ThemeProvider theme={theme}>{children}</ThemeProvider>;
}
1
2
3
4
5
6
7

In Theme.js we have now created a ThemeProvider component called Theme that accepts the variable theme as the object to store in a Context object and share with all shared components.

Theme will be passed a props object. We destructure out the children property so we can inject them back inside as children of the ThemeProvider.

Finally, we go to our <App> file to import and add the <Theme></Theme> around everything else.

//App.js
import Theme from './components/theme/Theme';
import Loader from './components/loader/Loader';
import styled from 'styled-components';

const Box = styled.div`
  color: ${(props) => props.theme.colors.brightTxt};
  background-color: ${(props) => props.theme.colors.darkBg};
  font-family: ${(props) => props.theme.fonts[1]};
  font-size: ${(props) => props.theme.fontSizes.large};
  padding: 1rem 2rem;
  margin: 1rem 0;
`;

export default function App() {
  return (
    <Theme>
      <Loader />
      <Box>Some Words.</Box>
    </Theme>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Here we have our imported Theme object wrapped around a Loader component that we imported, plus a Styled component called Box that we built here in this page.

The Box component is a styled component so it will automatically be passed a props object when it renders. The props object will contain a theme property. The theme property is the const theme from Theme.js.

# GlobalStyles

styled-components also have a createGlobalStyle method that can be imported and used to create a base CSS stylesheet.

When we create styled components, we are binding bits of css directly to a component that can be reused. That creates a CSS class that will be uniquely assigned to your component.

GlobalStyles are a CSS stylesheet that you can inject into your app as a component BUT they have the added benefit of being able to read values from our ThemeProvider.

Create a GlobalStyles.js file that imports {createGlobalStyle}.

import { createGlobalStyle } from 'styled-components';

export const GlobalStyles = createGlobalStyle`
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
html{
  min-height: 100vh;
}
body {
  min-height: 100vh;
  font-family: ${(props) => props.theme.fonts[1]};
}
`;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

All your theme props may be accessed inside your Global Style as long as GlobalStyles is nested inside <ThemeProvider>.

The theme props must be accessed as a function. You cannot access the properties directly.

All the syntax here must be css style, not JS style props.

# Combining Themes and Styled Components

As already mentioned, if you create a styled component in a web app that has a ThemeProvider then the contents of your theme object will automatically be passed in through props.

Now, while the component will have access to props.theme, the actual value will not be available until the render happens. So, we need to use a function to access the values. Here is the syntax for a simple arrow function that will be passed the props object. It accepts the props object and returns a value from inside the theme object.

${ props => props.theme.fontSizes.large }
1

The ${} wrapped around the function is there because we are inside a template literal.

const Box = styled.div`
  color: ${(props) => props.theme.colors.brightTxt};
  background-color: ${(props) => props.theme.colors.darkBg};
  font-family: ${(props) => props.theme.fonts[1]};
  font-size: ${(props) => props.theme.fontSizes.large};
  padding: 1rem 2rem;
  margin: 1rem 0;
`;
1
2
3
4
5
6
7
8

This same thing can be done inside the GlobalStyles file too.

import { createGlobalStyle } from 'styled-components';

export const GlobalStyles = createGlobalStyle`
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
html{
  min-height: 100vh;
}
body {
  min-height: 100vh;
  font-family: ${(props) => props.theme.fonts[1]};
}
`;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

In this way, we are creating a global CSS stylesheet that will include settings that we define in our theme, which should be getting its values from the Design System that you used when designing your app in XD, or Figma, or Sketch, etc.

# useTheme Hook

If you have a component that is not a styled component but you still want to access the theme object and use it's values in a style attribute, then you can use the useTheme hook to achieve this.

import { useTheme } from 'styled-components';
1

Call the method inside your component function and it will return a reference to the theme object.

export default function MyComponent(){
  //no props needed
  const theme = useTheme();

  return (
    <div style={ { backgroundColor: ${theme.colors.brightTxt}, } }>
      <p>Some words.</p>
    </div>
  )
}
1
2
3
4
5
6
7
8
9
10

Pay close attention to the syntax inside the style attribute. The first {} indicates that this is an expression. The second {} is the style object that React requires. So all the styles in here are JS style properties. Then we need to inject a variable inside the style object, which means wrapping our theme values in ${}.

# What to do this week

TODO Things to do before next week.

Last Updated: 4/9/2024, 8:56:38 PM