Global Sonner Toast System
DeployStack uses Sonner for toast notifications, providing elegant and accessible notifications across the entire application. The system is globally configured and requires no additional setup in individual components.
Architecture Overview
The global Sonner toaster is configured in App.vue
as the root-level toast provider:
<!-- App.vue -->
<template>
<div class="min-h-screen bg-gray-50">
<RouterView />
<Toaster class="pointer-events-auto" />
</div>
</template>
<script setup lang="ts">
import { Toaster } from '@/components/ui/sonner'
import 'vue-sonner/style.css'
</script>
This setup ensures toasts are displayed consistently across all routes and components without requiring local toast providers.
Basic Usage
Importing and Using Toasts
<script setup lang="ts">
import { toast } from 'vue-sonner'
// Success toast
const handleSuccess = () => {
toast.success('Operation completed successfully!')
}
// Error toast
const handleError = () => {
toast.error('Something went wrong')
}
// Warning toast
const handleWarning = () => {
toast.warning('Please review your changes')
}
// Info toast
const handleInfo = () => {
toast('Information message')
}
</script>
Toast with Description
toast.success('Team updated successfully!', {
description: 'Your team settings have been saved.'
})
toast.error('Failed to save team', {
description: 'Network connection error. Please try again.'
})
Real-World Examples
API Success/Error Handling
<script setup lang="ts">
import { toast } from 'vue-sonner'
import { TeamService } from '@/services/teamService'
const saveTeam = async () => {
try {
const updatedTeam = await TeamService.updateTeam(teamId, updateData)
// Success toast with description
toast.success(t('teams.manage.saveSuccess'), {
description: t('teams.manage.saveSuccessDescription')
})
return updatedTeam
} catch (error) {
// Error toast with error details
toast.error(t('teams.manage.saveError'), {
description: error.message
})
throw error
}
}
</script>
Form Validation Feedback
<script setup lang="ts">
import { toast } from 'vue-sonner'
const validateForm = () => {
if (!formData.name.trim()) {
toast.error('Validation Error', {
description: 'Team name is required'
})
return false
}
if (formData.name.length > 100) {
toast.warning('Name too long', {
description: 'Team name must be 100 characters or less'
})
return false
}
return true
}
</script>
Async Operations with Loading States
<script setup lang="ts">
import { toast } from 'vue-sonner'
const deployServer = async () => {
// Show loading toast
const loadingToast = toast.loading('Deploying server...', {
description: 'This may take a few minutes'
})
try {
await McpServerService.deploy(serverId)
// Dismiss loading toast and show success
toast.dismiss(loadingToast)
toast.success('Server deployed successfully!', {
description: `Server ${serverName} is now running`
})
} catch (error) {
// Dismiss loading toast and show error
toast.dismiss(loadingToast)
toast.error('Deployment failed', {
description: error.message
})
}
}
</script>
Advanced Usage
Custom Duration
// Auto-dismiss after 10 seconds
toast.success('Long message', {
duration: 10000
})
// Persistent toast (requires manual dismiss)
toast.error('Critical error', {
duration: Infinity
})
Action Buttons
toast('New version available', {
description: 'Click to update your application',
action: {
label: 'Update',
onClick: () => updateApplication()
}
})
Custom Styling
toast.success('Custom styled toast', {
className: 'custom-toast',
style: {
background: 'linear-gradient(45deg, #ff6b6b, #4ecdc4)'
}
})
Integration with Internationalization
Combine Sonner with the i18n system for localized messages:
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import { toast } from 'vue-sonner'
const { t } = useI18n()
const saveSettings = async () => {
try {
await SettingsService.save(settings)
toast.success(t('settings.saveSuccess'), {
description: t('settings.saveSuccessDescription')
})
} catch (error) {
toast.error(t('settings.saveError'), {
description: t('settings.errorMessages.networkError')
})
}
}
</script>
Event Bus Integration
Sonner works seamlessly with the global event bus:
<script setup lang="ts">
import { useEventBus } from '@/composables/useEventBus'
import { toast } from 'vue-sonner'
const eventBus = useEventBus()
// Listen for global notification events
eventBus.on('notification-show', (data) => {
switch (data.type) {
case 'success':
toast.success(data.message)
break
case 'error':
toast.error(data.message)
break
case 'warning':
toast.warning(data.message)
break
default:
toast(data.message)
}
})
// Emit notifications from other components
const triggerGlobalNotification = () => {
eventBus.emit('notification-show', {
message: 'Global notification',
type: 'success'
})
}
</script>
Best Practices
1. Use Appropriate Toast Types
// Good: Use semantic types
toast.success('Data saved') // For successful operations
toast.error('Save failed') // For errors and failures
toast.warning('Unsaved changes') // For warnings and cautions
toast('Information') // For neutral information
// Avoid: Using wrong types
toast.success('Error occurred') // Wrong type for error
toast.error('Success message') // Confusing for users
2. Provide Meaningful Descriptions
// Good: Clear and actionable
toast.error('Failed to save team', {
description: 'Network connection error. Please check your internet connection and try again.'
})
// Avoid: Vague messages
toast.error('Error', {
description: 'Something went wrong'
})
3. Handle Loading States
<script setup lang="ts">
const handleAsyncOperation = async () => {
const loadingToast = toast.loading('Processing...')
try {
await performOperation()
toast.dismiss(loadingToast)
toast.success('Operation completed')
} catch (error) {
toast.dismiss(loadingToast)
toast.error('Operation failed')
}
}
</script>
4. Avoid Toast Spam
// Good: Debounce frequent operations
import { debounce } from 'lodash-es'
const debouncedSave = debounce(() => {
toast.success('Auto-saved')
}, 1000)
// Avoid: Toast on every input change
const handleInput = () => {
toast('Input changed') // Too frequent!
}
Common Pitfalls
❌ Don't Add Multiple Toasters
<!-- DON'T: Never add local Toaster components -->
<template>
<div>
<Toaster /> <!-- This will create duplicate toasts -->
<!-- component content -->
</div>
</template>
The global Toaster in App.vue
handles all notifications automatically.
❌ Don't Overuse Toasts
// DON'T: Toast for every minor action
toast('Button clicked')
toast('Mouse moved')
toast('Form field focused')
// DO: Toast for meaningful user feedback
toast.success('Settings saved')
toast.error('Login failed')
toast.warning('Session expiring soon')
❌ Don't Forget Error Handling
// DON'T: Silent failures
const saveData = async () => {
try {
await api.save(data)
toast.success('Saved!')
} catch (error) {
// Silent failure - user doesn't know what happened
}
}
// DO: Always provide error feedback
const saveData = async () => {
try {
await api.save(data)
toast.success('Data saved successfully')
} catch (error) {
toast.error('Failed to save data', {
description: error.message
})
}
}
Testing Toasts
Unit Testing
// tests/components/TeamManager.test.ts
import { vi } from 'vitest'
import { toast } from 'vue-sonner'
// Mock toast functions
vi.mock('vue-sonner', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
}
}))
describe('TeamManager', () => {
it('shows success toast when team is saved', async () => {
const wrapper = mount(TeamManager)
await wrapper.vm.saveTeam()
expect(toast.success).toHaveBeenCalledWith(
'Team updated successfully!',
{ description: 'Your team settings have been saved.' }
)
})
})
Migration from Other Toast Systems
From Vue-Toastification
// Before (vue-toastification)
this.$toast.success('Success message')
this.$toast.error('Error message')
// After (Sonner)
import { toast } from 'vue-sonner'
toast.success('Success message')
toast.error('Error message')
From Custom Alert Components
<!-- Before: Custom Alert components -->
<Alert v-if="showSuccess" variant="success">
<AlertDescription>Success message</AlertDescription>
</Alert>
<Alert v-if="showError" variant="destructive">
<AlertDescription>Error message</AlertDescription>
</Alert>
<!-- After: Sonner toasts -->
<script setup lang="ts">
import { toast } from 'vue-sonner'
const handleSuccess = () => {
toast.success('Success message')
}
const handleError = () => {
toast.error('Error message')
}
</script>
The global Sonner system provides a consistent, accessible, and developer-friendly way to handle notifications across the entire DeployStack application.