Share Tailwind configuration with Storybook in an Angular Nx monorepo

Diego Langarica Fuentes
medialesson
Published in
6 min readMar 8, 2024

Say you have an Angular application in an Nx monorepo with Tailwind already configured and want to share the same configuration with a Storybook instance from your UI components library. Simple, right? Both Storybook and Tailwind are already closely integrated within Nx, and everything can be easily set up using existing generators. This topic has also been covered in other articles like Jumpstart your Angular 14 development with Storybook, Tailwind and Nx and Use Storybook with Tailwind in an Nx Workspace from the Nx blog. However, some of the information provided is slightly outdated and I found myself having to refer to multiple sources in order to reach the expected results. That’s when I decided to write this little guide, aiming to provide an updated solution and summarise all the steps and learnings in a single place.

Initial setup

Let’s start by creating an Nx workspace via:

npx create-nx-workspace@latest

I’ve used the following base configuration but feel free to change it to match your needs:

Now, let’s add Tailwind CSS to our Angular application by running:

cd nx-workspace
npx nx g @nx/angular:setup-tailwind angular-app

The generator will install the necessary dependencies, create a tailwind.config.js file and add Tailwind directives for each of Tailwind’s layers to our main styles.scss file inside our angular-app.

For our UI components library, let’s create a new simple Angular library (i.e. non-publishable and non-buildable) named ui-components in a directory with the same name:

npx nx g @nx/angular:library --name=ui-components\
--directory=libs/ui-components\
--projectNameAndRootFormat=as-provided\
--style=scss

Delete the auto-generated UiComponentsComponent and let’s instead create a new card component, again inside a directory with the same name:

npx nx g @nx/angular:component --name=card\
--directory=libs/ui-components/src/lib/card\
--nameAndDirectoryFormat=as-provided

Do not forget to export the new component in the library’s index.ts:

// libs/ui-components/src/index.ts
export * from './lib/card/card.component';

As the final step of our initial setup, let’s add Storybook for our UI components library.

npx nx g @nx/angular:storybook-configuration ui-components

So far, we’ve created a Nx monorepo with an Angular application and a components library containing our Storybook (relying purely on the Nx CLI), which we can run with the command:

npx nx storybook ui-components

The problem

For our main example, we’ll apply a simple styling for both our AppComponent and CardComponent.

First, let’s update the theme in the tailwind.config.ts file from our angular-app. I’ve set Barlow as the fontFamily and added a ml colour definition:

// apps/angular-app/tailwind.config.js
const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
const { join } = require('path');

/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
...createGlobPatternsForDependencies(__dirname),
],
theme: {
fontFamily: {
sans: ['Barlow', 'sans-serif'],
},
colors: {
ml: {
base: '#005A57',
contrast: '#AFE108',
foreground: '#EBECEC',
},
},
},
plugins: [],
};

Afterwards, we can use Tailwind classes in our CardComponent to implement our design:

<!--libs/ui-components/src/lib/card/card.component.html-->
<div class="bg-ml-base border-ml-contrast flex border-l-8 p-6">
<h2 class="text-ml-foreground">{{ title }}</h2>
</div>

Similarly, in our AppComponent:

<!--apps/angular-app/src/app/app.component.html-->
<h1 class="text-ml-base px-4 pt-3 text-2xl">App component</h1>
<div class="grid w-full grid-cols-3 gap-4 p-4">
@for (card of cards; track card) {
<nx-workspace-card [title]="card" />
}
</div>

Everything should work fine when building and serving the application:

Neat! But sadly, it will not work when running Storybook 😢:

That’s because, at least for now, Tailwind is only available in the context of our Angular app — we’ll fix that in the next section.

Sharing Tailwind configuration

Non-buildable and non-publishable libraries imported in an Angular application do not normally need to their own Tailwind configuration: Any classes added in the library’s components will be correctly styled once the application where Tailwind was added is built. But since we want the components in our library to look the same in Storybook as they would in our app, we are required to add a tailwind.config.js file within Storybook’s context. Additionally, in order to avoid having duplicated code 🤮, we need to make sure the same configuration is shared between our Storybook and Angular application.

An easy way to achieve this — described in detail in another Nx blog post: Set up Tailwind CSS with Angular in an Nx workspace — consists of creating a shared Tailwind preset, and then importing it in the Tailwind configuration from every application or library that requires it.

Creating a reusable configuration preset

Let’s begin by adding a tailwind.preset.js file in the root of our workspace.

Presets are just regular Tailwind configuration objects, taking the exact same shape as the configuration you would add in your tailwind.config.js file.

Inside our new preset, we can just copy our theme from our tailwind.config.js, which contains our font and colours, and export it as:

// tailwind.preset.js
module.exports = {
theme: {
fontFamily: {
sans: ['Barlow', 'sans-serif'],
},
colors: {
ml: {
base: '#005A57',
contrast: '#AFE108',
foreground: '#EBECEC',
},
},
},
};

Let’s also create a new styles folder in our root, and move the styles.scss file from our angular-app (which contains the auto-added Tailwind directives from our initial setup) to our new folder. Alternatively, you can create a new file and simply cut and paste the Tailwind directives and other global styles you want to be made available in our whole workspace.

// styles/styles.scss
@tailwind base;
@tailwind components;
@tailwind utilities;

It’s time to import the configuration. First, in our Angular application.

Importing the configuration in Angular app

In the apps’ tailwind.config.js, we can remove the theme section (given that it is already covered by our preset) and point to our root file via the presets option. The result should be:

// apps/angular-app/tailwind.config.js
const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
const { join } = require('path');

/** @type {import('tailwindcss').Config} */
module.exports = {
presets: [require('../../tailwind.preset.js')],
content: [
join(__dirname, 'src/**/!(*.stories|*.spec).{ts,html}'),
...createGlobPatternsForDependencies(__dirname),
],
};

We also need to update the project.json with the new location of our styles.

// apps/angular-app/project.json
{
...
"targets": {
"build": {
...
"options": {
...
"styles": ["styles/styles.scss"],
},
},
}
}

If we serve our app, it should look the same as it did before. Nothing is broken. So far so good.

Setting up Tailwind in Storybook

For our Storybook, the setup looks very similar to the changes we just did in our Angular app.

We create a tailwind.config.js file in our ui-components library, whose contents will look almost identical to those in the final version of our app’s config. I’ve only removed *.stories from the pattern in the content section, so that class names found in our stories are also included when Tailwind generates all of the required CSS. You may also want to make sure that the path to our preset is correct in case your library is not at the same depth as in our example.


// libs/ui-components/tailwind.config.js
const { createGlobPatternsForDependencies } = require('@nx/angular/tailwind');
const { join } = require('path');

/** @type {import('tailwindcss').Config} */
module.exports = {
presets: [require('../../tailwind.preset.js')],
content: [
join(__dirname, 'src/**/!(*.spec).{ts,html}'),
...createGlobPatternsForDependencies(__dirname),
],
};

Finally, just like before, let’s update the library’s project.json to include our global styles as part of the options in build-storybook target.

// libs/ui-components/project.json
{
...
"targets": {
"build-storybook": {
...
"options": {
...
"styles": ["styles/styles.scss"],
},
},
}
}

We are done! Re-launching Storybook should display or card with our expected styles.

Conclusion

Using only a handful of commands we were able to setup an Nx workspace combining Angular + Tailwind + Storybook. And by sharing our Tailwind configuration in both Angular and Storybook, we managed to implement a quick design that is consistent everywhere. The key, as we learned, is based on a reusable Tailwind preset, that can be imported in other configuration files, and a shared style file that can be included in any project’s build options.

--

--