dmhendricks/file-icon-vectors
A PHP library that maps file extensions to crisp SVG icons, making it easy to display accurate file-type icons for documents, media, archives, and more. Includes multiple icon styles, simple helper methods, and Laravel support for quick integration.
dmhendricks/file-icon-vectors is not a Laravel package — there is no service provider, no facade, no config file, no auto-discovery. It is a Composer-installed folder of SVG icons plus pre-built CSS that maps .fiv-icon-{extension} classes to those SVGs. Treat it as a static asset dependency.
1. Install
composer require dmhendricks/file-icon-vectors
2. Look at what you actually got
ls vendor/dmhendricks/file-icon-vectors/dist
ls vendor/dmhendricks/file-icon-vectors/dist/css
You will find the icon sets (classic, vivid, square-outline), a combined stylesheet, and an icons/ directory. Confirm the exact filenames here before writing any <link> tag — the CSS filenames have changed shape across releases.
3. Publish the whole dist/ folder into the webroot
Because vendor/ is not served by the web server, add a tiny provider (see Implementation Patterns) or, for a one-off:
cp -R vendor/dmhendricks/file-icon-vectors/dist public/vendor/file-icon-vectors
4. First use case — an icon next to a filename
{{-- resources/views/layouts/app.blade.php --}}
<link rel="stylesheet"
href="{{ asset('vendor/file-icon-vectors/css/file-icon-vectors.min.css') }}">
<span class="fiv-cla fiv-icon-pdf" aria-hidden="true"></span>
Report.pdf
The pattern is fiv-{set} + fiv-icon-{extension}. Sets are fiv-cla (classic), fiv-viv (vivid), fiv-sqo (square outline). The set class is also the base class — it supplies the inline-block, dimensions and background-size — so both classes are required.
// app/Providers/FileIconVectorsServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class FileIconVectorsServiceProvider extends ServiceProvider
{
public function boot(): void
{
$source = base_path('vendor/dmhendricks/file-icon-vectors/dist');
if (! is_dir($source)) {
return; // package not installed (e.g. --no-dev in some CI images)
}
$this->publishes([
$source => public_path('vendor/file-icon-vectors'),
], 'file-icon-vectors');
}
}
Then php artisan vendor:publish --tag=file-icon-vectors --force becomes a required step in your deploy script. Add it next to config:cache:
composer install --no-dev --optimize-autoloader
php artisan vendor:publish --tag=file-icon-vectors --force
php artisan config:cache
The --force matters: without it, a second deploy will not refresh the assets.
Icons are keyed by file extension, not MIME type. Your storage layer gives you MIME types, so you need a resolver, and you need aliases because the SVG set only ships one spelling per family.
// app/Support/FileIcon.php
namespace App\Support;
class FileIcon
{
/** Extensions the icon set lacks or spells differently. */
private const ALIASES = [
'jpeg' => 'jpg',
'htm' => 'html',
'tif' => 'tiff',
'yaml' => 'yml',
'mpeg' => 'mpg',
];
private const MIME_MAP = [
'application/pdf' => 'pdf',
'application/zip' => 'zip',
'text/plain' => 'txt',
'text/csv' => 'csv',
];
public static function fromPath(?string $path, ?string $mime = null): string
{
$ext = strtolower(pathinfo((string) $path, PATHINFO_EXTENSION));
if ($ext === '' && $mime !== null) {
$ext = self::MIME_MAP[$mime] ?? '';
}
$ext = self::ALIASES[$ext] ?? $ext;
return $ext !== '' ? $ext : 'blank';
}
}
Discover the real inventory instead of guessing:
grep -o 'fiv-icon-[a-z0-9]*' \
vendor/dmhendricks/file-icon-vectors/dist/css/file-icon-vivid.min.css \
| sed 's/.*fiv-icon-//' | sort -u | tr '\n' ' '
// app/View/Components/FileIcon.php
namespace App\View\Components;
use App\Support\FileIcon as Resolver;
use Illuminate\View\Component;
class FileIcon extends Component
{
public string $extension;
public function __construct(
public ?string $path = null,
public ?string $mime = null,
public string $set = 'viv',
public string $size = '1em',
) {
$this->extension = Resolver::fromPath($path, $mime);
}
public function render()
{
return view('components.file-icon');
}
}
{{-- resources/views/components/file-icon.blade.php --}}
<span class="fiv-{{ $set }} fiv-icon-{{ $extension }}"
style="width: {{ $size }}; height: {{ $size }};"
aria-hidden="true"
{{ $attributes }}></span>
<x-file-icon :path="$document->filename" size="1.5rem" class="mr-2" />
<x-file-icon :path="$upload->path" :mime="$upload->mime_type" set="sqo" />
Use the icon as decoration only — the filename text stays the accessible label:
<td class="flex items-center gap-2">
<x-file-icon :path="$file->name" />
<a href="{{ route('files.download', $file) }}">{{ $file->name }}</a>
</td>
Because the base class sets a fixed width/height (typically 1em), you can scale the whole thing by setting font-size on a parent instead of overriding width/height. That keeps the icon square and avoids fighting Tailwind:
<span class="text-2xl"><x-file-icon :path="$file->name" set="cla" /></span>
If you standardise on one set, link the per-set stylesheet rather than the combined bundle — it is a meaningfully smaller CSS payload and it is the only real performance lever you have with this package.
<link rel="stylesheet"
href="{{ asset('vendor/file-icon-vectors/css/file-icon-vivid.min.css') }}">
Vite is the default in modern Laravel, and vendor/ is outside its scope. Two workable options:
public/vendor/file-icon-vectors/ as a published static asset and reference it with asset(). Simple, cache-busted with ?v= if you care.dist/ into resources/ and @import the CSS from app.css. This works only if you also copy the icons and preserve the relative directory depth, because the CSS references ../icons/... — Vite will try to resolve those URLs at build time.Do not @import a stylesheet whose url() paths point into vendor/; you get build errors or silently broken icons.
The whole dist/ tree must move together. The CSS resolves icons relatively (../icons/...). Copying only the .css file into public/css/ gives you a page of empty boxes and a Network tab full of 404s. Publish dist/ as a directory, preserving its internal structure.
No auto-discovery, no config. composer require alone does nothing visible in a Laravel app. If you forget the publish step, the classes exist in your markup but no stylesheet defines them — the span collapses to zero width and you spend twenty minutes wondering why the layout shifted.
The package is frozen at 2018. This is the single biggest caveat. Expect missing modern extensions (heic, avif, webp, ts/tsx, various *x variants, newer Office/design formats). Verify your top extensions exist before designing around the package:
for e in webp avif heic ts docx xlsx; do
grep -q "fiv-icon-$e" vendor/dmhendricks/file-icon-vectors/dist/css/file-icon-vivid.min.css \
&& echo "ok $e" || echo "MISS $e"
done
Keep a fallback (fiv-icon-blank, or your own generic SVG) and consider forking the CSS if a handful of extensions are systematically missing.
Extension ≠ MIME type. image/jpeg is ambiguous between jpg and jpeg; text/x-yaml and application/yaml are both yaml. Always normalise through an alias map, and prefer the filename extension over the MIME type when you have both.
Uppercase extensions break silently. Report.PDF yields fiv-icon-PDF, which no rule matches. strtolower() in one place, always.
Framework resets can break the base class. The fiv-* base class relies on display, width, height and background-size. A Tailwind preflight, Bootstrap reboot, or a span { display: inline } rule from your own CSS can override it because these are single-class selectors. If icons render at the wrong size, inspect computed styles on the span first — it is almost always a reset, not the package.
Don't fight the sizing with utility classes. Setting w-6 h-6 directly on the span usually loses to the package's own width/height or produces a stretched icon. Drive the size via font-size on the parent or an inline style, as in the component above.
Accessibility. These are decorative. Always emit aria-hidden="true" and never let the icon be the only signal of file type — screen readers get nothing from fiv-icon-xlsx. Pair with the filename or an explicit label.
Deployment ordering. vendor:publish is not run by composer install. If your deploy pipeline only runs composer install && artisan migrate, the icons will 404 in production even though they worked locally — locally they were probably published months ago and the files are sitting in public/ (possibly gitignored).
Cache the grep, not the discovery. If you build a dropdown of selectable file types from the icon inventory, do the grep once at build time and commit the resulting list; do not shell out to grep at request time.
Where to extend: there is no extension point, but the CSS is trivially forkable. For a missing extension, add a rule pointing at a generic SVG:
/* app/overrides/file-icon-vectors.css — loaded after the package CSS */
.fiv-viv.fiv-icon-webp { background-image: url("/vendor/file-icon-vectors/icons/vivid/image.svg"); }
That is the pragmatic escape hatch for a package that will not be updated. If the missing-extension list grows past a handful, switch to a maintained alternative (the file-icon-vectors npm package, devicon, or inline SVGs via <x-icon>) rather than maintaining a fork of a dead asset set.
Debugging checklist, in order: (1) does View Source show the <link>? (2) does the CSS URL return 200? (3) do the icon URLs inside the CSS return 200? (4) does the class name exactly match a rule (grep fiv-icon-yourtype)? (5) is the element's computed width/height zero? Ninety percent of issues are step 2 or step 4.
How can I help you explore Laravel packages today?