Build Web Fullstack Apps with DIRT: Django, Inertia, React & Tailwind CSS aka D.I.R.T Stack
Tags: django, inertiajs, react, tutorial
Published: 2023-02-07 17:41:24

Lately, I've been digging into (🙃) the world of [InertiaJs](https://inertiajs.com/) so, here’s a mini guide on how to get Tailwind CSS working in your Django + InertiaJs + React (Vite) project. Since there are so many “stacks” out there already, I figured, let’s just make one (assuming this hasn’t been done before).

## A few notes to get started

The following pre-requisites would be helpful to get things going smoothly

* A code editor or IDE (I’m using PyCharm)
    
* A willingness to try things
    
* Some knowledge of Django 🐍, React ⚛️, TypeScript & Tailwind CSS
    
* Like most things code, this is a work-in-progress
    

ℹ️ PyCharm community does not have NodeJs support so you will not get code completion when we get to the React part. Also, I’ll be using a separate terminal just for the purpose of getting screenshots. Normally I use PyCharm’s terminal.

## Framework & Language versions

* **D**jango v4.1 LTS
    
* **I**nertisJS 1.0
    
* **R**eact 18 / Vite 4
    
* **T**ailwind CSS 3
    

## Tested Platforms

✅ MacOS

✅ Ubuntu

✳️ Windows (I'm sure this will work)

## Django Project Setup

Before we get started, I’m going to give an overview of what we’re going to be using

* `pipenv` to manage virtual environments (If you like, you can use the more classical approach of using `virtualenv`).
    
* `Git` because it’s just a good idea
    

Everything else we need will be pretty standard. Since this is a mini-guide, we’re not going to worry about things like what database we’ll be using.

ℹ️ We’re going to be using `pipenv`. if you have not yet installed it, click [here](https://pipenv.pypa.io/en/latest/install/) for instructions.

ℹ️ You may choose to use `virtualenv` if you’d like

* Start by creating a new Django Project. We’ll call it `dirt-tutorial`
    

```bash
# create the directory
mkdir dirt-tutorial

# navigate to directory
cd dirt-tutorial
```

* Activate the virtual environment
    

```bash
pipenv shell
```

![Pipenv activate](https://res.cloudinary.com/drcpiax9p/image/upload/v1675292843/dev-tutorials/dirt-tutorial-1/01-creating-virtual-env.png)

### Installing Django

We’re going to need to install a few dependencies on both the Python / Django side as well as the React / Vite side. To make it easier, I’m giving you all the steps you need to get started.

* Install Django via `pipenv`
    

```bash
pipenv install django==4.1
```

ℹ️ Be sure to check to see if any security updates need to be installed for Django and update accordingly

* Create the project using `django-admin` in the current directory
    

```bash
django-admin startproject dirt_tutorial .
```

![https://res.cloudinary.com/drcpiax9p/image/upload/v1675293701/dev-tutorials/dirt-tutorial-1/02-install-django-create-project_jbvmut.png](https://res.cloudinary.com/drcpiax9p/image/upload/v1675293701/dev-tutorials/dirt-tutorial-1/02-install-django-create-project_jbvmut.png)

Now would be a good time to make sure everything is working as it should.

* Run the Django dev server using `./`[`manage.py`](http://manage.py)
    

```bash
./manage.py runserver
```

![https://res.cloudinary.com/drcpiax9p/image/upload/v1675299772/dev-tutorials/dirt-tutorial-1/03-django-working_vnsbjy.png](https://res.cloudinary.com/drcpiax9p/image/upload/v1675299772/dev-tutorials/dirt-tutorial-1/03-django-working_vnsbjy.png)

✳️ You can apply the unapplied migrations at this point if you would like.

We should also set up GIT at this point. So let’s go ahead and do that. I’m including a minimal `.gitignore` file that should suffice for our purposes.

* Initialize a new repo in the current directory (📂)
    

```bash
git init
```

* Create a `.gitignore` file and copy in the contents below. This is a minimal ignore file btw.
    

```plaintext
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# PyInstaller
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Django stuff:
*.log
db.sqlite3
db.sqlite3-journal

# Minimal Virtualenv
.venv

# Jetbrains
.idea

# Minimal NodeJS
dist
node_modules
```

![git ignore](https://res.cloudinary.com/drcpiax9p/image/upload/v1675301872/dev-tutorials/dirt-tutorial-1/04-git-ignore_zj3632.png)

Go ahead and commit your files. If you have a remote for your repo, you can push.

## Inertia Setup

Now that we have a working base project, let’s start adding the pieces of the puzzle that will allow us to make use of Inertia.

* Create a new branch from `main`, we can call it something like `setup-inertia`
    

```bash
git checkout -b setup-inertia
```

* Install dependencies for Inertia via `pipenv install`
    

```bash
pipenv install inertia-django django-vite
```

![https://res.cloudinary.com/drcpiax9p/image/upload/v1675304121/dev-tutorials/dirt-tutorial-1/06-inertia-deps_lnpnhh.png](https://res.cloudinary.com/drcpiax9p/image/upload/v1675304121/dev-tutorials/dirt-tutorial-1/06-inertia-deps_lnpnhh.png)

Now that we have our dependencies installed, let’s make the necessary updates to the files we need.

* Update [`settings.py`](http://settings.py) to include `django_vite` and `inertia` in `INSTALLED_APPS`, and `inertia.middleware.InertiaMiddleware` in `MIDDLEWARE`
    

```python
INSTALLED_APPS = [
    # default apps removed to save space

    # D.I.R.T Stack apps
    "django_vite", # <- add this app 
    "inertia", # <- add this app too
    # our apps
]

MIDDLEWARE = [
    # default middleware removed to save space

    # D.I.R.T Stack middleware
    "inertia.middleware.InertiaMiddleware", # <- add this
]
```

![Update settings.py](https://res.cloudinary.com/drcpiax9p/image/upload/v1675305530/dev-tutorials/dirt-tutorial-1/07-settings-updates_a4nsgr.png)

* Create a template file `./templates/base.html`. We’ll modify this a few times so you can keep it open in your editor or IDE
    

```html
{% load django_vite %}
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
				 <!-- vite hmr -->
        {% vite_hmr_client %}
        {% vite_asset 'src/main.js' %}
        <title>D.I.R.T Stack - Django Inertia React Tailwind CSS</title>
    </head>
    <body>      
        <!-- inertia -->
        {% block inertia %}{% endblock %}
    </body>
</html>
```

* Update the [`settings.py`](http://settings.py) to include the following change to `DIRS` as shown below
    

```python
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],  # <- update this
        # other templates removed to save space
    },
]
```

* While we’re still in the [`settings.py`](http://settings.py) file, let’s add the following settings to the end
    

```python
# D.I.R.T Stack Settings

INERTIA_LAYOUT = 'base.html'

# We need this for django form posting
CSRF_HEADER_NAME = 'HTTP_X_XSRF_TOKEN'
CSRF_COOKIE_NAME = 'XSRF-TOKEN'

# Where ViteJS assets are built.
DJANGO_VITE_ASSETS_PATH = BASE_DIR / 'react-app' / 'dist'

# If we should use HMR or not.
DJANGO_VITE_DEV_MODE = DEBUG

# we need this to get around cors issues
DJANGO_VITE_DEV_SERVER_HOST = '127.0.0.1'

# this is the default, but I'm leaving this here, so you know what to change if you want to run on a different port
DJANGO_VITE_PORT = 3000

# Name of our static files' folder (after called python manage.py collectstatic)
STATIC_ROOT = BASE_DIR / 'static'

# Include DJANGO_VITE_ASSETS_PATH into STATICFILES_DIRS to be copied inside
# when run command python manage.py collectstatic
STATICFILES_DIRS = [DJANGO_VITE_ASSETS_PATH]
```

Let’s get our initial `django-vite` setup going

ℹ️ We will be using `pnpm` for this project. That being said, you may use `npm` or `yarn` if you’d like. For installation instructions for `pnpm` click [here](https://pnpm.io/installation).

* At the root of our project, let’s create our `package.json`
    

```bash
pnpm init
```

* Install Vite 🚀 and Prettier
    

```bash
pnpm i -D vite prettier
```

![Vite install](https://res.cloudinary.com/drcpiax9p/image/upload/v1675309265/dev-tutorials/dirt-tutorial-1/09-install-vite-prettier_wny1ag.png)

* Create a `.prettierrc.json` at the root of our project with the following content (minimal)
    

```json
{
  "semi": true,
  "singleQuote": true,
  "tabWidth": 2,
  "trailingComma": "es5"
}
```

* Create `vite.config.js` at the root of our project with the following content
    

```jsx
import { resolve }  from 'path';

module.exports = {
 plugins: [],
 root: resolve('./react-app'),
 base: '/static/',
 server: {
   host: 'localhost',
   port: 3000,
   open: false,
   watch: {
     usePolling: true,
     disableGlobbing: false,
   },
 },
 resolve: {
   extensions: ['.js', '.json'],
 },
 build: {
   outDir: resolve('./react-app/dist'),
   assetsDir: '',
   manifest: true,
   emptyOutDir: true,
   target: 'es2015',
   rollupOptions: {
     input: {
       main: resolve('./react-app/src/main.js'),
     },
     output: {
       chunkFileNames: undefined,
     },
   },
 },
};
```

* create the folder `./react-app/dist` since this is where our built assets will eventually end up
    
* create our main file `./react-app/src/main.js` (we’ll rename this to `main.jsx` later as this will be our Inertia entry point)
    

```jsx
alert('Hello DIRT Stack!');
```

We’re almost ready to get things going. Let’s update our `package.json` file so that we can run Vite

* Update the `scripts` section our `package.json` as shown below
    

```json
{
	"scripts": {
    "vite-build": "vite build",
    "vite-dev": "vite",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
}
```

* Create a views file `./dirt_tutorial/views.py` with the following content
    

```python
from django.shortcuts import render

def index(request):
    return render(request, template_name="base.html")
```

* Update our main url pattern file `./dirt_tutorial/urls.py` as follows
    

```python
from django.contrib import admin
from django.urls import path
from dirt_tutorial import views  # <- this was added

urlpatterns = [
    path('', views.index, name='home'),  # <- this was added
    path('admin/', admin.site.urls),
]
```

Now we can test things out. Go ahead and start the Django dev server in a separate terminal and then start Vite.

```bash
./manage.py runserver

# in another terminal
pnpm run vite-dev
```

If everything worked, you should see the following results

![Vite](https://res.cloudinary.com/drcpiax9p/image/upload/v1675311420/dev-tutorials/dirt-tutorial-1/10-vite-pnpm_j2bwqw.png)

![Vite Running](https://res.cloudinary.com/drcpiax9p/image/upload/v1675311335/dev-tutorials/dirt-tutorial-1/10-vite-running_xgsvij.png)

✳️ This would be a good time to commit the changes and prepare to move on to the next section. You can also merge these changes into the main branch.

## React (Vite Setup)

We’re now at the point where we can add in React and the frontend part for InertiaJS. Let’s go ahead and get set up.

* Create a new branch
    

```bash
git checkout -b setup-react
```

ℹ️ If you have Vite running, now would be a good time to stop it.

* Install the dependencies for React
    

```bash
pnpm i -D react react-dom @vitejs/plugin-react
```

* Install the dependencies for TypeScript
    

```bash
pnpm i -D typescript ts-loader @types/react @types/react-dom
```

![Install React & TypeScript](https://res.cloudinary.com/drcpiax9p/image/upload/v1675462341/dev-tutorials/dirt-tutorial-1/11-install-react-ts-deps_kvmt5l.png)

* Install InertiaJS
    

```bash
pnpm i -D @inertiajs/react
```

* Create a `tsconfig.json` file in the root of our project and copy the contents below
    

```json
{
 "compilerOptions": {
   "target": "ESNext",
   "useDefineForClassFields": true,
   "lib": ["DOM", "DOM.Iterable", "ESNext"],
   "allowJs": false,
   "skipLibCheck": true,
   "esModuleInterop": false,
   "allowSyntheticDefaultImports": true,
   "strict": true,
   "forceConsistentCasingInFileNames": true,
   "module": "ESNext",
   "moduleResolution": "Node",
   "resolveJsonModule": true,
   "isolatedModules": true,
   "noEmit": true,
   "jsx": "react-jsx",
   "types": ["vite/client"],
 },
 "include": ["react-app/src"],
}
```

* Update our `vite.config.js` file to work with React
    

```jsx
import { resolve } from 'path';
import react from '@vitejs/plugin-react';

module.exports = {
	// this was changed
  plugins: [
    react({
      include: '**/*.disabled',
    }),
  ],
  root: resolve('./react-app'),
  base: '/static/',
  server: {
    host: 'localhost',
    port: 3000,
    open: false,
    watch: {
      usePolling: true,
      disableGlobbing: false,
    },
  },
  resolve: {
    extensions: ['.js', '.json'],
  },
  build: {
    outDir: resolve('./react-app/dist'),
    assetsDir: '',
    manifest: true,
    emptyOutDir: true,
    target: 'es2015',
    rollupOptions: {
      input: {
        main: resolve('./react-app/src/main.jsx'), // <- renamed from main.js
      },
      output: {
        chunkFileNames: undefined,
      },
    },
  },
};
```

Before we make our changes to `main.js`, we’ll need to make our index page which will be imported into our main file.

* Create our Index component `./react-app/src/pages/Home/Index.tsx` with the content below (or anything you want to use)
    

```tsx
import * as React from 'react';

const Index = (): React.ReactNode => {
  return (
    <div>
      <h1>D.I.R.T Stack Tutorial</h1>
      <p>The D.I.R.T Stack consists of 4 major pieces</p>
      <ul>
        <li><strong>D</strong>jango</li>
        <li><strong>I</strong>nertia</li>
        <li><strong>R</strong>eact</li>
        <li><strong>T</strong>ailwind CSS</li>
      </ul>
    </div>
  )
}

export default Index;
```

* Rename `main.js` to `main.jsx` and update the contents as follows
    

```tsx
import {createRoot} from 'react-dom/client';
import {createInertiaApp} from '@inertiajs/react';

document.addEventListener('DOMContentLoaded', () => {
  createInertiaApp({
    resolve: (name) => {
      const pages = import.meta.glob('./pages/**/*.tsx', { eager: true });
      return pages[`./pages/${name}.tsx`];
    },
    setup({ el, App, props }) {
      createRoot(el).render(<App {...props} />);
    }
  }).then(() => {});
});
```

This should cover the React side of things, for now at least. Let’s look at setting up the Django side of things so we can have our view rendered.

* Update `./dirt_tutorial/views.py` as follows
    

```python
# from django.shortcuts import render  # <- remove this
from inertia import inertia  # <- add this

@inertia('Home/Index')  # <- add this
def index(request):
		# return render(request, template_name="base.html")  # <- remove this
		return {}  # <- add this
```

* Edit `./templates/base.html` to point to our newly renamed `main.jsx` file
    

```html
{% load django_vite %}
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <!-- vite hmr -->
        {% vite_hmr_client %}
        {% vite_asset 'src/main.jsx' %} <!-- rename main.js to main.jsx -->
        <title>D.I.R.T Stack - Django Inertia React Tailwind CSS</title>
    </head>
    <body>
        <!-- inertia -->
        {% block inertia %}{% endblock %}
    </body>
</html>
```

At this point, we can go ahead and make sure things are working.

* Start both the Django dev server and Vite and navigate to [http://127.0.0.1:8000](http://127.0.0.1:8000)
    

ℹ️ You should see something like the window below without any console errors. If you do, have a 🍪 or a 🍺.

![React & InertiaJ](https://res.cloudinary.com/drcpiax9p/image/upload/v1675464697/dev-tutorials/dirt-tutorial-1/12-react-inertiajs-ts_acbyyk.png)

* Commit your changes and merge them into `main`.
    

## Tailwind CSS setup

We’re at the final part of setting up the D.I.R.T Stack. So, let’s take care of all styling needs for this project.

* Stop Vite and then create a new branch
    
* Install `tailwindscss` and recommended dependencies along with `concurrently`
    

```bash
pnpm i -D tailwindcss postcss autoprefixer concurrently
```

* Initialize tailwind css
    

```html
pnpx tailwindcss init -p
```

![Initialising Tailwind CSS](https://res.cloudinary.com/drcpiax9p/image/upload/v1675466093/dev-tutorials/dirt-tutorial-1/13-tailwindcss-init_o8klp8.png)

ℹ️ Be sure to add the generated files (`postcss.config.js` and `tailwind.config.js`) to version control

* Update the generated tailwind config `tailwind.config.js` as follows
    

```jsx
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ['./templates/**/*.html', './react-app/src/**/*.{js,jsx,ts,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
};
```

* Create a `static` directory at the root of the project
    
* Create our main CSS file `./static/css/main.css` and add the following
    

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

* update `package.json` to include a script that will run `tailwindcss` and `vite` via `concurrently`
    

```json
"scripts": {
  "vite-build": "vite build",
  "vite-dev": "vite",
  "tailwind-dev": "tailwindcss -i ./static/css/main.css -o ./static/dist/css/app.css --watch",
  "dirt-dev": "concurrently \"npm run tailwind-dev\" \"npm run vite-dev\" ",
  "test": "echo \"Error: no test specified\" && exit 1"
},
```

* Update our [`settings.py`](http://settings.py) file so that we can serve our static files
    

```python
# Name of our static files' folder (after called python manage.py collectstatic)
# STATIC_ROOT = BASE_DIR / 'static'  # <- remove this

# Include DJANGO_VITE_ASSETS_PATH into STATICFILES_DIRS to be copied inside
# when run command python manage.py collectstatic
STATICFILES_DIRS = [
    BASE_DIR / 'static',
    DJANGO_VITE_ASSETS_PATH
]
```

* Update our base template `./templates/base.html` to include the link to generated CSS.
    

```html
{% load static %}
{% load django_vite %}
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1"/>
        <!-- css -->
        <link rel="stylesheet" href="{% static 'dist/css/app.css' %}" />
        <!-- vite hmr -->
        {% vite_hmr_client %}
        {% vite_asset 'src/main.jsx' %}
        <title>D.I.R.T Stack - Django Inertia React Tailwind CSS</title>
    </head>
    <body>
        <!-- inertia -->
        {% block inertia %}{% endblock %}
    </body>
</html>
```

* run our newly added script via `pnpm`
    

```bash
pnpm run dirt-dev
```

![let's do things...concurrently](https://res.cloudinary.com/drcpiax9p/image/upload/v1675467516/dev-tutorials/dirt-tutorial-1/14-dirt-dev-concurrently_ah02xr.png)

![D.I.R becomes D.I.R.T](https://res.cloudinary.com/drcpiax9p/image/upload/v1675478684/dev-tutorials/dirt-tutorial-1/15-tailwind-working_lmaicn.png)

✅ Should work without issues

* Edit our Index component `./react-app/src/page/Home/Index.tsx` to include some Tailwind CSS classes
    

```tsx
import * as React from 'react';
const Index = (): React.ReactNode => {
  return (
    <div className="flex flex-col gap-y-4 p-2">
      <h1 className="text-blue-800 text-4xl">D.I.R.T Stack Tutorial</h1>
      <div className="bg-blue-100 border-blue-800 border-2 flex flex-col p-4 rounded">
        <p className="text-blue-800 text-lg">The D.I.R.T Stack consists of 4 major pieces</p>
      </div>
      <ul className="text-2xl">
        <li><span className="font-semibold">D</span>jango</li>
        <li><span className="font-semibold">I</span>nertia</li>
        <li><span className="font-semibold">R</span>eact</li>
        <li><span className="font-semibold">T</span>ailwind CSS</li>
      </ul>
    </div>
  )
}

export default Index;
```

![Hello D.I.R.T](https://res.cloudinary.com/drcpiax9p/image/upload/v1675483887/dev-tutorials/dirt-tutorial-1/16-tailwind-classes_hhy4rx.png)

Not bad so far, right? So, in summary here’s a rundown of what we have done so far

* Setup a base Django project
    
* Setup Inertia
    
* Setup React with Vite
    
* Install and configure Tailwind CSS
    
* At this point, we can go ahead and commit our changes so that we can proceed to the next section.
    

## Bonus: Storybook Integration (Experimental)

Now, you guys know I am a fan of tools that allow for the previewing of components as we build them.

We’ll be using Storybook which is one of my favorite tools. For more information about Storybook, click [here](https://storybook.js.org/). And with the integration of Storybook, we can call this D.I.R.T+.

⚠️ Important Note: For Storybook to work with our D.I.R.T Stack, do not use the installation instructions provided by Storybook as our project does not conform to a typical structure.

### Storybook Manual Setup

To get Storybook working, a manual setup is required. The steps are below

* Create a new branch for this section
    
* Install the dependencies
    

```shell
# storybook manual setup

# add dependencies via PNPM or NPM or Yarn
pnpm add -D @storybook/builder-vite @storybook/react @storybook/client-api @storybook/client-logger
@storybook/addon-links @storybook/addon-essentials @storybook/addon-docs 
@storybook/addon-actions @storybook/addon-backgrounds @storybook/addon-measure 
@storybook/addon-outline @babel/core@^7.0.0
```

* Create a Storybook config file `.storybook/main.js` at the root of the project and paste the following
    

```jsx
// .storybook/main.js

module.exports = {
  stories: [
    '../react-app/src/**/*.stories.mdx',
    '../react-app/src/**/*.stories.@(js|jsx|ts|tsx)',
  ],
  addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
  core: {
    builder: '@storybook/builder-vite',
  },
};
```

* Create a preview file for Storybook `./storybook/preview.js`
    

```jsx
import "../static/dist/css/app.css";

export const parameters = {
  actions: { argTypesRegex: "^on[A-Z].*" },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
}
```

* Update `package.json` to include a script to start `Storybook`.
    

```json
{
	"scripts": {
		/* other scripts removed to save space. copy line below */
		"storybook": "start-storybook -p 6006"
	}
}
```

![ created showing contents](https://res.cloudinary.com/drcpiax9p/image/upload/v1675728511/dev-tutorials/dirt-tutorial-1/17.1-storybook-main_s4yxsl.png)

`main.js` created showing contents

ℹ️ [Storybook CLI reference](https://storybook.js.org/docs/web-components/api/cli-options)

* Run storybook via `pnpm`
    

```shell
pnpm run storybook
```

ℹ️ If everything worked, then a new browser tab should open with StoryBook. At this point, since we don’t have any stories, we will be greeted with a message telling us that we don’t have any stories.

![Storybook running without errors](https://res.cloudinary.com/drcpiax9p/image/upload/v1675727913/dev-tutorials/dirt-tutorial-1/18.pnpm-storybook_pjcebt.png)

![No stories found](https://res.cloudinary.com/drcpiax9p/image/upload/v1675727962/dev-tutorials/dirt-tutorial-1/19-storybook-initial_yi3qfg.png)

You’ll notice that Storybook isn’t too happy with us since we have no stories defined. So we’ll have to fix that. Let’s proceed.

### Creating our first story

At this point, we can create stories based on components. For this, we will create a few components and the accompanying story files.

Before we do that, let’s get a few things installed.

* Install `headlessui` via pnpm.
    

```bash
pnpm add -d @headlessui/react
```

* Create a Button component `./react-app/src/components/shared/Button/Button.tsx`
    

```tsx
import * as React from 'react';
import type { FC, ButtonHTMLAttributes } from 'react';

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  labelText: string;
  overrideButtonClass?: string;
}

export const Button: FC<ButtonProps> = ({
  labelText,
  overrideButtonClass,
  ...props
}) => {
  return (
    <button
      className={[
        'px-4 py-2 bg-green-800 hover:bg-green-600 text-white text-lg rounded duration-200',
        overrideButtonClass,
      ].join(' ')}
      {...props}
    >
      {labelText}
    </button>
  );
};
```

* Create the corresponding Storybook file adjacent to the Button component `Button.stories.tsx`
    

```tsx
import type {ComponentMeta, ComponentStory} from '@storybook/react';
import {Button} from './Button';

export default {
  title: 'Shared/Button',
  component: Button,
} as ComponentMeta<typeof Button>;

const Template: ComponentStory<typeof Button> = args => <Button {...args} />;

export const Primary = Template.bind({});
Primary.args = {
  labelText: 'Primary Button',
}
```

We should see our newly created Button story file as shown below

![Button Story](https://res.cloudinary.com/drcpiax9p/image/upload/v1675729859/dev-tutorials/dirt-tutorial-1/20-button-story_tnsa9r.png)

* Create a Confirm Dialog component `./react-app/src/components/shared/ConfirmDialog/ConfirmDialog.tsx`
    

```tsx
import * as React from 'react';
import type { FC } from 'react';
import { Dialog } from '@headlessui/react';
import { Button } from '../Button/Button';

interface ConfirmDialogProps {
  cancelAction: () => void;
  confirmAction: () => void;
  dialogTitle: string;
  overrideCancelText?: string;
  overrideConfirmText?: string;
  promptText: string;
  titleText: string;
  visible: boolean;
}

export const ConfirmDialog: FC<ConfirmDialogProps> = ({
  overrideConfirmText,
  overrideCancelText,
  promptText,
  titleText,
  confirmAction,
  visible,
  cancelAction,
}) => {
  return (
    <Dialog onClose={cancelAction} open={visible}>
      <div className="fixed inset-0 bg-black/30" aria-hidden="true" />
      <div className="fixed inset-0 flex items-center justify-center p-4">
        <Dialog.Panel className="mx-auto max-w-sm rounded bg-white p-4 rounded">
          <div className="flex flex-col items-center">
            <Dialog.Title className="font-normal text-2xl text-slate-600 ml-0">
              {titleText}
            </Dialog.Title>
          </div>
          <div className="mt-2">
            <p className="text-center text-lg text-slate-600">{promptText}</p>
          </div>
          <div className="flex flex-1 flex-col mt-6 items-center">
            <div className="flex gap-x-2">
              <Button
                onClick={cancelAction}
                overrideButtonClass="bg-slate-800 hover:bg-slate-500"
                labelText={overrideCancelText ? overrideCancelText : 'Cancel'}
              />
              <Button
                onClick={confirmAction}
                labelText={
                  overrideConfirmText ? overrideConfirmText : 'Confirm'
                }
              />
            </div>
          </div>
        </Dialog.Panel>
      </div>
    </Dialog>
  );
};
```

* Create the corresponding Storybook file adjacent to Confirm Dialog component `ConfirmDialog.stories.tsx`
    

```tsx
import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { ConfirmDialog } from './ConfirmDialog';

export default {
  title: 'Shared/ConfirmDialog',
  component: ConfirmDialog,
} as ComponentMeta<typeof ConfirmDialog>;

const Template: ComponentStory<typeof ConfirmDialog> = (args) => (
  <ConfirmDialog {...args} />
);

export const Primary = Template.bind({});
Primary.args = {
  visible: false,
  promptText: 'You are about to do the thing, would you like to continue?',
  titleText: 'Do the thing?',
  cancelAction: () => {},
  confirmAction: () => {},
};
```

We should see our newly created Confirm Dialog story below

![Confirm Dialog story](https://res.cloudinary.com/drcpiax9p/image/upload/v1675729860/dev-tutorials/dirt-tutorial-1/21-dialog-story_hdqn0r.png)

If everything worked, then we can celebrate 🎉. This concludes the D.I.R.T+ Stack setup with Storybook.

⚠️ Currently there is an issue with Storybook that requires the page to be reloaded manually. So, if you see something that looks like the screen below when adding a new story file, just reload it (for now). I’m looking for a solution to this issue.

![Storybook error](https://res.cloudinary.com/drcpiax9p/image/upload/v1675782692/dev-tutorials/dirt-tutorial-1/22-story-error_t5vp8q.png)

```shell
# The error as it is.
Cannot overwrite a zero-length range – use appendLeft or prependRight instead
9:49:26 PM [vite] Internal server error: Cannot overwrite a zero-length range – use appendLeft or prependRight instead
```

## References:

The following resources were helpful in the writing of this:

* [Storybook documentation](https://storybook.js.org/docs/react/builders/vite)
    
* [Storybook CLI reference](https://storybook.js.org/docs/web-components/api/cli-options)
    
* [Inertia](https://inertiajs.com/)
    
* [Pipenv Installation Docs](https://pipenv.pypa.io/en/latest/install/)
    

## What's next?

I have things related to D.I.R.T that I'm working on (starter template, etc). Stay tuned for updates. Also, I'm going to be refining the approach and optimizing a few things