Feedback Components

User feedback components including tooltips, popovers, and toast notifications for enhanced user experience.

Feedback Components

Feedback components provide contextual information, hints, and notifications to users. These include tooltips for quick hints, popovers for detailed information, and toast notifications for non-blocking messages.

Available Feedback Components

Component Purpose
<x-tooltip> Hover hints and information
<x-popover> Click-triggered info panels
<x-toast-notifications> Non-blocking notifications

Tooltip Component

Displays contextual information on hover, ideal for explaining UI elements or providing additional context.

Props

Prop Type Default Description
id string null Tooltip ID for accessibility
title string '' Main tooltip text
description string '' Additional description
position string 'top' Position: top, bottom, left, right
width string '' Custom width
arrowAlign string 'center' Arrow alignment: left, center, right

Basic Usage

<x-tooltip title="Click to save your changes">
    <x-buttons.button variant="primary" icon="lucide:save">
        Save
    </x-buttons.button>
</x-tooltip>

With Position

{{-- Top (default) --}}
<x-tooltip title="Top tooltip" position="top">
    <span class="text-gray-500 cursor-help">Hover me</span>
</x-tooltip>

{{-- Bottom --}}
<x-tooltip title="Bottom tooltip" position="bottom">
    <span class="text-gray-500 cursor-help">Hover me</span>
</x-tooltip>

{{-- Left --}}
<x-tooltip title="Left tooltip" position="left">
    <span class="text-gray-500 cursor-help">Hover me</span>
</x-tooltip>

{{-- Right --}}
<x-tooltip title="Right tooltip" position="right">
    <span class="text-gray-500 cursor-help">Hover me</span>
</x-tooltip>

With Title and Description

<x-tooltip
    title="Pro Feature"
    description="Upgrade to Pro to unlock this feature"
>
    <iconify-icon icon="lucide:crown" class="text-yellow-500"></iconify-icon>
</x-tooltip>

On Form Labels

<div class="flex items-center gap-2">
    <label class="form-label">API Key</label>
    <x-tooltip
        title="Your API Key"
        description="Keep this key secret. It provides full access to your account."
    >
        <iconify-icon icon="lucide:info" class="text-gray-400 cursor-help"></iconify-icon>
    </x-tooltip>
</div>

Icon Button with Tooltip

<x-tooltip title="Edit user">
    <button class="p-2 hover:bg-gray-100 rounded">
        <iconify-icon icon="lucide:edit" class="text-gray-600"></iconify-icon>
    </button>
</x-tooltip>

Real Example from Codebase

{{-- Permission indicator --}}
<x-tooltip
    title="{{ __('Permission Required') }}"
    description="{{ __('You need the :permission permission to access this feature.', ['permission' => 'manage settings']) }}"
>
    <iconify-icon icon="lucide:shield" class="text-warning"></iconify-icon>
</x-tooltip>

{{-- Status indicator --}}
<x-tooltip title="{{ $user->is_active ? __('Active') : __('Inactive') }}">
    <span class="w-2 h-2 rounded-full {{ $user->is_active ? 'bg-green-500' : 'bg-red-500' }}"></span>
</x-tooltip>

Popover Component

Click-triggered panel for displaying more detailed information or interactive content.

Props

Prop Type Default Description
id string auto-generated Unique popover ID
position string 'bottom' Position: top, bottom, left, right
width string 'max-w-xs' Max width class
trigger string 'click' Trigger type
triggerClass string default styles Trigger button classes
contentClass string '' Content container classes

Slots

Slot Description
trigger The element that opens the popover
default Popover content

Basic Usage

<x-popover>
    <x-slot name="trigger">
        <iconify-icon icon="lucide:info" class="text-gray-500 cursor-pointer"></iconify-icon>
    </x-slot>

    <div class="p-4">
        <h4 class="font-medium mb-2">Help Information</h4>
        <p class="text-sm text-gray-600">
            This is detailed help text that appears when you click the icon.
        </p>
    </div>
</x-popover>

With Position

<x-popover position="top">
    <x-slot name="trigger">
        <x-buttons.button variant="secondary">Show Above</x-buttons.button>
    </x-slot>

    <div class="p-4">
        <p>This popover appears above the trigger.</p>
    </div>
</x-popover>

<x-popover position="right">
    <x-slot name="trigger">
        <x-buttons.button variant="secondary">Show Right</x-buttons.button>
    </x-slot>

    <div class="p-4">
        <p>This popover appears to the right.</p>
    </div>
</x-popover>

Interactive Content

<x-popover width="max-w-sm">
    <x-slot name="trigger">
        <x-buttons.button variant="primary" icon="lucide:share">
            Share
        </x-buttons.button>
    </x-slot>

    <div class="p-4">
        <h4 class="font-medium mb-3">Share this page</h4>

        <div class="flex gap-2">
            <button class="p-2 hover:bg-gray-100 rounded" title="Twitter">
                <iconify-icon icon="mdi:twitter" class="text-xl"></iconify-icon>
            </button>
            <button class="p-2 hover:bg-gray-100 rounded" title="Facebook">
                <iconify-icon icon="mdi:facebook" class="text-xl"></iconify-icon>
            </button>
            <button class="p-2 hover:bg-gray-100 rounded" title="LinkedIn">
                <iconify-icon icon="mdi:linkedin" class="text-xl"></iconify-icon>
            </button>
        </div>

        <div class="mt-3 pt-3 border-t">
            <label class="text-sm text-gray-600">Or copy link</label>
            <div class="flex mt-1">
                <input
                    type="text"
                    value="{{ url()->current() }}"
                    class="form-control text-sm"
                    readonly
                />
                <button class="btn-secondary ml-2" onclick="copyToClipboard()">
                    Copy
                </button>
            </div>
        </div>
    </div>
</x-popover>

User Profile Popover

<x-popover position="bottom" width="max-w-xs">
    <x-slot name="trigger">
        <img src="{{ $user->avatar_url }}" class="w-8 h-8 rounded-full cursor-pointer" />
    </x-slot>

    <div class="p-4">
        <div class="flex items-center gap-3 mb-3">
            <img src="{{ $user->avatar_url }}" class="w-12 h-12 rounded-full" />
            <div>
                <h4 class="font-medium">{{ $user->name }}</h4>
                <p class="text-sm text-gray-500">{{ $user->email }}</p>
            </div>
        </div>

        <div class="space-y-1">
            <a href="{{ route('profile') }}" class="block px-2 py-1.5 hover:bg-gray-100 rounded text-sm">
                View Profile
            </a>
            <a href="{{ route('settings') }}" class="block px-2 py-1.5 hover:bg-gray-100 rounded text-sm">
                Settings
            </a>
            <hr class="my-2" />
            <form method="POST" action="{{ route('logout') }}">
                @csrf
                <button class="w-full text-left px-2 py-1.5 hover:bg-gray-100 rounded text-sm text-red-600">
                    Logout
                </button>
            </form>
        </div>
    </div>
</x-popover>

Toast Notifications

Non-blocking notifications that appear in the corner of the screen. Great for success messages, errors, and status updates.

Setup

Include the component in your main layout (usually already included):

{{-- In your layout file --}}
<x-toast-notifications />

Props

Prop Type Default Description
soundEffect boolean false Play sound on notification
displayDuration integer 5000 Auto-dismiss time (ms)

Triggering from JavaScript

// Success toast
window.dispatchEvent(new CustomEvent('notify', {
    detail: {
        variant: 'success',
        title: 'Success!',
        message: 'Your changes have been saved.'
    }
}));

// Error toast
window.dispatchEvent(new CustomEvent('notify', {
    detail: {
        variant: 'error',
        title: 'Error',
        message: 'Failed to save changes. Please try again.'
    }
}));

// Warning toast
window.dispatchEvent(new CustomEvent('notify', {
    detail: {
        variant: 'warning',
        title: 'Warning',
        message: 'Your session will expire in 5 minutes.'
    }
}));

// Info toast
window.dispatchEvent(new CustomEvent('notify', {
    detail: {
        variant: 'info',
        title: 'New Feature',
        message: 'Check out the new dashboard widgets!'
    }
}));

Triggering from Livewire

// In Livewire component
public function save()
{
    $this->validate();
    $this->model->save();

    $this->dispatch('notify', [
        'variant' => 'success',
        'title' => 'Saved!',
        'message' => 'Your changes have been saved successfully.'
    ]);
}

public function delete()
{
    try {
        $this->model->delete();

        $this->dispatch('notify', [
            'variant' => 'success',
            'title' => 'Deleted',
            'message' => 'The item has been removed.'
        ]);
    } catch (\Exception $e) {
        $this->dispatch('notify', [
            'variant' => 'error',
            'title' => 'Delete Failed',
            'message' => $e->getMessage()
        ]);
    }
}

Triggering from Alpine.js

<button
    @click="$dispatch('notify', {
        variant: 'success',
        title: 'Copied!',
        message: 'Link copied to clipboard.'
    })"
>
    Copy Link
</button>

Available Variants

Variant Color Icon Use Case
success Green Checkmark Successful operations
error / danger Red Exclamation Errors and failures
warning Amber Warning Warnings and cautions
info Blue Info General information

With Sound Effect

<x-toast-notifications :soundEffect="true" />

Requires a notification sound file at /public/sounds/notification.mp3.

Custom Display Duration

{{-- Show for 10 seconds --}}
<x-toast-notifications :displayDuration="10000" />

{{-- Show for 3 seconds --}}
<x-toast-notifications :displayDuration="3000" />

Real Example from Codebase

// After successful form submission
public function updateProfile()
{
    $this->validate([
        'name' => 'required|string|max:255',
        'email' => 'required|email|unique:users,email,' . auth()->id(),
    ]);

    auth()->user()->update([
        'name' => $this->name,
        'email' => $this->email,
    ]);

    $this->dispatch('notify', [
        'variant' => 'success',
        'title' => __('Profile Updated'),
        'message' => __('Your profile has been updated successfully.')
    ]);
}

// After bulk operation
public function bulkDelete()
{
    $count = count($this->selectedItems);

    Model::whereIn('id', $this->selectedItems)->delete();

    $this->selectedItems = [];
    $this->dispatch('resetSelectedItems');

    $this->dispatch('notify', [
        'variant' => 'success',
        'title' => __('Items Deleted'),
        'message' => __(':count items have been deleted.', ['count' => $count])
    ]);
}

Variable Selector Component

A specialized component for selecting and inserting template variables.

Basic Usage

<x-variable-selector
    :variables="[
        'user.name' => 'User Name',
        'user.email' => 'User Email',
        'site.name' => 'Site Name',
        'date.today' => 'Today\'s Date',
    ]"
    target="content-editor"
/>

Used in email template builders and content editors to insert dynamic placeholders.


Text Editor Component

Rich text editing component with toolbar and formatting options.

Basic Usage

<x-text-editor
    name="content"
    wire:model="content"
    :value="$content"
/>

Searchable Select Component

Enhanced select with search/filter functionality.

Props

Prop Type Default Description
label string null Label text
name string null Input name
options array [] Available options
value mixed null Selected value
placeholder string 'Search...' Search placeholder
multiple boolean false Allow multiple selection

Basic Usage

<x-searchable-select
    label="Select User"
    name="user_id"
    :options="$users->pluck('name', 'id')->toArray()"
    wire:model="user_id"
    placeholder="Search users..."
/>

Multiple Selection

<x-searchable-select
    label="Select Categories"
    name="categories"
    :options="$categories"
    wire:model="selectedCategories"
    :multiple="true"
/>

Best Practices

Tooltips

  1. Keep text short - 1-2 sentences max
  2. Use for hints - Not essential information
  3. Position appropriately - Don't cover other elements
  4. Don't overuse - Only where needed

Popovers

  1. Use for more detail - When tooltips aren't enough
  2. Include actions - Links, buttons, forms
  3. Close on outside click - Standard behavior
  4. Manage focus - Trap focus when open

Toast Notifications

  1. Use correct variants - Match message type
  2. Keep messages brief - Title + 1 sentence
  3. Don't block UI - Non-intrusive placement
  4. Auto-dismiss - Unless action required
  5. Allow manual dismiss - Always include close button

Accessibility Considerations

  • Tooltips use aria-describedby for screen readers
  • Popovers trap focus when open
  • Toast notifications use role="alert"
  • Escape key closes interactive components
  • Focus returns to trigger on close

Next Steps

/