Skip to content
Brok UI

Loading…

No results

File Thumbnail

Open source

Compact tile that previews a file by type with an icon, extension badge and filename caption.

Version
v1.2.2
Stability
stable
License
MIT
Related
Dropzone
File Finder
Document Frame

Preview

invoice.pdf
proposal.docx
budget-2026.xlsx
pitch.pptx
export.csv
cover.jpg
assets.zip
intro.mp3
demo-recording.mp4
config.json
notes.txt
unknown.bin
previews.components.file-thumbnail.default.blade.php Blade
<div class="mx-auto max-w-lg">
    <div class="grid grid-cols-4 gap-6 sm:grid-cols-6">
        <x-ui.file-thumbnail type="pdf" name="invoice.pdf" />
        <x-ui.file-thumbnail type="docx" name="proposal.docx" />
        <x-ui.file-thumbnail type="xlsx" name="budget-2026.xlsx" />
        <x-ui.file-thumbnail type="pptx" name="pitch.pptx" />
        <x-ui.file-thumbnail type="csv" name="export.csv" />
        <x-ui.file-thumbnail
            type="image"
            name="cover.jpg"
            src="https://images.unsplash.com/photo-1492144534655-ae79c964c9d7?auto=format&fit=crop&w=200&q=60"
        />
        <x-ui.file-thumbnail type="zip" name="assets.zip" />
        <x-ui.file-thumbnail type="mp3" name="intro.mp3" />
        <x-ui.file-thumbnail type="mp4" name="demo-recording.mp4" />
        <x-ui.file-thumbnail type="json" name="config.json" />
        <x-ui.file-thumbnail type="txt" name="notes.txt" />
        <x-ui.file-thumbnail type="generic" name="unknown.bin" />
    </div>
</div>

Installation

terminal
php artisan ui:add file-thumbnail

Registry contract

php artisan ui:add file-thumbnail writes only the files below. The CLI validates each file hash before writing and asks before it replaces a local change, unless you pass --force.

  • blade resources/views/components/ui/file-thumbnail.blade.php
Registry dependencies
None — installs on its own.
Packages
composer: jml/brok:^0.2

Use with AI

A brief for your coding agent: install command, usage, props, guidance and the rules. Copy it, or open a prompt about this component in an assistant.

file-thumbnail.md
# Brok UI: File Thumbnail (`file-thumbnail`)

Compact tile that previews a file by type with an icon, extension badge and filename caption.

Brok UI is a Laravel Blade component registry. Installed components are plain Blade files the app owns.

## Install

```bash
php artisan ui:add file-thumbnail
```

## Usage

```blade
<div class="mx-auto max-w-lg">
    <div class="grid grid-cols-4 gap-6 sm:grid-cols-6">
        <x-ui.file-thumbnail type="pdf" name="invoice.pdf" />
        <x-ui.file-thumbnail type="docx" name="proposal.docx" />
        <x-ui.file-thumbnail type="xlsx" name="budget-2026.xlsx" />
        <x-ui.file-thumbnail type="pptx" name="pitch.pptx" />
        <x-ui.file-thumbnail type="csv" name="export.csv" />
        <x-ui.file-thumbnail
            type="image"
            name="cover.jpg"
            src="https://images.unsplash.com/photo-1492144534655-ae79c964c9d7?auto=format&fit=crop&w=200&q=60"
        />
        <x-ui.file-thumbnail type="zip" name="assets.zip" />
        <x-ui.file-thumbnail type="mp3" name="intro.mp3" />
        <x-ui.file-thumbnail type="mp4" name="demo-recording.mp4" />
        <x-ui.file-thumbnail type="json" name="config.json" />
        <x-ui.file-thumbnail type="txt" name="notes.txt" />
        <x-ui.file-thumbnail type="generic" name="unknown.bin" />
    </div>
</div>
```

## Props

- `type` (string, default `generic`) — File extension or family used to choose the icon glyph and tone, such as pdf, docx, xlsx, zip, mp3, mp4, or an image extension.
- `name` (mixed|null, default `null`) — Filename shown in the caption and used as the tile's accessible label; the extension is kept from truncating even when the base name is long.
- `src` (mixed|null, default `null`) — Image URL; when type is an image family and src is set, renders an actual thumbnail instead of the generic page glyph.
- `size` (sm|lg, default `md`) — Tile size: sm, md, or lg.
- `caption` (bool, default `true`) — Shows the filename caption under the tile.

## Use when

- Use when users need to upload, inspect, edit, annotate, or sign real documents as part of the workflow.
- Representing an uploaded or attached file in a grid or list before it is opened, such as in a file picker or attachments panel.
- Needing a type-appropriate icon and extension tag without loading the actual file content.

## Avoid when

- Do not use a heavy document interface when the same task can be completed more simply in structured product UI.
- The file's content itself should be previewed inline; use pdf-viewer, docx-viewer, excel-viewer, or csv-viewer for that file type.
- Listing many files in a dense, sortable table; use table with a smaller icon instead.

## Anti-patterns

- Heavy document UI when structured fields are simpler

## Rules

- Use the `<brok:file-thumbnail>` tag (or `<x-ui.file-thumbnail>`) in Blade; do not rewrite the component.
- Prefer the documented props and variants over utility-class overrides; when a utility must win, use the `!` important modifier.
- Use semantic design tokens (`bg-primary`, `text-muted-foreground`), never raw colour utilities.
- Keep the `data-slot` attributes; they are the styling and test hooks.

## Links

- Docs: https://brokui.dev/docs/components/file-thumbnail
- Registry JSON (files, props, contract): https://brokui.dev/r/open/file-thumbnail.json

Working in Claude Code, Cursor or Codex? Give the agent the whole registry through the MCP server or the Brok UI skill.

API

manifest knowledge + registry-derived coverage

Props

Props accepted by this component: name, type, default value and description.
Prop Type Default Description
type string generic File extension or family used to choose the icon glyph and tone, such as pdf, docx, xlsx, zip, mp3, mp4, or an image extension.
name mixed | null null Filename shown in the caption and used as the tile's accessible label; the extension is kept from truncating even when the base name is long.
src mixed | null null Image URL; when type is an image family and src is set, renders an actual thumbnail instead of the generic page glyph.
size sm | lg md Tile size: sm, md, or lg.
caption bool true Shows the filename caption under the tile.

Slots

  • default —

Data slots

Stable hooks for CSS overrides and browser tests.

file-thumbnail

Behavior

  • Maps the type prop to a file family (document, spreadsheet, slides, archive, audio, video, code, image, or generic), each with its own glyph and token-bound tone.
  • For image types with a src, renders an actual cropped thumbnail instead of the page-silhouette glyph.
  • The caption keeps the file extension from truncating, so a long base name shortens while ".xlsx" or similar stays visible.
  • Declares registry capability flags: a11y, responsive, rtl, darkMode, localized.

Guidance

Document workflow

Upload, inspect, edit, annotate, or sign a real document.

Use when

  • Use when users need to upload, inspect, edit, annotate, or sign real documents as part of the workflow.
  • Representing an uploaded or attached file in a grid or list before it is opened, such as in a file picker or attachments panel.
  • Needing a type-appropriate icon and extension tag without loading the actual file content.

Avoid when

  • Do not use a heavy document interface when the same task can be completed more simply in structured product UI.
  • The file's content itself should be previewed inline; use pdf-viewer, docx-viewer, excel-viewer, or csv-viewer for that file type.
  • Listing many files in a dense, sortable table; use table with a smaller icon instead.

Use instead

  • Structured product form
  • Direct data entry

Anti-patterns

  • Heavy document UI when structured fields are simpler
Anatomy
root
Theming hooks
file-thumbnail

Accessibility

WCAG 2.2 AA Keyboard focus-visible RTL-ready Localized labels Dark mode
Keyboard
No component-owned keyboard interaction; native element behavior applies.
Focus
none
  • The tile carries role="img" with an aria-label built from the filename, so the file type and name are announced as one unit rather than through a decorative icon plus separate caption text.
  • Semantic HTML and a stable data-slot attribute for styling and scripting hooks.
  • Focus-visible rings use the ring token, so keyboard focus is always visible.
  • Disabled and invalid states are conveyed to assistive tech, not by color alone.
  • Targets WCAG 2.2 AA; verify contrast in light, dark, admin and customer surfaces in the preview.
  • Labels go through __() and layout uses logical properties (ms-*, text-start), so it mirrors under dir="rtl" — flip the preview to RTL to confirm.
  • Dark mode uses the same semantic tokens under the dark class; high contrast follows forced-color system tokens.

Livewire

Safe

Livewire can update this component through forwarded wire:* attributes.

Source

The exact, editable file ui:add writes into your app. Previews render this same code; there are no preview-only components.

resources/views/components/ui/file-thumbnail.blade.php Blade
@props([
    'type' => 'generic',
    'name' => null,
    'src' => null,
    'size' => 'md',
    'caption' => true,
])

@php
    $type = strtolower(trim((string) $type));

    $imageTypes = ['image', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'avif'];
    $isImage = in_array($type, $imageTypes, true);

    // type → family: the page's tint + tag colour and its content glyph. Each
    // family uses one status/chart token so the tile stays theme-bound.
    $families = [
        'pdf' => ['pdf', 'destructive'],
        'doc' => ['document', 'info'], 'docx' => ['document', 'info'], 'txt' => ['document', 'muted'], 'rtf' => ['document', 'info'], 'md' => ['document', 'muted'],
        'xls' => ['spreadsheet', 'success'], 'xlsx' => ['spreadsheet', 'success'], 'csv' => ['spreadsheet', 'success'], 'numbers' => ['spreadsheet', 'success'],
        'ppt' => ['slides', 'warning'], 'pptx' => ['slides', 'warning'], 'key' => ['slides', 'warning'],
        'zip' => ['archive', 'warning'], 'rar' => ['archive', 'warning'], '7z' => ['archive', 'warning'], 'tar' => ['archive', 'warning'], 'gz' => ['archive', 'warning'],
        'mp3' => ['audio', 'chart-4'], 'wav' => ['audio', 'chart-4'], 'm4a' => ['audio', 'chart-4'],
        'mp4' => ['video', 'chart-4'], 'mov' => ['video', 'chart-4'], 'webm' => ['video', 'chart-4'],
        'js' => ['code', 'chart-1'], 'ts' => ['code', 'chart-1'], 'php' => ['code', 'chart-1'], 'json' => ['code', 'chart-1'], 'html' => ['code', 'chart-1'], 'css' => ['code', 'chart-1'],
    ];
    foreach ($imageTypes as $it) {
        $families[$it] = ['image', 'chart-2'];
    }
    [$glyph, $tone] = $families[$type] ?? ['generic', 'muted'];

    // Fixed utilities per tone (JIT-visible; never spliced).
    $tones = [
        'destructive' => ['page' => 'fill-destructive/10 stroke-destructive/40', 'glyph' => 'text-destructive', 'tag' => 'bg-destructive text-destructive-foreground'],
        'info' => ['page' => 'fill-info/10 stroke-info/40', 'glyph' => 'text-info', 'tag' => 'bg-info text-primary-foreground'],
        'success' => ['page' => 'fill-success/10 stroke-success/40', 'glyph' => 'text-success', 'tag' => 'bg-success text-primary-foreground'],
        'warning' => ['page' => 'fill-warning/10 stroke-warning/40', 'glyph' => 'text-warning', 'tag' => 'bg-warning text-primary-foreground'],
        'chart-1' => ['page' => 'fill-chart-1/10 stroke-chart-1/40', 'glyph' => 'text-chart-1', 'tag' => 'bg-chart-1 text-primary-foreground'],
        'chart-2' => ['page' => 'fill-chart-2/10 stroke-chart-2/40', 'glyph' => 'text-chart-2', 'tag' => 'bg-chart-2 text-primary-foreground'],
        'chart-4' => ['page' => 'fill-chart-4/10 stroke-chart-4/40', 'glyph' => 'text-chart-4', 'tag' => 'bg-chart-4 text-primary-foreground'],
        'muted' => ['page' => 'fill-muted stroke-border', 'glyph' => 'text-muted-foreground', 'tag' => 'bg-muted-foreground text-primary-foreground'],
    ];
    $t = $tones[$tone];

    $tileSize = match ($size) {
        'sm' => 'size-10',
        'lg' => 'size-20',
        default => 'size-14',
    };
    $tagSize = match ($size) {
        'sm' => 'px-1 text-[8px] leading-3',
        'lg' => 'px-2 text-xs leading-5',
        default => 'px-1 text-2xs leading-4',
    };
    $captionWidth = match ($size) {
        'sm' => 'max-w-20',
        'lg' => 'max-w-32',
        default => 'max-w-24',
    };

    $extension = $type !== '' && $type !== 'generic' && ! $isImage ? strtoupper($type) : null;

    // Caption keeps the extension visible: the base name truncates, the
    // extension never does ("budget-2026….xlsx" rather than "budget-…").
    $dot = $name !== null ? strrpos($name, '.') : false;
    $base = $dot !== false && $dot > 0 ? substr($name, 0, $dot) : (string) $name;
    $ext = $dot !== false && $dot > 0 ? substr($name, $dot) : '';

    $label = $name !== null && $name !== ''
        ? $name
        : __(':type file', ['type' => strtoupper($type !== '' ? $type : 'generic')]);
@endphp

<div
    data-slot="file-thumbnail"
    data-type="{{ $type }}"
    data-size="{{ $size }}"
    {{ $attributes->merge(['class' => 'inline-flex flex-col items-center gap-2 text-center']) }}
>
    <div
        role="img"
        aria-label="{{ $label }}"
        class="relative flex shrink-0 items-center justify-center {{ $tileSize }}"
    >
        @if ($isImage && $src)
            {{-- The wrapping container carries role="img" + aria-label, so the
                 image itself is decorative to avoid a double announcement. --}}
            <img
                src="{{ $src }}"
                alt=""
                aria-hidden="true"
                loading="lazy"
                class="size-full rounded-lg border border-border object-cover"
            />
        @else
            {{-- A page silhouette with a folded corner fills the tile; the type
                 glyph sits in the page, the extension tag overlaps its bottom
                 edge so it never covers the glyph. --}}
            <svg viewBox="0 0 40 48" class="h-full w-auto {{ $t['glyph'] }}" aria-hidden="true">
                <path d="M6 1.5h20l8 8V44a2.5 2.5 0 0 1-2.5 2.5h-25A2.5 2.5 0 0 1 4 44V4a2.5 2.5 0 0 1 2.5-2.5z" class="{{ $t['page'] }}" stroke-width="1.25" stroke-linejoin="round" />
                <path d="M26 1.5v6a2 2 0 0 0 2 2h6" class="{{ $t['page'] }}" stroke-width="1.25" stroke-linejoin="round" fill="none" />
                <g fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" transform="translate(11 16) scale(0.75)">
                    @switch($glyph)
                        @case('pdf')
                            <path d="M3 4h18M3 9h18M3 14h10M3 19h6" />
                            @break
                        @case('document')
                            <path d="M3 5h18M3 10h18M3 15h12" />
                            @break
                        @case('spreadsheet')
                            <path d="M3 4h18v14H3zM3 9h18M3 13.5h18M10 4v14M16 4v14" />
                            @break
                        @case('slides')
                            <path d="M3 4h18v12H3zM12 16v4M8 20h8" />
                            @break
                        @case('archive')
                            <path d="M10 3v2m0 2v2m0 2v2M8 13h4v6H8z" />
                            @break
                        @case('audio')
                            <path d="M9 18V6l11-2v12M6 18a3 3 0 1 0 6 0 3 3 0 0 0-6 0zM17 16a3 3 0 1 0 6 0 3 3 0 0 0-6 0z" />
                            @break
                        @case('video')
                            <path d="M3 5h13v14H3zM16 10l6-3v10l-6-3z" />
                            @break
                        @case('code')
                            <path d="M8 6 2 12l6 6M16 6l6 6-6 6" />
                            @break
                        @case('image')
                            <path d="M3 4h18v16H3zM8 10a2 2 0 1 0 0-.01M21 16l-5-5-9 9" />
                            @break
                        @default
                            <path d="M6 12h12M12 6v12" class="opacity-0" />
                    @endswitch
                </g>
            </svg>

            @if ($extension)
                <span class="absolute -bottom-1 start-0 rounded-sm font-semibold uppercase tracking-wide shadow-sm {{ $tagSize }} {{ $t['tag'] }}">
                    {{ $extension }}
                </span>
            @endif
        @endif
    </div>

    @if ($caption && $name !== null && $name !== '')
        <span class="flex text-xs text-muted-foreground {{ $captionWidth }}" title="{{ $name }}">
            <span class="min-w-0 truncate">{{ $base }}</span><span class="shrink-0">{{ $ext }}</span>
        </span>
    @endif
</div>

Ownership & lifecycle

Owner, release state, review evidence and adoption for this item.
Owner
Platform UI (@JoshJML)
Current version
1.2.2
Status
Stable
License
open
Accessibility reviewed
No review date recorded
Last breaking change
No date recorded
Deprecation
Not deprecated
Contract
v6
Foundation
≥ 1.0.0