Dynamic component styles in Vue (Composition API and TypeScript) using Tailwind CSS and Lookup tables
Tags: vue, css, webdev, javascript
Published: 2020-09-29 13:28:59

> This article will assume some knowledge of Vue

You can find the options API version [here](https://dev.to/wearethreebears/dynamic-component-styles-in-vue-options-api-using-tailwind-css-and-lookup-tables-3nac).

Last week I wrote an article about how I choose to utilize lookup tables when working with dynamic component styles in Nuxt, which you can find [here](https://dev.to/wearethreebears/dynamic-component-styles-in-nuxt-using-tailwind-css-and-lookup-tables-4k59). This week I've been working with Vue 3 and just like when using Nuxt, in Vue I utilize lookup tables when working with dynamic component styles, here's how:

# Tailwind and PurgeCSS

Tailwind CSS is one of the hottest topics in frontend development right now. A utility first CSS framework, created by Adam Wathan, which over the past few years has grown from side project to successful business. If you've ever used Tailwind, you may be aware that it utilizes PurgeCSS at build time to prune unused styles and create a slimline stylesheet made up of only the classes used in your web application. Many frameworks now make use of PurgeCSS to remove unnecessary bulk from production stylesheets at build time.

PurgeCSS is a fantastic plugin, however, it cannot parse or run JavaScript and in most cases is only used at build time. Because of this, it can lead to unexpected inconsistencies between development and production environments if used incorrectly.

# Starting a fresh Vue project with Tailwind CSS

Let's begin by creating a fresh Vue installation by opening your terminal and running the following command:

`vue create <your-project-name>`

We'll follow the CLI instructions to set up Vue with the "manually select features" option, ensuring to check "Choose Vue version" and "TypeScript" from the features list, the rest can be left as deffault. Once our project has finished setting up, we can navigate to the root directory and install Tailwind CSS with the following command:

`npm install tailwindcss`

Once Tailwind CSS has successfully installed, we'll need to create our `tailwind.config.js` using the following command:

`npx tailwindcss init`

When the `tailwind.config.js` file has been created we will need to configure it to scan our `.vue` files for classes. First we'll uncomment the properties in the `future` object, this will make upgrading easier in future. Next, within the `purge` array, add the following line:

`'src/**/*.vue',`

Now we can 
create our Tailwind stylesheet. Navigate to the `src/assets` folder, create a new directory called `css` and within it create a new file called `styles.css` and populate it with the Tailwind CSS imports:

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

Now that the stylesheet is set up we can import it into our application by opening the `main.js` file from within the `src` directory and adding the following line:

```js
import '@/assets/css/styles.css';
```

Finally we need to create our PurgeCSS config file, again in the project root, create a new file, this time called `postcss.config.js` and configure it with the following code:

```js
// postcss.config.js

const autoprefixer = require('autoprefixer');
const tailwindcss = require('tailwindcss');

module.exports = {
    plugins: [
        tailwindcss,
        autoprefixer,
    ],
};
```

# Dynamic component styles in Vue with Tailwind

One of the key features of components in Vue is the ability to pass props. Props are custom attributes passed to a component that can be used to control appearance and function. Let's look at creating a simple button component using Tailwind that accepts two colorways, 'primary' and 'secondary':


```vue
<template>
    <button 
        class="px-4 py-2 text-center transition-colors duration-300 ease-in-out border border-solid rounded shadow-md"
        :class="{ 
            'bg-blue-800 text-white border-blue-800 hover:bg-transparent hover:text-blue-800 hover:border-blue-800' : color == 'primary',
            'bg-transparent text-blue-800 border-blue-800 hover:bg-blue-800 hover:text-white hover:border-blue-800' : color == 'secondary'
        }"
    >
        <slot />
    </button>
</template>

<script lang="ts">
    import { defineComponent, PropType } from 'vue'

    export default defineComponent({
        name: 'component--button',

        props: {
            color: {
                required: false,
                type: String as PropType<string>,
                default: 'primary',
                validator: value => {
                    return ['primary', 'secondary'].includes(value)
                }
            }
        }
    })
</script>
```

So we have our button component that accepts 2 dynamic colorways, 'primary' and 'secondary', exactly as we'd set out, however even in this simple component it's easy to see how these dynamic styles could spiral out of control in more complex components. We also have a color props validator which we have to manually keep in sync with the dynamic styles in the template.

# Extracting styles and keeping validators synced with Lookup tables

If you haven't heard of a lookup table, a lookup table is a simple key-value pair object we can use to match keys to data. We can take advantage of lookup tables to extract the dynamic styles and ensure our validator always stays in sync with those dynamic styles. 

For this example, we'll be creating a new folder in the `src` directory called `validators` to store our lookup tables, although the same technique could be used to make use of lookup tables within the component file if preferred. Once you've created a new folder called `validators`, create a new file inside called `Button.ts`. Inside `Button.ts` we are going to export a `const` called `ButtonColors`, a lookup table which will contain our key-value pairs for our dynamic styles, like so: 

```js
export const ButtonColors = {
    'primary': 'bg-blue-800 text-white border-blue-800 hover:bg-transparent hover:text-blue-800 hover:border-blue-800',
    'secondary': 'bg-transparent text-blue-800 border-blue-800 hover:bg-blue-800 hover:text-white hover:border-blue-800'
}
```

Now we have extracted our dynamic styles to a lookup table we need to make a couple of changes to our component, firstly, beneath the opening script tag we need to import our `ButtonColors const`:

```vue
<script lang="ts">
/**/
import { ButtonColors } from '../validators/Button'

export default defineComponent({/**/})
</script>
```

Next in our `color` prop we need to let TypesScript know that the string should be equal to one of the keys of our ButtonColors lookup table and replace the array current array of strings with an array of keys from the `ButtonColors` lookup table:

```vue
<script lang="ts">
    export default defineComponent({
        /**/
        props: {
            color: {
                required: false,
                type: String as PropType<keyof typeof ButtonColors>,
                default: 'primary',
                validator: (value : string ) => {
                    return Object.keys(ButtonColors).includes(value)
                }
            }
        }
        /**/
    })
</script>
```

Now we can create a computed property to handle the dynamic classes in the component template, first we need to import `computed` from `vue`, in the same way we have with `defineComponent` and `PropType`:

```vue
<script>
    import { defineComponent, PropType, computed } from 'vue'
    /**/
</script>
```

Now we can use the `setup` method and pass it `props` as a parameter to allow us to set up a computed `buttonColor` property like so:

```vue
<script>
/**/
export default defineComponent({
    /**/
    setup(props) {
        const buttonColor = computed(() : string  => {
            return ButtonColors[props.color]
        })

        return {
            buttonColor
        }
    }
})
</script>
```

We can then replace all of the dynamic classes in the template with our new computed property:

```vue
<template>
    <button 
        class="px-4 py-2 text-center transition-colors duration-300 ease-in-out border border-solid rounded shadow-md"
        :class="[buttonColor]"
    >
        <slot />
    </button>
</template>
```

Altogether that should give us a component template that looks like this:

```vue
<template>
    <button 
        class="px-4 py-2 text-center transition-colors duration-300 ease-in-out border border-solid rounded shadow-md"
        :class="[buttonColor]"
    >
        <slot />
    </button>
</template>

<script lang="ts">
    import { defineComponent, PropType, computed } from 'vue'

    import { ButtonColors } from '../validators/Button'

    export default defineComponent({
        name: 'component--button',

        props: {
            color: {
                required: false,
                type: String as PropType<keyof typeof ButtonColors>,
                default: 'primary',
                validator: (value : string ) => {
                    return Object.keys(ButtonColors).includes(value)
                }
            }
        },

        setup(props) {
            const buttonColor = computed(() : string  => {
                return ButtonColors[props.color]
            })

            return {
                buttonColor
            }
        }
    })
</script>
```

Everything is looking great, our dynamic styles are extracted and our validators will automatically stay in sync with any new dynamic styles we add, however unfortunately at the moment our component will still not be styled as expected in production. Thankfully, there's a very simple fix, open up `tailwind.config.js` from the root of the project and within the `purge` array, add `'src/validators/*.js'`, this will tell PurgeCSS to check for styles in our validators folder, our final `purge` object should look something like this:

```js
module.exports = {
/**/
    purge: [
        'src/**/*.vue',
        'src/validators/*.ts'
    ]
/**/
}
```

# Testing

If you'd like to test your lookup tables are working correctly in production you can test your project in production locally. Begin by installing the Node.js static file server:

`npm install -g serve`

Once installed, navigate to the root of your project and run:

`serve -s dist`

# Conclusion

Hopefully, you've found this a useful exercise in cleaning up your dynamic classes in Vue options API, Tailwind, and PurgeCSS. 

If you’ve found this article useful, please follow me on [Medium](https://medium.com/@wearethreebears), Dev.to and/ or [Twitter](https://twitter.com/wearethreebears).