Media Components

File uploads, image galleries, carousels, video and audio players

Drag & Drop File Uploader

Drag and drop files with visual feedback and validation

Drag and drop files here, or click to select

Code
<div x-data="{ files: [], dragging: false }">
    <div @drop.prevent="
            dragging = false;
            const droppedFiles = Array.from($event.dataTransfer.files);
            files = droppedFiles.map(f => ({ name: f.name, size: (f.size / 1024).toFixed(2) + ' KB' }));
        "
        @dragover.prevent="dragging = true"
        @dragleave.prevent="dragging = false"
        :class="dragging ? 'border-blue-500 bg-blue-50 dark:bg-blue-900/20' : 'border-gray-300 dark:border-gray-600'"
        class="border-2 border-dashed rounded-lg p-8 text-center transition">
        <svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48">
            <path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
        </svg>
        <p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Drag and drop files here, or click to select</p>
    </div>
    <ul x-show="files.length > 0" class="mt-4 space-y-2">
        <template x-for="file in files" :key="file.name">
            <li class="flex justify-between items-center p-3 bg-gray-50 dark:bg-gray-700 rounded">
                <span class="text-sm" x-text="file.name"></span>
                <span class="text-xs text-gray-500" x-text="file.size"></span>
            </li>
        </template>
    </ul>
</div>

Drag & Drop File Uploader

Drag and drop files with visual feedback and validation

Drag and drop files here, or click to select

Code
<div x-data="{ files: [], dragging: false }">
    <div &#64;drop.prevent="
            dragging = false;
            const droppedFiles = Array.from($event.dataTransfer.files);
            files = droppedFiles.map(f => ({ name: f.name, size: (f.size / 1024).toFixed(2) + &#39; KB&#39; }));
        "
        &#64;dragover.prevent="dragging = true"
        &#64;dragleave.prevent="dragging = false"
        :class="dragging ? &#39;border-blue-500 bg-blue-50 dark:bg-blue-900/20&#39; : &#39;border-gray-300 dark:border-gray-600&#39;"
        class="border-2 border-dashed rounded-lg p-8 text-center transition">
        <svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48">
            <path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
        </svg>
        <p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Drag and drop files here, or click to select</p>
    </div>
    <ul x-show="files.length > 0" class="mt-4 space-y-2">
        <template x-for="file in files" :key="file.name">
            <li class="flex justify-between items-center p-3 bg-gray-50 dark:bg-gray-700 rounded">
                <span class="text-sm" x-text="file.name"></span>
                <span class="text-xs text-gray-500" x-text="file.size"></span>
            </li>
        </template>
    </ul>
</div>

Multiple File Upload with Progress

Upload multiple files with individual progress bars

Code
<div x-data="{ 
    files: [],
    addFiles(event) {
        const newFiles = Array.from(event.target.files).map(f => ({
            name: f.name,
            size: (f.size / 1024).toFixed(2) + ' KB',
            progress: 0
        }));
        this.files = [...this.files, ...newFiles];
        this.uploadFiles();
    },
    uploadFiles() {
        this.files.forEach((file, index) => {
            const interval = setInterval(() => {
                if (file.progress < 100) {
                    file.progress += 10;
                } else {
                    clearInterval(interval);
                }
            }, 200);
        });
    }
}">
    <input type="file" multiple @change="addFiles($event)" class="block w-full text-sm">
    <div x-show="files.length > 0" class="mt-4 space-y-3">
        <template x-for="(file, index) in files" :key="index">
            <div class="p-3 bg-gray-50 dark:bg-gray-700 rounded">
                <div class="flex justify-between mb-2">
                    <span class="text-sm font-medium" x-text="file.name"></span>
                    <span class="text-xs text-gray-500" x-text="file.size"></span>
                </div>
                <div class="w-full bg-gray-200 dark:bg-gray-600 rounded-full h-2">
                    <div class="bg-blue-600 h-2 rounded-full transition-all" :style="`width: ${file.progress}%`"></div>
                </div>
            </div>
        </template>
    </div>
</div>

Image Carousel/Slider

Auto-playing carousel with navigation controls and indicators

Code
<div x-data="{
    current: 0,
    images: [
        &#39;https://picsum.photos/800/400?random=1&#39;,
        &#39;https://picsum.photos/800/400?random=2&#39;,
        &#39;https://picsum.photos/800/400?random=3&#39;
    ],
    next() { this.current = (this.current + 1) % this.images.length; },
    prev() { this.current = this.current === 0 ? this.images.length - 1 : this.current - 1; }
}" x-init="setInterval(() => next(), 5000)">
    <div class="relative overflow-hidden rounded-lg">
        <img :src="images[current]" class="w-full h-64 object-cover">
        
        <button &#64;click="prev()" class="absolute left-2 top-1/2 -translate-y-1/2 bg-white/80 p-2 rounded-full">
            <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
            </svg>
        </button>
        
        <button &#64;click="next()" class="absolute right-2 top-1/2 -translate-y-1/2 bg-white/80 p-2 rounded-full">
            <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/>
            </svg>
        </button>
        
        <div class="absolute bottom-4 left-1/2 -translate-x-1/2 flex gap-2">
            <template x-for="(img, index) in images" :key="index">
                <button &#64;click="current = index" 
                    :class="current === index ? &#39;bg-white&#39; : &#39;bg-white/50&#39;"
                    class="w-2 h-2 rounded-full"></button>
            </template>
        </div>
    </div>
</div>

Video Player with Controls

Custom video player with play/pause and volume controls

Code
<div x-data="{ playing: false, volume: 50 }">
    <div class="relative bg-black rounded-lg overflow-hidden">
        <video x-ref="video" class="w-full" 
            src="https://www.w3schools.com/html/mov_bbb.mp4"
            &#64;click="playing = !playing; playing ? $refs.video.play() : $refs.video.pause()">
        </video>
        
        <div class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 p-4">
            <div class="flex items-center gap-4">
                <button &#64;click="playing = !playing; playing ? $refs.video.play() : $refs.video.pause()" 
                    class="text-white">
                    <svg x-show="!playing" class="w-8 h-8" fill="currentColor" viewBox="0 0 20 20">
                        <path d="M6.3 2.841A1.5 1.5 0 004 4.11V15.89a1.5 1.5 0 002.3 1.269l9.344-5.89a1.5 1.5 0 000-2.538L6.3 2.84z"/>
                    </svg>
                    <svg x-show="playing" class="w-8 h-8" fill="currentColor" viewBox="0 0 20 20">
                        <path d="M5 4a2 2 0 012-2h2a2 2 0 012 2v12a2 2 0 01-2 2H7a2 2 0 01-2-2V4zm8 0a2 2 0 012-2h2a2 2 0 012 2v12a2 2 0 01-2 2h-2a2 2 0 01-2-2V4z"/>
                    </svg>
                </button>
                
                <input type="range" min="0" max="100" x-model="volume" 
                    &#64;input="$refs.video.volume = volume / 100"
                    class="w-24">
                <span class="text-white text-sm" x-text="volume + &#39;%&#39;"></span>
            </div>
        </div>
    </div>
</div>

Audio Player

Custom audio player with progress bar and time display

Code
<div x-data="{ 
    playing: false, 
    currentTime: &#39;0:00&#39;, 
    duration: &#39;0:00&#39;,
    progress: 0
}" x-init="
    $refs.audio.addEventListener(&#39;loadedmetadata&#39;, () => {
        duration = Math.floor($refs.audio.duration / 60) + &#39;:&#39; + (Math.floor($refs.audio.duration % 60)).toString().padStart(2, &#39;0&#39;);
    });
    $refs.audio.addEventListener(&#39;timeupdate&#39;, () => {
        currentTime = Math.floor($refs.audio.currentTime / 60) + &#39;:&#39; + (Math.floor($refs.audio.currentTime % 60)).toString().padStart(2, &#39;0&#39;);
        progress = ($refs.audio.currentTime / $refs.audio.duration) * 100;
    });
">
    <div class="bg-gradient-to-r from-purple-500 to-pink-500 rounded-lg p-6 text-white">
        <audio x-ref="audio" src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"></audio>
        
        <div class="flex items-center gap-4">
            <button &#64;click="playing = !playing; playing ? $refs.audio.play() : $refs.audio.pause()"
                class="bg-white text-purple-600 p-3 rounded-full">
                <svg x-show="!playing" class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
                    <path d="M6.3 2.841A1.5 1.5 0 004 4.11V15.89a1.5 1.5 0 002.3 1.269l9.344-5.89a1.5 1.5 0 000-2.538L6.3 2.84z"/>
                </svg>
                <svg x-show="playing" class="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
                    <path d="M5 4a2 2 0 012-2h2a2 2 0 012 2v12a2 2 0 01-2 2H7a2 2 0 01-2-2V4zm8 0a2 2 0 012-2h2a2 2 0 012 2v12a2 2 0 01-2 2h-2a2 2 0 01-2-2V4z"/>
                </svg>
            </button>
            
            <div class="flex-1">
                <div class="flex justify-between text-sm mb-2">
                    <span x-text="currentTime"></span>
                    <span x-text="duration"></span>
                </div>
                <div class="w-full bg-white/30 rounded-full h-2">
                    <div class="bg-white h-2 rounded-full transition-all" :style="`width: ${progress}%`"></div>
                </div>
            </div>
        </div>
    </div>
</div>

PDF Viewer

Embedded PDF viewer with iframe

Code
<div class="border dark:border-gray-600 rounded-lg overflow-hidden">
    <iframe src="https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" 
        class="w-full h-96"></iframe>
</div>

Avatar Upload with Crop Preview

Avatar upload with circular crop preview

Profile Picture

JPG, PNG or GIF. Max 2MB.

Click the camera icon to upload

Code
<div x-data="{ avatar: null }">
    <div class="flex items-center gap-6">
        <div class="relative">
            <div class="w-32 h-32 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center overflow-hidden">
                <img x-show="avatar" :src="avatar" class="w-full h-full object-cover">
                <svg x-show="!avatar" class="w-16 h-16 text-gray-400" fill="currentColor" viewBox="0 0 20 20">
                    <path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"/>
                </svg>
            </div>
            <label class="absolute bottom-0 right-0 bg-blue-600 text-white p-2 rounded-full cursor-pointer hover:bg-blue-700">
                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"/>
                </svg>
                <input type="file" class="hidden" accept="image/*" &#64;change="
                    const file = $event.target.files[0];
                    if (file) {
                        const reader = new FileReader();
                        reader.onload = (e) => avatar = e.target.result;
                        reader.readAsDataURL(file);
                    }
                ">
            </label>
        </div>
        <div>
            <h4 class="font-semibold text-gray-900 dark:text-white">Profile Picture</h4>
            <p class="text-sm text-gray-500 dark:text-gray-400">JPG, PNG or GIF. Max 2MB.</p>
        </div>
    </div>
</div>