Ever wondered how websites show instant notifications without making you refresh the page? Today we are building a real-time notification bell using Laravel for the backend, Vue.js for the reactive frontend, and TailwindCSS for clean styling.

  • Broadcast backend events using Laravel WebSockets or Reverb
  • Handle reactive state changes in Vue.js
  • Design a dropdown UI toggle with TailwindCSS

Setting Up the Laravel Backend

First, we need a database table and a model for our notifications. Run the standard Laravel notification migration command to get started quickly.

php artisan notifications:table
php artisan migrate

This creates the exact database structure Laravel needs to store notification objects.

Creating the Vue Notification Bell Component

Next, we build a Vue component that fetches existing notifications and listens for new ones. We will use Axios to fetch our initial list when the page loads.

<template>
  <div class="relative">
    <button @click="isOpen = !isOpen" class="relative p-2 text-gray-600 hover:text-gray-900">
      <span v-if="unreadCount > 0" class="absolute top-0 right-0 w-2 h-2 bg-red-500 rounded-full"></span>
      🔔
    </button>
    <div v-if="isOpen" class="absolute right-0 mt-2 w-80 bg-white border rounded-lg shadow-lg p-4">
      <div v-for="notification in notifications" :key="notification.id" class="py-2 border-b">
        {{ notification.data.message }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';
const isOpen = ref(false);
const unreadCount = ref(3);
const notifications = ref([{ id: 1, data: { message: 'New comment on your post' } }]);
</script>

This component handles the dropdown toggle state and displays a red indicator dot when unread items exist.

Vue.js development Photo by Mohammad Rahmani on Unsplash

Common mistakes

A common mistake is forgetting to authorize your broadcast channels in Laravel. Always check your routes/channels.php file to ensure users only listen to their own private feeds.

Another frequent issue is leaving WebSocket connections open when unmounting Vue components. Make sure to call .leave() in your component's unmounted lifecycle hook to prevent memory leaks.

Clone our starter repository from GitHub and try adding a mark-as-read button to your notification dropdown today.