Skip to content
Back to skills

File Preview

ASecurity

Use this skill when previewing files with nuxt-ui-tools as a package consumer. Covers mounting UiFilePreviewProvider, opening one file or a gallery with useFilePreview() or useNuxtApp().$filePreview, file descriptors (URL, File/Blob, or a function that returns a signed URL), options (index, mode, gallery, loop, pdf page, callbacks), the returned handle, the built-in renderers (image, video, audio, PDF, text/JSON, CSV, Markdown, Office and archive fallbacks), renditions, download and custom he...

  • 2 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 29, 2026
developmentgovueapi

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 29, 2026

npx -y skills add ChronicStone/nuxt-ui-tools --skill file-preview --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of File Preview?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for File Preview
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/chronicstone-file-preview/badge)](https://www.skillsdirectory.com/skills/chronicstone-file-preview)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: nuxt-ui-tools-file-preview
description: Use this skill when previewing files with nuxt-ui-tools as a package consumer. Covers mounting UiFilePreviewProvider, opening one file or a gallery with useFilePreview() or useNuxtApp().$filePreview, file descriptors (URL, File/Blob, or a function that returns a signed URL), options (index, mode, gallery, loop, pdf page, callbacks), the returned handle, the built-in renderers (image, video, audio, PDF, text/JSON, CSV, Markdown, Office and archive fallbacks), renditions, download and custom header actions, the markdown render hook, custom renderers with defineFilePreviewRenderer and UiFilePreviewTools, theming tokens, and how the upload field uses the preview.
---

# nuxt-ui-tools File Preview

One call opens any file in an overlay with a renderer made for its kind. Media opens in a modal,
documents in a right drawer, and phones get fullscreen. Renderers load on first use, so an app only
downloads the code for the kinds it actually shows.

## Mount the provider once

The module creates one API for the whole app. Mount the provider next to the form provider; it
renders the previews:

```vue
<template>
  <UApp>
    <UiToolsProvider :locale="uiToolsLocale">
      <UiFormProvider>
        <UiFilePreviewProvider :markdown="renderMarkdown">
          <NuxtPage />
        </UiFilePreviewProvider>
      </UiFormProvider>
    </UiToolsProvider>
  </UApp>
</template>
```

The component prefix follows the module `prefix` option (`Ui` by default).

`markdown` is optional. It receives the file text and must return sanitized HTML (or a promise of
it). Without it, Markdown files show their source.

## Open files

Components use `useFilePreview()`. Code outside setup, such as entity actions or stores, uses
`useNuxtApp().$filePreview`:

```ts
export function previewAccountFile(account: { id: string; name: string }, file: 'kbis' | 'logo') {
  const { $filePreview } = useNuxtApp()
  return $filePreview.open({
    name: `${file.toUpperCase()} — ${account.name}.pdf`,
    src: `/v1/accounts/${account.id}/files/${file}`,
  })
}
```

`open()` accepts one input or an array (a gallery). Each input is a URL string, a `File` or `Blob`,
or a descriptor:

```ts
const preview = $filePreview.open(
  contract.files.map((file) => ({
    id: file.id,
    name: file.label,
    mime: file.mimeType,
    size: file.size,
    updatedAt: file.createdAt,
    // Runs only when this file is shown, and again when the user presses Retry.
    src: () => $api.contracts.fileUrl({ params: { fileId: file.id } }).then((res) => res.url),
    download: () => downloadContractFile(file),
    actions: [{ icon: 'i-lucide-folder-open', label: 'Open in registry', to: registryPath(file) }],
  })),
  { index: 2 },
)

preview.next()
await preview.closed
```

### Descriptor fields

| Field                 | Meaning                                                                                                                                                                        |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `src`                 | URL, `File`/`Blob`, or `() => MaybePromise<string \| Blob>`. Functions resolve once per file and again on Retry.                                                               |
| `name`                | Display name. Defaults to `File.name`, then the last URL segment.                                                                                                              |
| `mime`                | Wins over the extension to pick a renderer.                                                                                                                                    |
| `kind`                | Forces a renderer (`'image'`, `'pdf'`, …, or a custom kind).                                                                                                                   |
| `size`, `updatedAt`   | Shown in the meta line and the details panel. `File` inputs fill them.                                                                                                         |
| `thumbnail`, `poster` | Gallery strip image; video poster or audio cover.                                                                                                                              |
| `rendition`           | A preview made by your server for files browsers cannot render (DOCX → PDF, HEIC → JPEG). The preview shows it with a "Converted preview" notice; download keeps the original. |
| `details`             | Extra `{ label, value }` rows in the details panel.                                                                                                                            |
| `download`            | `false` hides it, a string downloads that URL, a function replaces the download. Default: the resolved source under `name`.                                                    |
| `actions`             | Extra header buttons: `{ label, icon?, to?, onSelect? }`. External `to` opens a new tab; internal `to` closes the preview and navigates.                                       |
| `tracks`              | Video captions: `{ src, srcLang, label, kind?, default? }`.                                                                                                                    |

Text values (`label`, `value`) accept a getter such as `() => t('files.uploadedBy')` so they follow
the language.

### Options

| Option                               | Meaning                                                                                                                                                       |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `index`                              | File shown first.                                                                                                                                             |
| `mode`                               | `'modal' \| 'drawer' \| 'fullscreen'` or a responsive value like `'fullscreen md:drawer'`. Responsive values start at `sm`, so the first value covers phones. |
| `id`                                 | Opening the same id again updates that preview instead of stacking another.                                                                                   |
| `gallery`                            | `'strip'` (default, thumbnails) or `'counter'`. Phones always show the counter.                                                                               |
| `loop`                               | Wraps from the last file to the first.                                                                                                                        |
| `pdf.page`                           | Page PDFs open at.                                                                                                                                            |
| `onChange(file, index)`, `onClose()` | Callbacks.                                                                                                                                                    |

Without `mode`, each file's renderer suggests a container and the largest wins, so a gallery keeps
one container while navigating: images and video use `fullscreen md:modal`, documents
`fullscreen md:drawer`. A single audio file or fallback card uses a narrow modal.

### Handle

`open()` returns right away: `{ id, index (readonly ref), next(), previous(), goTo(i), update(input), close(), closed }`.
`closed` resolves once the overlay has left the screen. The API also has `close(id?)` (top-most
preview when omitted), `closeAll()`, `isOpen(id?)`, `instances`, and `mounted` (a provider is
rendering).

## Built-in renderers

| Kind                         | Matches                                        | Rendering                                                                                                                                                                           |
| ---------------------------- | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `image`                      | `image/*`, png jpg webp gif avif svg heic…     | Fits without upscaling; wheel/pinch/double-click zoom, drag to pan, rotate. Transparent formats sit on a checkerboard. SVG renders through `<img>`, so its scripts never run.       |
| `video`                      | `video/*`, mp4 webm mov…                       | `<video>` with custom controls (play, seek, volume, speed, captions, picture-in-picture, fullscreen).                                                                               |
| `audio`                      | `audio/*`, mp3 m4a wav…                        | Card with cover and the same controls.                                                                                                                                              |
| `pdf`                        | `application/pdf`                              | The browser's own viewer in an iframe (`#view=FitH`). Blob sources are re-typed as PDF. When `navigator.pdfViewerEnabled` is `false` (Android), it shows Open and Download instead. |
| `text`                       | `text/*`, json, xml, yaml, log, code files     | Line numbers, wrap, copy. JSON is formatted and tinted.                                                                                                                             |
| `csv`                        | `text/csv`, csv, tsv                           | Table with sticky header, row numbers, right-aligned numbers. Detects `;`, `,`, tab, and `\|`, and strips the BOM. Shows the first 1,000 rows.                                      |
| `markdown`                   | md, `text/markdown`                            | Your `markdown` hook's HTML with a Preview/Source switch, or the source.                                                                                                            |
| `office`, `archive`, `other` | doc(x) xls(x) ppt(x) odt…, zip…, anything else | A card with the facts and Download. Pass a `rendition` to preview Office files.                                                                                                     |

Text, CSV, and Markdown read at most 512 KB (`FILE_PREVIEW_TEXT_LIMIT`) and say when a file was cut.
They read `Blob` sources directly; for URLs they use `fetch`, so the URL must be readable from the
page (same origin, or CORS allowing your origin). Images, video, audio, and PDFs use elements that
follow redirects without CORS.

Errors show a reason and the right action: Retry when `src` is a function (an expired signed link),
Download when the browser cannot decode the format.

Keys: `←` `→` `Home` `End` navigate, `Esc` closes, `+` `−` `0` `R` zoom, fit, and rotate images,
`Space` `K` `M` `F` control media.

## Custom renderers

Register a renderer in a plugin. Registering a built-in kind replaces it (for example, a pdf.js
renderer for phones):

```ts
// app/plugins/file-preview.ts
export default defineNuxtPlugin(() => {
  useNuxtApp().$filePreview.register(
    defineFilePreviewRenderer({
      kind: 'email',
      icon: 'i-lucide-mail',
      label: () => 'Email',
      match: ({ mime, extension }) => mime === 'message/rfc822' || extension === 'eml',
      component: () => import('~/components/previews/EmailPreview.vue'),
      mode: 'fullscreen md:drawer',
    }),
  )
})
```

The component receives `{ file, url, blob, container }` and emits `ready(details?)` once content is
on screen and `error({ reason, message? })` when it cannot render. Put toolbar buttons in
`<UiFilePreviewTools>`; they appear in the header, or in the bottom bar on phones. Use
`useFilePreviewText(props)` to read text with the size limit, and `onFilePreviewKey(keys, handler)`
for shortcuts.

```vue
<script setup lang="ts">
import type { FilePreviewRendererEmits, FilePreviewRendererProps } from '#ui-tools/file-preview'
import { useFilePreviewText } from '#ui-tools/file-preview'

const props = defineProps<FilePreviewRendererProps>()
const emit = defineEmits<FilePreviewRendererEmits>()
const content = useFilePreviewText(props)

watch(content.status, (status) => {
  if (status === 'ready') emit('ready')
  if (status === 'error') emit('error', { reason: 'source', message: content.error.value?.message })
})
</script>
```

## Upload fields

When a provider is mounted, the upload field's open action previews the field's files as a
gallery, starting at the clicked one. Files uploaded in the current session preview from the
browser's copy with no request; stored files use the `url` their `resolve()` returns. A custom
`upload.open` still wins, and without a provider the field keeps opening a new tab.

## Theming

Override these CSS variables: `--nut-fp-veil` (overlay), `--nut-fp-stage` (image and loading
background), `--nut-fp-document` (behind PDFs), `--nut-fp-checker-a` / `--nut-fp-checker-b`,
and `--nut-fp-token-key|string|number|literal` (JSON tint). Strings live in the `filePreview`
section of the package locales (`en`, `fr`).

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…