Den Dribbles

How To Use Your Tailwind Theme Anywhere Throughout Your React App

July 26, 2020

Today we’re going to use Tailwind’s resolveConfig to write out a JavaScript file that we can use with the React Context API to access the values from anywhere throughout the application.

We will create a small application and show it in action by overriding some styles from the React Select library.

Setting up

We are going with the basic Create React App template. Let’s set that up and install the dependencies for today.

# Create app
npx create-react-app tailwind-theme-example
# Change into folder
cd tailwind-theme-example
# Install dependencies
yarn add tailwindcss \
  react-select \
  prettier
# Create a bin folder for us to write our JS generation script
mkdir bin
touch bin/generate-tailwind-theme
# Add execution permissions
chmod u+x bin/generate-tailwind-theme

Tailwind Configuration

We want to have a tailwind.config.js file that can be used to extend the Tailwind defaults. We won’t do too much with it today, but let’s create it to use without our script later.

# Setup tailwind config file
npx tailwindcss init

This will create a tailwind.config.js file at the root directory that looks like this:

// tailwind.config.js
module.exports = {
  theme: {},
  variants: {},
  plugins: [],
}

Let’s now write our script that will reference this file.

Updating our script

Inside of bin/generate-tailwind-theme, update the file to have the following:

#!/usr/bin/env node

const fs = require("fs")
const resolveConfig = require("tailwindcss/resolveConfig")
const prettier = require("prettier")
const path = require("path")
// bring in the Tailwind config
const tailwindConfig = require("../tailwind.config.js")

const { theme } = resolveConfig(tailwindConfig)
const themeStr = JSON.stringify(theme)
const js = `
const theme  = ${themeStr}

export default theme
`

try {
  // write the file to src/theme.js after
  // having prettier format the string for us
  fs.writeFileSync(
    path.resolve(process.cwd(), "./src/theme.js"),
    prettier.format(js, { parser: "babel" }),
    "utf-8"
  )
} catch (err) {
  // uh-oh, something happened here!
  console.log(err.message)
}

Here, we follow these steps:

  1. Use resolveConfig from Tailwind to combine our config and their default config. We are destructing theme from the result.
  2. Stringify the theme value and interpolate it within a string js. This string is valid JavaScript.
  3. Writing that file out to src/theme.js after having our Prettier library format it.

We can now run this using bin/generate-tailwind-theme. If this doesn’t work, you may need to check you have the correct permissions and shabang (#!) reference to your Node installation. If this doesn’t work, feel free to run node bin/generate-tailwind-theme and see what happens.

Theme Output

After success, a short look into our src/theme.js file should look like the following:

const theme = {
  screens: { sm: "640px", md: "768px", lg: "1024px", xl: "1280px" },
  colors: {
    transparent: "transparent",
    current: "currentColor",
    black: "#000",
    white: "#fff",
    gray: {
      "100": "#f7fafc",
      "200": "#edf2f7",
      "300": "#e2e8f0",
      "400": "#cbd5e0",
      "500": "#a0aec0",
      "600": "#718096",
      "700": "#4a5568",
      "800": "#2d3748",
      "900": "#1a202c",
    },
    // ... the rest has been omitted for brevity
  },
  // ... the rest has been omitted for brevity
}

export default theme

Awesome! Now we have our theme config that we can use with React Context.

Setting up the Provider for our app

Update the src/App.jsx file to look like the following:

import React, { createContext } from "react"
import theme from "./theme"
import { Select } from "./Select"
import "./App.css"

export const ThemeContext = createContext(theme)

function App() {
  const [select, setSelect] = React.useState()

  return (
    <ThemeContext.Provider value={theme}>
      <Select
        id="select"
        name="select"
        options={[
          { value: "chocolate", label: "Chocolate" },
          { value: "strawberry", label: "Strawberry" },
          { value: "vanilla", label: "Vanilla" },
        ]}
        value={select}
        onChange={option => {
          setSelect(option?.value)
        }}
      />
    </ThemeContext.Provider>
  )
}

export default App

The ThemeContext that we have created using createContext will allow the theme to be accessible with the useContext hook throughout our application!

At the moment, our App will not run (we haven’t created our Select file!).

We’re going to write an adapter file for our Select component.

Create a Select Adapter

Add a new Select component file.

touch src/Select.jsx

Now, inside that src/Select.jsx file, add the following:

import React from "react"
import BaseSelect from "react-select"
import { ThemeContext } from "./App"
export const Select = props => {
  const theme = React.useContext(ThemeContext)

  const customStyles = {
    control: (provided, state) => ({
      ...provided,
      zIndex: theme.zIndex["10"],
      fontFamily: theme.fontFamily.sans.join(","),
      fontSize: theme.fontSize.base,
      borderColor: state.isFocused
        ? theme.colors.blue["500"]
        : theme.colors.gray["300"],
      borderWidth: theme.borderWidth["2"],
      outline: "none",
      boxShadow: "none",
      "&:hover": {
        borderColor: state.isFocused
          ? theme.colors.blue["500"]
          : theme.colors.gray["500"],
      },
    }),
    menu: provided => ({
      ...provided,
      fontFamily: theme.fontFamily.sans.join(","),
      fontSize: theme.fontSize["text-base"],
    }),
    option: (provided, state) => ({
      ...provided,
      backgroundColor: state.isSelected
        ? theme.colors.blue["500"]
        : theme.colors.white,
      "&:hover": {
        ...provided["&:hover"],
        backgroundColor: theme.colors.blue["700"],
        color: theme.colors.white,
      },
    }),
  }

  return <BaseSelect styles={customStyles} {...props} />
}

In this file, we are exporting the BaseSelect with some default styles that come from our theme.

These theme values come from the theme.js object that have added to our context! This is possible at the line const theme = React.useContext(ThemeContext) where we set the theme to come from our context we set in App.js.

Note, the one quick here are the font family values theme.fontFamily.sans.join(" "). Basically, the fontFamily key values are an array, so for it to be valid for the font-family CSS property, we want to join that array to have a space between each value ie ['Open Sans', 'sans-serif'] as an example would become Open Sans,sans-serif.

This styling isn’t perfect. They are just some styles I was playing around with this morning, but they illustrate the point.

Running the app

Let’s get our app up and going the normal way:

yarn start

You can now see that we have our Tailwind theme styles applied!

Styles applied

We can now see these changes in action. If we head back to our src/Select.jsx file and replace all instances of theme.colors.blue with theme.colors.teal, you will now see teal!

Teal applied

Congratulations, you now have access to your Tailwind theme values across your React application!

Resources and Further Reading

  1. Completed Project on GitHub
  2. React Select - Custom Styles
  3. Tailwind - Referencing in JavaScript
  4. React Context API

Image credit: Ibrahim Asad


Related Articles

December 09, 2020

See how you can dynamically create UIs based on React State using dynamic imports, Next.js 10 and React State

December 02, 2020

Have you ever wanted to build a UI Component Library with TypeScript and React? This blog post will take you through a straightforward set up that uses the bare minimum to get a working component library that you can re-use across your different React projects.

November 16, 2020

This post will go through a simple example of setting up simple authentication with a user and password to your Next.js website.

November 08, 2020

This blog post will explore the new Next.js Image component for an optimised image experience on the web.

November 08, 2020

This blog post will explore the new internationalised routing in Next.js 10 and how you can use this to your advantage with react-intl for React.js

November 06, 2020

See a roundup of my look into Vercel's new analytics feature that you can enable on a Vercel project and see how I used GTMetrix to help push some numbers.

November 05, 2020

Learn how to deploy the base Next.js 10 app with the Vercel CLI and/or the Vercel GitHub Integration

November 04, 2020

Learn how to export static HTML from a Nextjs 10 project to host

October 28, 2020

Updating my previous post on using Create React App with Snowpack for the latest versions and NPM 7

August 18, 2020

Look at how we can work around one of the Service Worker's biggest misunderstanding

A personal blog on all things of interest. Written by Dennis O'Keeffe, Follow me on Twitter