Loading Skeletons in Vue with Tailwind

Written on: November, 2024

4 min read

Loading states are essential in frontend development, providing feedback during data fetches or complex rendering. This is especially beneficial for users with slower connections or large datasets. In this post, I will show you how to manage loading skeleton components in a Vue project using Tailwind CSS, with a mock API to simulate data fetching. Follow these steps to effectively implement loading skeletons in your projects.

Steps

  1. Generate Random User Data
  2. I created functions to generate random user data. If you don't have this data in your backend, you can implement your own functions. Find the code for these functions at the link below.

    https://github.com/Aldikrasniqi/vue-tailwind-loading-skeleton/blob/main/src/utils/utils.ts
  3. Set Up Mock API with MSW
  4. Mock Service Worker (MSW) allows you to intercept and mock API requests during development. Create a handlers.ts file in the src/mocks directory to define API endpoints and responses, simulating backend interactions without a server.

    https://github.com/Aldikrasniqi/vue-tailwind-loading-skeleton/blob/main/src/mocks/handlers.ts
    1npm i msw
    1import { http, HttpResponse } from 'msw'
    2import type { User } from '@/interfaces/user'
    3import { posts, users } from '@/utils/utils'
    4import type { Post } from '@/interfaces/post'
    5
    6export const handlers = [
    7	http.get('/users', () => {
    8		return HttpResponse.json<User[]>(users)
    9	}),
    10
    11	http.get('/posts', () => {
    12		return HttpResponse.json<Post[]>(posts)
    13	}),
    14]
  5. Enable MSW in Development
  6. MSW intercepts API calls and returns mock data. To enable it, create a `browser.ts` file in the `src/mocks` folder and set up the MSW worker. Import this file into `src/main.ts` to initialize the mock server.

    1import './assets/index.css'
    2
    3import { createApp } from 'vue'
    4import { createPinia } from 'pinia'
    5
    6async function prepareApp() {
    7	const { worker } = await import('./mocks/browser')
    8	return worker.start()
    9}
    10
    11import App from './App.vue'
    12import router from './router'
    13
    14const app = createApp(App)
    15
    16app.use(createPinia())
    17app.use(router)
    18
    19prepareApp().then(() => {
    20	app.mount('#app')
    21})
  7. State Management with Pinia
  8. Pinia is a user-friendly state management library for Vue.js. It allows you to define stores as reactive data containers. Create a `users.ts` file in the `src/stores` directory to define the `users` store, which includes an array for users and a loading flag. The `fetchUsers` action retrieves users from the mock API.

    1import type { User } from '@/interfaces/user'
    2import { defineStore } from 'pinia'
    3import { getUsers } from '@/services/user.service'
    4export const useUsersStore = defineStore('users', {
    5	state: () => ({
    6		users: [] as User[],
    7		loadUsers: false,
    8	}),
    9	actions: {
    10		async fetchUsers() {
    11			this.loadUsers = true
    12			const resp = await getUsers()
    13			if (resp?.data) {
    14				this.users = resp.data
    15				setTimeout(() => {
    16					this.loadUsers = false
    17				}, 2000)
    18			} else {
    19				this.loadUsers = false
    20			}
    21		},
    22	},
    23})
  9. Create UserSkeleton Component
  10. Build a UserSkeleton component in the src/components/ui/users folder. It accepts the number of columns and rows as props, using a `for` loop to render the skeleton. Tailwind provides classes for styling, and I use the `animate-pulse` class for animation. Find the code for the UserSkeleton component at the link below.

    https://github.com/Aldikrasniqi/vue-tailwind-loading-skeleton/blob/main/src/components/ui/users/UserSkeleton.vue
  11. Create UserList Component
  12. Build a UserList component in the src/components/ui/users folder to display user data in a list. This component separates UI from logic, accepting user objects as props for rendering. Find the code for the UserList component at the link below.

    https://github.com/Aldikrasniqi/vue-tailwind-loading-skeleton/blob/main/src/components/ui/users/UserList.vue
  13. Create UserView Component
  14. Create a UserView component in the src/components/ui/users folder to pass user objects to the UserList and UserSkeleton components, maintaining separation of UI and logic.

    https://github.com/Aldikrasniqi/vue-tailwind-loading-skeleton/blob/main/src/components/ui/users/UserView.vue
    1<script setup lang="ts">
    2import UserList from '@/components/ui/users/UserList.vue'
    3import UsersLoadingSkeleton from '@/components/ui/users/UsersLoadingSkeleton.vue'
    4import { useUsersStore } from '@/stores/users'
    5import { onMounted } from 'vue'
    6const store = useUsersStore()
    7
    8onMounted(async () => {
    9	store.fetchUsers()
    10})
    11</script>
    12
    13<template>
    14	<main>
    15		<div>
    16			<h1 class="md:py-6 px-3 text-2xl font-semibold">Users</h1>
    17			<div v-if="store.loadUsers">
    18				<UsersLoadingSkeleton :numberOfSkeletons="store.users.length" />
    19			</div>
    20			<div v-for="user in store.users" v-if="!store.loadUsers" :key="user.id">
    21				<UserList
    22					:id="user.id"
    23					:firstName="user.firstName"
    24					:lastName="user.lastName"
    25					:username="user.username"
    26					:email="user.email"
    27					:profilePicture="user.profilePicture"
    28				/>
    29			</div>
    30		</div>
    31	</main>
    32</template>
  15. Loading State UI
  16. While the mock API fetches data, the `UserSkeleton` component shows loading progress, providing users with feedback that the system is working.

    Loading State UI
  17. Success State UI
  18. Once data is fetched, the loading skeleton is replaced by user data in the UI, enhancing user satisfaction by indicating task completion.

    Success State UI

Conclusion

Managing loading skeletons in a Vue Tailwind project is simple with the right tools. By following these steps, you can create smooth loading experiences. If you have questions or feedback, feel free to comment. Thank you for reading!
Back to Blogs
Aldi Krasniqi