Accordion In this article, we will be creating an accordion with vue.js. An accordion is used in websites mostly for FAQ sections where the answer section expands or collapses when a user clicks on the question itself or any icon like chevron🔰, arrow➡️, or plus sign➕. Preview: live Demo First, we will create a Vue project using vite. So, ensure that and are already installed on your computer. After installing vite, type the following command in the command prompt section or terminal and press <kbd>Enter</kbd>: vite node npm init vite <project-name> After pressing <kbd>Enter</kbd> you will be prompted with a few questions like which framework you want to use vite with. We will choose vue. You’ll also be asked to choose between JavaScript and TypeScript. We will choose JavaScript. After answering the above questions vite will create a vue project for us. After that we have to run a few commands in the terminal: cd <project-name> npm install // It will install all the necessary dependencies for our project in node_modules folder We will be using as css-preprocessor. So, let’s install it. LESS npm install -D less // installs less as dev-dependency Once vite creates a project for us we can run our project using the following command: npm run dev The above command will start the development server created by vite at and you will see a welcome page at this address. http://localhost:5173/ Now, let’s modify the project as per our needs. First, delete component from folder then boilerplate code inside file and also delete file from folder. HelloWorld src/components src/App.vue style.css src Since we are using as a css-preprocessor, create a folder and inside the folder, create file and add the following code to it: LESS less global.less *{ margin: 0; padding: 0; box-sizing: border-box; } body{ font-family: 'Source Sans Pro', sans-serif; background-color: antiquewhite; } In the above code, we are resetting the default browser styles and adding some general styles to . body Open and you will see something like this: src/main.js import { createApp } from 'vue' import './style.css' import App from './App.vue' createApp(App).mount('#app') Replace import with because we deleted file and created file. After replacing will look like this: ./style.css ./less/global.less style.css less/global.less main.js import { createApp } from 'vue' import './less/global.less' import App from './App.vue' createApp(App).mount('#app') Creating Accordion First, we will create component in folder and import that component into our component. Accordion.vue src/components App.vue 👇🏻 Accordion.vue <script setup></script> <template> <h1>Accordion</h1> </template> <style scoped lang="less"></style> 👇🏻 App.vue <script setup> /* importing Accordion.vue👇🏻 */ import Accordion from './components/Accordion' </script> <template> <Accordion/> <!-- 👈🏻Using Accordion.vue --> </template> is the parent component of all other components in our app. This component is passed to the function provided by vue.js which translates our vue code into native JavaScript, HTML, and CSS. The translated code is then passed to function which puts this translated code into an html element with id inside file which is then executed by the browser. It is important to import other components for so that vue can translate the code in all of our components. App.vue createApp mount app index.html App.vue In folder we created an component that houses all other components of our app. Currently, this component only contains a tag with the text 'Accordion'. So, if we run in the terminal and open browser, we will see 'Accordion' text on our screen. src/components Accordion.vue h1 npm run dev Now, let's create a main container for our accordion and the container is simply a html element. And also give some styling to it. Inside we will create a with class which will contain all of our accordion items. main main div faqs <script setup></script> <template> <main> <!-- 👈🏻Main container for accordion --> <div class="faqs"></div> </main> </template> <style scoped lang="less"> .main{ width: 500px; box-shadow: 5px 5px 20px 0 rgba(0, 0, 0, 0.5); margin: 0 auto; margin-top: 10%; margin-bottom: 10%; background-color: rgb(240, 248, 255); padding: 10px; border-radius: 6px; .faqs{ display: flex; flex-direction: column; gap: 20px; } } </style> Now, we will create another component in folder called in which we will write code for a single accordion item. Our single accordion item will have a element with class which contains two elements with classes and respectively. In element, we will create two further elements with classes and . div will contain a faq-question and div contains a chevron image. And in element, we have a element that contains faq-answer. src/component Faq.vue div faq div header answer header div question icon question icon answer p 👇🏻 Faq.vue <script setup></script> <template> <div class="faq"> <div class="header"> <div class="question"> <!-- contains question --> </div> <div class="icon"> <img src="" alt=""/> </div> </div> <div class="answer"> <p> <!-- contains answer --> </p> </div> </div> </template> <style scoped lang="less"></style> Our accordion will have five questions in total and we will store those in an array of objects. Each object will have four properties: a unique number for identifying an object. id stores faq question. question stores faq answer. answer stores a boolean value that specifies whether the answer part of the accordion is expanded or collapsed. isOpen const data = [ { "id": 1, "question": "What is the capital of Australia?", "answer": "The capital of Australia is Canberra. It is a relatively new city, established in 1913, and is located between Sydney and Melbourne. Canberra is home to numerous national institutions and landmarks, including Parliament House, the Australian War Memorial, and the National Gallery of Australia. The city is known for its modern architecture and urban planning, and has a population of over 400,000 people. Despite not being one of the country's largest cities, Canberra is an important political and cultural center, and has a significant impact on the nation's economy and development.", "isOpen": false }, { "id": 2, "question": "What is the tallest animal on earth?", "answer": "The tallest animal on earth is the giraffe, which can grow up to 18 feet tall. Giraffes are known for their long necks, which can reach up to 6 feet in length, and are used to reach leaves and fruits from tall trees. Giraffes are found in savannas and grasslands in Africa, and are herbivorous, feeding on leaves, fruits, and flowers. Despite their size, giraffes are social animals and live in groups called towers. They are also known for their distinctive spotted coat, which helps them blend in with their environment and avoid predators.", "isOpen": false }, { "id": 3, "question": "What is the largest country in the world by area?", "answer": "The largest country in the world by area is Russia, which covers over 17 million square kilometers. Russia is located in northern Eurasia, and is bordered by Norway, Finland, Estonia, Latvia, Lithuania, Poland, Belarus, Ukraine, Georgia, Azerbaijan, Kazakhstan, China, North Korea, and Mongolia. Russia has a population of over 144 million people, and is known for its rich history and culture, as well as its natural resources, such as oil, gas, and minerals. The country is also home to numerous landmarks and tourist attractions, including the Red Square, the Kremlin, and the Hermitage Museum.", "isOpen": false }, { "id": 4, "question": "What is the largest animal on earth?", "answer": "The largest animal on earth is the blue whale, which can grow up to 100 feet in length and weigh up to 200 tons. Blue whales are found in all the world's oceans, and are known for their distinctive blue-gray coloration and long, slender bodies. They are filter feeders, feeding on tiny shrimp-like creatures called krill, and can consume up to 4 tons of krill in a single day. Despite their enormous size, blue whales are graceful swimmers and can travel at speeds of up to 30 miles per hour.", "isOpen": false }, { "id": 5, "question": "What is the capital of France?", "answer": "The capital of France is Paris. It is one of the most famous cities in the world, known for its beautiful architecture, rich history, and world-class museums and landmarks. Paris is home to iconic landmarks such as the Eiffel Tower, the Louvre Museum, and Notre-Dame Cathedral, and is famous for its cuisine and fashion. The city has a population of over 2 million people, and is a global center for art, fashion, and culture. Despite its cosmopolitan character, Paris has retained much of its historic charm, with narrow streets", "isOpen": false } ] In we will store the above array as a reactive state using so import from . Currently, our questions and answers are in an array but to display them on the browser we have to extract those from arrays and put them into HTML elements. We have already created a component for that which is so import it. is a component for a single accordion or single question & answer so, we have to pass a single object from data array to the component at a time. We have a total of five questions & answers which means we have to call five times and pass a single object of question & answer each time. Calling five times is tedious so we will us directive on to loop over data array and in each iteration, we will pass single object to the component. Accordion.vue ref ref vue Faq.vue Faq.vue Faq.vue Faq.vue v-for Faq.vue faq <script setup> import { ref } from 'vue' import Faq from './Faq' const faqs = ref(data) // data is above array of faq questions & answers </script> <template> <main> <!-- 👈🏻Main container for accordion --> <div class="faqs"> <!-- Looping over faqs array and passing single faq as a prop --> <Faq v-for="faq in faqs" :key="faq.id" :faq="faq" /> </div> </main> </template> <style scoped lang="less"> .main{ width: 500px; box-shadow: 5px 5px 20px 0 rgba(0, 0, 0, 0.5); margin: 0 auto; margin-top: 10%; margin-bottom: 10%; background-color: rgb(240, 248, 255); padding: 10px; border-radius: 6px; .faqs{ display: flex; flex-direction: column; gap: 20px; } } </style> After passing as a prop, we have to recieve that inside of using macro. Then we can use object in of to extract question & answer from it. We also need a chevron icon in each accordion so, let's import that and bind the attribute of element to it. faq Faq.vue defineProps faq template Faq.vue src img At the end, let’s add some styling to the component. <script setup> import chevron from '../assets/chevron.svg' defineProps(['faq']) // recieving faq object as a prop. </script> <template> <div class="faq"> <div class="header"> <div class="question"> {{ faq.question }} <!--Extracting question from faq prop --> </div> <div class="icon"> <img :src="chevron" alt="chevron-icon"/> </div> </div> <div class="answer"> <p> {{ faq.answer }} </p> <!--Extracting answer from faq prop --> </div> </div> </template> <style scoped lang="less"> .faq{ flex-grow: 1; .header{ display: flex; align-items: center; justify-content: space-between; border: 2px solid antiquewhite; padding: 10px; border-radius: 6px 6px 0 0; cursor: pointer; .question{ font-weight: 700; } .icon{ width: 30px; height: 30px; transition: transform .5s; img{ width: 100%; height: auto; } &.open{ transform: rotate(180deg); } } } .answer{ height: 0; overflow-y: scroll; line-height: 1.5; background-color: antiquewhite; transition: height .5s; &::-webkit-scrollbar{ width: 5px; } &::-webkit-scrollbar-track{ appearance: none; background-color: transparent; } &::-webkit-scrollbar-thumb{ width: 5px; background-color: rgb(232, 210, 182); border-radius: 50px; } p{ padding: 10px; } &.open{ height: 200px; } } } </style> By default answer, part of each accordion should be collapsed and we did it in css by giving element of and setting to so that we can have scroll-bar on if the content is long and it overflows. We also gave some styling to the scroll-bar also. .answer height 0 overflow scroll .answer Adding Functionality Whenever a user clicks on a question or chevron icon it should toggle the answer part of that accordion meaning if the answer part is collapsed it should expand and vice-versa. One thing to keep in mind is that click event will occur in component but the data that has to be updated is in .Because is child component of we can emit the event from and then listen to that emit event in . Faq.vue Accordion.vue Faq.vue Accordion.vue Faq.vue Accordion.vue Another thing is that we have to keep track of whether the answer part of a particular accordion is collapsed or expanded for that we will use property of faq object in data array. isOpen <script setup> import chevron from '../assets/chevron.svg' defineProps(['faq']) // recieving faq object as a prop. const emit = defineEmits(['toggleAnswer']) // defining events to emit const handleClick = id => emit('toggleAnswer', id) // emitting toggleAnswer event with id attribute. </script> <template> <div class="faq"> <div class="header" @click="() => handleClick(faq.id)"> <div class="question"> {{ faq.question }} <!--Extracting question from faq prop --> </div> <div :class="['icon', {open: faq.isOpen}]"> <img :src="chevron" alt="chevron-icon"/> </div> </div> <div :class="['answer', {open: faq.isOpen}]"> <p> {{ faq.answer }} </p> <!--Extracting answer from faq prop --> </div> </div> </template> <style scoped lang="less"> /* Styling goes here */ </style> First in we will add a click event listener to the element that contains question and chevron icon. When a user clicks on it runs functions which emits event to its parent component but to keep track of which accordion has been clicked we also send of that faq object. Also, in template we are checking if is and if it is then we are adding class to element so that we can expand the answer part of the accordion. We also add class to element which contains chevron icon because we need to rotate it if is . Faq.vue header header handleClick toggleAnswer id faq.isOpen true true open answer open icon faq.isOpen true <script setup> import { ref } from 'vue' import Faq from './Faq' const faqs = ref(data) // data is above array of faq questions & answers const toggleAnswer = id => { faqs.value = faqs.value.map(faq => faq.id === id ? {...faq, isOpen: !faq.isOpen} : faq) } </script> <template> <main> <!-- 👈🏻Main container for accordion --> <div class="faqs"> <!-- Looping over faqs array and passing single faq as a prop --> <Faq v-for="faq in faqs" :key="faq.id" :faq="faq" @toggle-answer="toggleAnswer" /> </div> </main> </template> <style scoped lang="less"> /* Styling goes here */ </style> In we are listening to which executes function which takes as a parameter sent by . In function we are updating state by looping over array using function. Inside function in each iteration we are checking if the id of the current item is equal to the of clicked accordion and if it returns that means the current item is the accordion that was clicked so we are changing its property to if it is and vice-versa. Accordion.vue toggle-answer toggleAnswer id Faq.vue toggleAnswer faqs faqs map map id true isOpen true false Another thing that we want in our Accordion is that at one time only one accordion item should have an expanded answer part. So, if a user clicks on some accordion to expand its answer part the answer parts of other accordion items should collapse if they are expanded. For that, we first have to set property of each accordion whose property is to false except the one user just clicked by doing this we will make sure that every other accordion have collapsed answer part. isOpen isOpen true In Accordion.vue const toggleAnswer = id => { faqs.value = faqs.value.map(faq => faq.isOpen && faq.id !== id ? {...faq, isOpen: false} : faq) faqs.value = faqs.value.map(faq => faq.id === id ? {...faq, isOpen: !faq.isOpen} : faq) } I hope you enjoyed this article😊.