How to build a details drop-down with VueJS and TailwindCSS
Tags: tailwindcss, vue, html, details
Published: 2022-08-17 07:27:00


# Introduction

> Requires a basic understanding of html, vue and tailwindcss.

It turns out that the default styling of the `<details>` tag is prety ugly because of this,
most people just decide not to use it and fallback to `divs`, `h1` and `ul` with some kind
of state management to support it.

It basically looks like this closed without the styling

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uw64gz74e0q35w128ijh.png)

And this when open

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/miqtnl4cfsvqjgk69tql.png)


But with Tailwind and a bit of vue magic, we can achieve this

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2w63wzteax5mxhc5sqjf.png)
when open.


With no time to waste, let's get started.

## Project Structure

To incorporate this with your vue website, you will need not to start a new `npm` or `yarn` or `pnpm` vue app.

I personally used pnpm and vite for this project

```bash
pnpx create-vite .
pnpm install . 
pnpm install tailwindcss autoprefixer postcss 
tailwindcss init -p
pnpm run dev
```

All you need is your component file and tailwindcss enabled. It should look nothing less than this.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/exy6tfy94mjyh084xew8.png)

For best results, Add a vector library you like. But in this project, I will be using [font-awesome](https://fontawesome.com).

I included the vector library by adding this script tag to the `index.html`

```html
    <script defer src="https://use.fontawesome.com/releases/v5.15.4/js/all.js" integrity="sha384-rOA1PnstxnOBLzCLMcre8ybwbTmemjzdNlILg8O7z1lUkLXozs4DHonlDtnE7fpc" crossorigin="anonymous"></script>
```

Don't forget to import the index.css file into your main.js file.

## App.vue
Nothing much here. Just basic imports and data declaration. 
```html
<template>
  <custom-details :title="heading" :data="tools" />
</template>

<script>
import CustomDetails from './components/custom-details.vue';
export default {
  components: { CustomDetails },
  data(){
    return {
      heading: "Tools for building a website",
      tools: ["HTML", "CSS", "JavaScript", "VueJS", "ReactJS", "Vite"]
    }
  }
}
</script>
```

## custom-details component

In out custom details component, lets write down some templates.

```html
<template>
  <details>
    <summary>
      <div>
        <h2> {{title}} </h2>
        <i class="fa fa-caret-down"></i>
      </div>
    </summary>
    <ul>
      <li v-for="(detail, index) in detailData" :key="index">
        {{ detail }}
      </li>
    </ul>
  </details>
</template>
```

> In this template, no style is being added to it to keep it readable and easy to modify. All styles will be written in the `styles` tag.

### Time to add some functionality

In our script tag,

```html
<script>
export default {
  props: ["title", "data"],
  data(){
    return {
      detailData: this.data instanceof Array ? [...this.data] : []
    }
  }
}
</script>
```

### Explanation

First, we have to define the data our custom-details will need which are a **title** and some **array of strings** data by using the `props` attribute.

```javascript
  ...
  props: ["title", "data"]
  ...
```

Then, we have to ensure that the `data` being provided by the parent component is an Array, so we check if the data is and array, if not, we return an empty array using the **tenery operator**.

```javascript
this.data instanceof Array ? [...this.data] : []
```

# Time to add some styles (the fun part :)

To make our code clean, we are using the `styles` tag.

## Default styles
  To our style.css which is usually automatically imported by your build tool into the `main.js` file, add the following to the top.

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

  The `@tailwind components;` was not added since we do not require any more styles.
## Component styles
  To our styles tag should initially look like this.
  
  ```html
  <style scoped>
  </style>
  ```

  First of, let us add some style to the `details` tag.

  ```css
    details{
      @apply border border-gray-300 rounded-md my-2 mx-auto;
    }
  ```

  Next, remove default markers of the `summary` tag and style it

  ```css
    ...
    summary::-webkit-details-marker,
    summary::marker{
      display: none;
    }

    details > summary{
      @apply flex py-2 cursor-pointer;
    }

    details[open] > summary{
      @apply border-b border-gray-300;
    }
    ...
  ```

  Then, styling the inner summary div

  ```css
    ...

    summary > div {
      @apply flex items-center gap-x-6 w-full text-gray-700 px-4;
    }

    ...
  ```

  Finally, we then style the list

  ```css
    ...

    details > ul {
      @apply flex flex-col divide-y-2 divide-gray-200;
    }

    ul > li {
      @apply flex px-4 hover:bg-gray-50 cursor-pointer text-sm text-gray-500 font-bold py-1.5
    }

  ```

Your final CSS should look like this. 

```html

<style scoped>

  details{
    @apply border border-gray-300 rounded-md my-2 mx-auto;
  }

  details > summary{
    @apply flex py-2 cursor-pointer;
  }

  details[open] > summary{
    @apply border-b border-gray-300;
  }

  summary::-webkit-details-marker,
  summary::marker{
    display: none;
  }

  summary > div {
    @apply flex items-center gap-x-6 w-full text-gray-700 px-4;
  }

  details > ul {
    @apply flex flex-col divide-y-2 divide-gray-200;
  }

  ul > li {
    @apply flex px-4 hover:bg-gray-50 cursor-pointer text-sm text-gray-500 font-bold py-1.5
  }

</style>
```

If properly followed, the results should give you this. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2w63wzteax5mxhc5sqjf.png)


Thanks for reading and have a good day.