DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Sybren W
Sybren W

Posted on

The one with styling Zag.js components with Tailwind CSS

When building a web application, I would advise using Chakra UI Vue to speed up your development and have accessibility build in.
But sometimes the project requirements are different, and you have to use Tailwind CSS, for example.

There is no reason to not start from zero, you can use Zag.js components with Vue.js (or React or Solid.js). Those components come with all the logic and accessibility in mind that you need to have a solid foundation.

Let’s go over how we can style Zag.js components using Tailwind CSS as CSS Framework. I will use the accordion component as an example, the purpose of this post is to show how you can set up Tailwind CSS and use it to add styling to the Zag.js components you are using.

A StackBlitz playground can be found here: https://stackblitz.com/edit/vue-zag-tailwind?file=src/App.vue

For those who do not know Zag.js, let’s start there first.

Zag.js

Zag is a toolkit that provides framework-agnostic UI components powered by State Machines. The components are build with accessibility in mind and handle all the logic for you. They are completely headless and unstyled, which gives you the full control to use your favourite styling solution.

Zag.js currently works seamless with React, Vue.js and Solid.js. In this blog, we will use Vue.js.

The Zag.js documentation can be found here: https://zagjs.com/.

Styling solution

As you might have guessed from the title, the styling solution will be Tailwind CSS. Let’s assume you do know about this CSS Framework, documentation can be found here: https://tailwindcss.com/.

Getting started

We will probably start with setting up our Vue.js project, using the Vue CLI and use Vite for example. When this has been done, we can easily add Zag.js to our project. If we scan the documentation, we quickly notice we first will have to pick a component we would like to use and style. Lets for example say the Accordion component. Documentation here: https://zagjs.com/components/vue-sfc/accordion.

We will have to install the machine for this component, which is completely framework-agnostic.

yarn add @zag-js/accordion
Enter fullscreen mode Exit fullscreen mode

Then comes the part to connect this machine with the Vue.js framework. For this, we need to install the adapter.

yarn add @zag-js/vue
Enter fullscreen mode Exit fullscreen mode

Just to play around with the accordion, we will add it to the App.vue file. For more information about what the Zag.js code does, please take a look at the documentation of Zag.js.

<script setup>
import * as accordion from '@zag-js/accordion'
import { normalizeProps, useMachine } from '@zag-js/vue'
import { computed } from 'vue'

const data = [
    { title: 'Watercraft', content: 'Sample accordion content' },
    { title: 'Automobiles', content: 'Sample accordion content' },
    { title: 'Aircrafts', content: 'Sample accordion content' },
]
const [state, send] = useMachine(accordion.machine({ id: '1' }))
const api = computed(() => accordion.connect(state.value, send, normalizeProps))
</script>

<template>
    <div ref="ref" v-bind="api.rootProps">
        <div
            v-for="item in data"
            :key="item.title"
            v-bind="api.getItemProps({ value: item.title })"
        >
            <h3>
                <button
                    v-bind="api.getTriggerProps({ value: item.title })"
                >
                    {{ item.title }}
                    <svg
                        data-accordion-icon
                        fill="currentColor"
                        viewBox="0 0 20 20"
                        xmlns="http://www.w3.org/2000/svg"
                    >
                        <path
                            fill-rule="evenodd"
                            d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
                            clip-rule="evenodd"
                        ></path>
                    </svg>
                </button>
            </h3>
            <div
                v-bind="api.getContentProps({ value: item.title })"
            >
                {{ item.content }}
            </div>
        </div>
    </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Inside the accordion header, we have added an SVG which is an arrow to indicate the state of the accordion item. Right now we notice our accordion component has any styling to it, the SVG arrow is not rotating or anything. It’s time to add Tailwind CSS to make this all possible.

Unstyled Zagjs Accordion component

Adding styling

The Zag.js documentation does show a styling guide on how to style each accordion part with CSS here.
We won’t be using this solution since we would like to use the Tailwind CSS framework.

First, we will have to install Tailwind CSS, and it’s dependencies. Then run the init command to generate the following Tailwind CSS files: tailwind.config.cjs
and postcss.config.cjs.

yarn add tailwindcss postcss autoprefixer -D

npx tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode

Inside the generated tailwind.config.cjs file, we add the paths to all our/ template files.

// tailwind.config.cjs
module.exports = {
  content: [
    "./index.html",
    "./src/**/*.{vue,js,ts,jsx,tsx}",
  ],
  ...
}
Enter fullscreen mode Exit fullscreen mode

The last thing we need to do before adding Tailwind CSS classes and running our project is adding the @tailwind directives to our ./src/style.css file.

<!-- style.css -->
@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

Now we can run the project and add some styling!

Add Tailwind CSS styling

We can start with adding some styling to the header of our accordion component by adding the following classes, for example.

<template>
...
    <h3>
        <button
            v-bind="api.getTriggerProps({ value: item.title })"
            class="flex justify-between items-center p-5 w-full font-medium text-left text-gray-500 rounded-t-xl border border-b-0 border-gray-200 focus:ring-4 focus:ring-gray-200 hover:bg-gray-100"
            >
            ...
        </button>
    </h3>
...
</template>
Enter fullscreen mode Exit fullscreen mode

Inside the accordion header we can now add the style logic to have the arrow SVG turn depending on the state. If the Zag machine api value is the same as the accordions item title this accordion item is open.

<template>
...
    <svg
        data-accordion-icon
        class="w-6 h-6 shrink-0"
        :class="{ 'rotate-180': api.value == item.title }"
        fill="currentColor"
        viewBox="0 0 20 20"
        xmlns="http://www.w3.org/2000/svg"
    >
        <path
            fill-rule="evenodd"
            d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
            clip-rule="evenodd"
        ></path>
    </svg>
...
</template>
Enter fullscreen mode Exit fullscreen mode

Some small styling to the accordion content, and we have a nicely styled accordion that is also accessible.

<template>
    ...
    <div
        v-bind="api.getContentProps({ value: item.title })"
        class="p-5 border border-b-0 border-gray-200"
        >
        {{ item.content }}
    </div>
...
</template>
Enter fullscreen mode Exit fullscreen mode

Styled Zagjs Accordion component with Tailwind CSS

Slot

That’s it! Not that complicated to set up and a pleasure to use!
I do know I didn’t put much time or thought into the styling itself, feel free to make it better! I would also like to challenge you to make the accordion component reusable.
Note that setting this up for a React project with Vite is very similar.

Sources

StackBlitz playground: https://stackblitz.com/edit/vue-zag-tailwind?file=src/App.vue

Zag.js Documentation: https://zagjs.com/

Tailwind CSS Documentation: https://tailwindcss.com/

Top comments (1)

Collapse
 
psysolix profile image
PsySolix

Great stuff!

Timeless DEV post...

How to write a kickass README

Arguably the single most important piece of documentation for any open source project is the README. A good README not only informs people what the project does and who it is for but also how they use and contribute to it.

If you write a README without sufficient explanation of what your project does or how people can use it then it pretty much defeats the purpose of being open source as other developers are less likely to engage with or contribute towards it.