Integrating a Tailwind design system with CRAs, using Git Submodules

By Raj Rajhans -
September 5th, 2021
8 minute read

Recently, I had to integrate a tailwind based design system into a CRA app as a git submodule, and make it work with Storybook, Chromatic, GH Actions and Netlify. I found a lot of helpful guides and blog posts detailing how to do some part of what I wanted to do, but there wasn’t any that had all the components I was dealing with. I also ran into numerous issues which I was able to fix after a lot of trial and error. This post is a documentation of all the issues I faced.

The Situation

There is an existing React application created using Create React App (CRA). It also has StorybookJS integration. The Github repository for this app uses GH Actions to build and run tests. The app is deployed using Netlify and the Storybook gets published using Chromatic. Chromatic also runs visual tests on every PR. Netlify and Chromatic, both are connected to the Github repository.

Now, another team has been working on a tailwind based design system, which lives in a separate repo. I had to integrate the design system into that CRA application as a git submodule, so that we can use the tailwind classes, and also import components directly from the design system.

Why import as a git submodule, you ask? Well the other option was to publish the design system package in an internal npm registry, but that approach was tried before, and many parts of the existing infrastructure didn’t play well with it. So, the submodule approach was chosen.

The Steps

On the first glance, it looks pretty straight forward. To accomplish the task, we’ll have to first add the design system repository as a git submodule. Doing that will give us a way to access the files in that repo through our repo.

Now, we need to configure our CRA so that it knows to use tailwind from the submodule. To do this, follow the instructions for integrating tailwind into an CRA using CRACO to extend the Webpack configuration. Note that in your tailwind.config.js you will have to give the paths of the submodule for things like colors, fontSize, spacings, etc.

Then finally, we’ll also need to configure Storybook so that it knows to use tailwind. To do that, follow steps for setting up TailwindCSS with Storybook.

Now let’s see what problems may arise when following all these steps.

The Problems that may arise, and How To Fix them

Depending on what you are using, you might not run into all of them, as I did. Also, at the end, we’ll also see how we can add a dark light mode toggle inside storybook so that we can preview how components look like in both themes from within storybook itself.

Problem 1: CRA & TypeScript give errors as they try to compile files inside the submodule

If your submodule repository also has something else, like, say a docs folder which contains another React app, then by default our CRA (and TypeScript, if you’re using it) will also try to compile those. Then you might see errors like module not found or random TypeScript error. Logically speaking, our CRA has nothing to do with the docs folder in the design system repository. So here, we’ll need to ignore that folder using craco.config.js. Add the following to craco.config.js to ignore the directory from your submodule:

webpack: {
configure: {
module: {
rules: [
test: /\.(js|mjs|jsx|ts|tsx)$/,
exclude: [path.resolve(__dirname, 'PATH_TO_DIRECTORY_WE_WANT_TO_IGNORE')],

Doing so tells our CRA webpack to ignore that path, and then it won’t complain about any module not found or anything like that. Next, we need to tell TypeScript to ignore it as well. This is simple, just add the following in your tsconfig.json:


At this point, all your errors from the submodule directory should go away!

Problem 2: Github Actions cannot access the submodule

The next problem which you will face, if you are using Github Actions to build and test code on every PR is that it will show an error like this:

ERROR: Repository not found. Error: fatal: Could not read from remote repository.

In my case, both the repositories were under the same organization, and both were private. To fix this error, you need to add a token to the actions/checkout@v2 github action that you would be using in your yml file.

The way GH Actions works is that it first checks out the repository so that the workflow runner can access it. To actually check out the repo, actions/checkout@v2 is used. And, when it tries to check out your submodule also, it will fail because it doesn’t have permissions to access it. To fix that, we will provide it a token. You can provide a Personal Access Token or you can provide an org level token, based on your situation. Here’s a code for the workflow YML file:

# Workflow name
name: 'Build and Test'
# Event for the workflow
on: push
# List of jobs
# Operating System
runs-on: ubuntu-latest
# Checkout the repository to the GitHub Actions runner
- name: Checkout
uses: actions/checkout@v2
token: ${{ secrets.YOUR_ACCESS_TOKEN }}
submodules: 'recursive'
# Update references
- name: Git Submodule Update
run: |
git submodule update --init --recursive
# rest of your GH Actions Worflow definition

One more thing to note here is to make sure that the submodule repo URL in the .gitmodules file is SSH and not HTTPS. For some reason, sometimes the token based auth would just fail when the URL was https.

Problem 3: Netlify cannot access the submodule

If you are using Netlify for deployment, and the submodule repository is private, then it would probably give an error similar to this:

3:43:25 PM: fatal: Could not read from remote repository.
3:43:25 PM: Please make sure you have the correct access rights
3:43:25 PM: and the repository exists.
3:43:25 PM: Unable to fetch in submodule path 'src/submodule'

To fix this, you need to generate a deploy key, then add it as a Read Only Deploy Key in the repo settings, and that’s it! Netlify can now access your submodule securely. Read this support guide for more details on this process.

Problem 4: Tailwind class styles don’t get applied in CRA

If you are facing this problem, it means that you haven’t followed the Tailwind guide for CRA properly. Make sure all the steps given in there are followed. Ensure that the following points are satisfied in your project -

  • You have installed Tailwind’s PostCSS 7 compatibility build. This is necessary because CRA doesn’t support PostCSS 8 yet.
  • You have installed and configured CRACO. This is needed to override the PostCSS configuration and include autoprefixer. Following is my craco.config.js
const path = require('path');
module.exports = {
style: {
postcss: {
plugins: [require('tailwindcss'), require('autoprefixer')],
// rest of your craco config (for things like excluding a directory for webpack)
  • You have updated scripts in your package.json to use craco instead of react-scripts
// ...
"scripts": {
"start": "craco start",
"build": "CI='' craco build",
"test": "craco test",
"eject": "react-scripts eject",

Problem 5: Tailwind class styles don’t get applied in Storybook

If you are facing this problem, you need to make sure that webpack config Storybook is modified to include Tailwind in the build. Here is what your .storybook/main.js should look like -

const path = require('path');
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
webpackFinal: async (config) => {
test: /\.(css|scss)$/,
use: [
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [require('tailwindcss'), require('autoprefixer')],
include: path.resolve(__dirname, '../src'),
return config;

Once this is done, don’t forget to import your base styles file (which imports all the tailwind files) in .storybook/preview.js. If you follow these two steps, tailwind will start working inside Storybook

Bonus: Adding dark mode toggle in Storybook

If you have dark and light modes in your app, and you are using Tailwind with Storybook, then it’s nice to be able to preview how would components look like in dark mode and light mode from within storybook itself. To do that, we’ll use the storybook-addon-themes add on.

Configuring it is really simple. Modify your .storybook/main.js to include the addon:

module.exports = {
addons: [
// Maybe other addons here...
// Or here...

Then, in your .storybook/preview.js file, configure the add on to show dark and light themes:

export const parameters = {
// ...
themes: {
clearable: false,
list: [
name: 'Light',
class: [],
color: '#ffffff',
default: true,
name: 'Dark',
class: ['dark'],
color: '#000000',

This will have two themes “dark” and “light”, with light being the default in your storybook. When you switch to the dark theme, it will add the dark class to the parent so that your component will then render in dark mode styles.

That is it for this post. I hope it was helpful and you don’t spend hours figuring out what’s wrong like I did. Although, to look on the brighter side, I learned a lot of new things about webpack, and how GH actions work under the hood. See you in the next one!



Raj Rajhans

Product Engineer @ invideo