FileUpload

GitLab
Um elemento de input para enviar arquivos.

Uso

Use a diretiva v-model para controlar o valor do FileUpload.

<script setup lang="ts">
const value = ref(null)
</script>

<template>
  <NFileUpload v-model="value" class="w-96 min-h-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'

const value = ref(null)
</script>

<template>
  <NFileUpload v-model="value" class="w-96 min-h-48" />
</template>

Múltiplo

Use a prop multiple para permitir que vários arquivos sejam selecionados.

<template>
  <NFileUpload multiple class="w-96 min-h-48" />
</template>

Zona de soltar

Use a prop dropzone para habilitar/desabilitar a área de soltar. O padrão é true.

<template>
  <NFileUpload :dropzone="false" class="w-96 min-h-48" />
</template>

Interativo

Use a prop interactive para habilitar/desabilitar a área clicável. O padrão é true.

Isso pode ser útil ao adicionar um componente Button no slot #actions.
<template>
  <NFileUpload :interactive="false" class="w-96 min-h-48" />
</template>

Aceitar

Use a prop accept para especificar os tipos de arquivo permitidos no input. Forneça uma lista separada por vírgulas de tipos MIME ou extensões de arquivo (ex.: image/png,application/pdf,.jpg). O padrão é * (todos os tipos de arquivo).

<template>
  <NFileUpload accept="image/*" class="w-96 min-h-48" />
</template>

Label

Use a prop label para definir o rótulo do FileUpload.

Drop your image here
<template>
  <NFileUpload label="Drop your image here" class="w-96 min-h-48" />
</template>

Descrição

Use a prop description para definir a descrição do FileUpload.

Drop your image here
SVG, PNG, JPG or GIF (max. 2MB)
<template>
  <NFileUpload
    label="Drop your image here"
    description="SVG, PNG, JPG or GIF (max. 2MB)"
    class="w-96 min-h-48"
  />
</template>

Ícone

Use a prop icon para definir o ícone do FileUpload. O padrão é i-lucide-upload.

Drop your image here
SVG, PNG, JPG or GIF (max. 2MB)
<template>
  <NFileUpload
    icon="i-lucide-image"
    label="Drop your image here"
    description="SVG, PNG, JPG or GIF (max. 2MB)"
    class="w-96 min-h-48"
  />
</template>
Você pode personalizar esse ícone globalmente no seu app.config.ts na chave ui.icons.upload.
Você pode personalizar esse ícone globalmente no seu vite.config.ts na chave ui.icons.upload.

Cor

Use a prop color para alterar a cor do FileUpload.

Drop your image here
SVG, PNG, JPG or GIF (max. 2MB)
<template>
  <NFileUpload
    color="neutral"
    highlight
    label="Drop your image here"
    description="SVG, PNG, JPG or GIF (max. 2MB)"
    class="w-96 min-h-48"
  />
</template>
A prop highlight é usada aqui para mostrar o estado de foco. Ela é usada internamente quando ocorre um erro de validação.

Variante

Use a prop variant para alterar a variante do FileUpload.

<template>
  <NFileUpload variant="button" />
</template>

Tamanho

Use a prop size para alterar o tamanho do FileUpload.

Drop your image here
SVG, PNG, JPG or GIF (max. 2MB)
<template>
  <NFileUpload
    size="xl"
    variant="area"
    label="Drop your image here"
    description="SVG, PNG, JPG or GIF (max. 2MB)"
  />
</template>

Layout

Use a prop layout para alterar como os arquivos são exibidos no FileUpload. O padrão é grid.

Esta prop só funciona quando variant é area.
Drop your images here
SVG, PNG, JPG or GIF (max. 2MB)
<template>
  <NFileUpload
    layout="list"
    multiple
    label="Drop your images here"
    description="SVG, PNG, JPG or GIF (max. 2MB)"
    class="w-96"
    :ui="{
      base: 'min-h-48'
    }"
  />
</template>

Posição

Use a prop position para alterar a posição dos arquivos no FileUpload. O padrão é outside.

Esta prop só funciona quando variant é area e quando layout é list.
Drop your images here
SVG, PNG, JPG or GIF (max. 2MB)
<template>
  <NFileUpload
    position="inside"
    layout="list"
    multiple
    label="Drop your images here"
    description="SVG, PNG, JPG or GIF (max. 2MB)"
    class="w-96"
    :ui="{
      base: 'min-h-48'
    }"
  />
</template>

Exemplos

Com validação de Form

Você pode usar o FileUpload dentro dos componentes Form e FormField para lidar com validação e tratamento de erros.

JPG, GIF ou PNG. 2MB no máximo.

Com slot padrão

Você pode usar o slot padrão para criar o seu próprio componente FileUpload.

JPG, GIF ou PNG. 1MB no máximo.

Com slot files-bottom

Você pode usar o slot files-bottom para adicionar um Button abaixo da lista de arquivos para remover todos os arquivos, por exemplo.

Solte suas imagens aqui
SVG, PNG, JPG ou GIF (máx. 2MB)
A prop interactive é definida como false neste exemplo para evitar a área clicável padrão.

Com slot files-top

Você pode usar o slot files-top para adicionar um Button acima da lista de arquivos para adicionar novos arquivos, por exemplo.

Solte suas imagens aqui
SVG, PNG, JPG ou GIF (máx. 2MB)

API

Props

Prop Default Type
as'div'any

The element or component this component should render as.

id string
name string
iconappConfig.ui.icons.uploadany

The icon to display. Set to false to hide the icon.

label string
description string
color'primary' "primary" | "secondary" | "accent" | "success" | "info" | "warning" | "error" | "neutral"
variant'area' "button" | "area"

The button variant is only available when multiple is false.

size'md' "xs" | "sm" | "md" | "lg" | "xl"
layout'grid' "list" | "grid"

The layout of how files are displayed. Only works when variant is area.

position'outside' "inside" | "outside"

The position of the files. Only works when variant is area and when layout is list.

highlightboolean

Highlight the ring color like a focus state.

accept'*' string

Specifies the allowed file types for the input. Provide a comma-separated list of MIME types or file extensions (e.g., "image/png,application/pdf,.jpg").

multiplefalse M
resetfalseboolean

Reset the file input when the dialog is opened.

dropzonetrueboolean

Create a zone that allows the user to drop files onto it.

interactivetrueboolean

Make the dropzone interactive when the user is clicking on it.

requiredboolean
disabledboolean
fileIconappConfig.ui.icons.fileany

The icon to display for the file.

fileImagetrueboolean

Preview the file (currently only <img> is rendered) When set false, only fileIcon is displayed

fileDeletetrueboolean | Omit<ButtonProps, LinkPropsKeys>

Configure the delete button for the file. When layout is grid, the default is { color: 'neutral', variant: 'solid', size: 'xs' } When layout is list, the default is { color: 'neutral', variant: 'link' }

fileDeleteIconappConfig.ui.icons.closeany

The icon displayed to delete a file.

previewtrueboolean

Show the file preview/list after upload.

modelValue null | M extends true ? File[] : File
ui { root?: SlotClass; base?: SlotClass; wrapper?: SlotClass; icon?: SlotClass; avatar?: SlotClass; label?: SlotClass; description?: SlotClass; actions?: SlotClass; files?: SlotClass; file?: SlotClass; fileLeadingAvatar?: SlotClass; fileWrapper?: SlotClass; fileName?: SlotClass; fileSize?: SlotClass; fileTrailingButton?: SlotClass; }
Este componente também suporta todos os atributos HTML nativos de <input>.

Slots

Slot Type
default{ open: (localOptions?: Partial<UseFileDialogOptions> | undefined) => void; removeFile: (index?: number | undefined) => void; ui: object; }
leading{ ui: object; }
label{}
description{}
actions{ files: FileUploadFiles<M> | undefined; open: (localOptions?: Partial<UseFileDialogOptions> | undefined) => void; removeFile: (index?: number | undefined) => void; }
files{ files: FileUploadFiles<M>; removeFile: (index?: number | undefined) => void; }
files-top{ files: FileUploadFiles<M>; open: (localOptions?: Partial<UseFileDialogOptions> | undefined) => void; removeFile: (index?: number | undefined) => void; }
files-bottom{ files: FileUploadFiles<M>; open: (localOptions?: Partial<UseFileDialogOptions> | undefined) => void; removeFile: (index?: number | undefined) => void; }
file{ file: File; index: number; removeFile: (index?: number | undefined) => void; }
file-leading{ file: File; index: number; ui: object; }
file-name{ file: File; index: number; }
file-size{ file: File; index: number; }
file-trailing{ file: File; index: number; ui: object; }

Emits

Event Type
change[event: Event]
update:modelValue[value: (M extends true ? File[] : File) | null | undefined]

Expose

Ao acessar o componente por meio de um template ref, você pode usar o seguinte:

NameType
inputRefRef<HTMLInputElement | null>
dropzoneRefRef<HTMLDivElement | null>

Tema

app.config.ts
export default defineAppConfig({
  ui: {
    fileUpload: {
      slots: {
        root: 'relative flex flex-col',
        base: [
          'w-full flex-1 bg-default border border-default flex flex-col gap-2 items-stretch justify-center rounded-lg focus-visible:outline-2',
          'transition-[background]'
        ],
        wrapper: 'flex flex-col items-center justify-center text-center',
        icon: 'shrink-0',
        avatar: 'shrink-0',
        label: 'font-medium text-default mt-2',
        description: 'text-muted mt-1',
        actions: 'flex flex-wrap gap-1.5 shrink-0 mt-4',
        files: '',
        file: 'relative',
        fileLeadingAvatar: 'shrink-0',
        fileWrapper: 'flex flex-col min-w-0',
        fileName: 'text-default truncate',
        fileSize: 'text-muted truncate',
        fileTrailingButton: ''
      },
      variants: {
        color: {
          primary: '',
          secondary: '',
          accent: '',
          success: '',
          info: '',
          warning: '',
          error: '',
          neutral: ''
        },
        variant: {
          area: {
            wrapper: 'px-4 py-3',
            base: 'p-4'
          },
          button: {}
        },
        size: {
          xs: {
            base: 'text-xs',
            icon: 'size-4',
            file: 'text-xs px-2 py-1 gap-1',
            fileWrapper: 'flex-row gap-1'
          },
          sm: {
            base: 'text-xs',
            icon: 'size-4',
            file: 'text-xs px-2.5 py-1.5 gap-1.5',
            fileWrapper: 'flex-row gap-1'
          },
          md: {
            base: 'text-sm',
            icon: 'size-5',
            file: 'text-xs px-2.5 py-1.5 gap-1.5'
          },
          lg: {
            base: 'text-sm',
            icon: 'size-5',
            file: 'text-sm px-3 py-2 gap-2',
            fileSize: 'text-xs'
          },
          xl: {
            base: 'text-base',
            icon: 'size-6',
            file: 'text-sm px-3 py-2 gap-2'
          }
        },
        layout: {
          list: {
            root: 'gap-2 items-start',
            files: 'flex flex-col w-full gap-2',
            file: 'min-w-0 flex items-center border border-default rounded-md w-full',
            fileTrailingButton: 'ms-auto'
          },
          grid: {
            fileWrapper: 'hidden',
            fileLeadingAvatar: 'size-full rounded-lg',
            fileTrailingButton: 'absolute -top-1.5 -end-1.5 p-0 rounded-full border-2 border-bg'
          }
        },
        position: {
          inside: '',
          outside: ''
        },
        dropzone: {
          true: 'border-dashed data-[dragging=true]:bg-elevated/25'
        },
        interactive: {
          true: ''
        },
        highlight: {
          true: ''
        },
        multiple: {
          true: ''
        },
        disabled: {
          true: 'cursor-not-allowed opacity-75'
        }
      },
      compoundVariants: [
        {
          color: 'primary',
          class: 'focus-visible:outline-primary'
        },
        {
          color: 'primary',
          highlight: true,
          class: 'border-primary'
        },
        {
          color: 'neutral',
          class: 'focus-visible:outline-inverted'
        },
        {
          color: 'neutral',
          highlight: true,
          class: 'border-inverted'
        },
        {
          size: 'xs',
          layout: 'list',
          class: {
            fileTrailingButton: '-me-1'
          }
        },
        {
          size: 'sm',
          layout: 'list',
          class: {
            fileTrailingButton: '-me-1.5'
          }
        },
        {
          size: 'md',
          layout: 'list',
          class: {
            fileTrailingButton: '-me-1.5'
          }
        },
        {
          size: 'lg',
          layout: 'list',
          class: {
            fileTrailingButton: '-me-2'
          }
        },
        {
          size: 'xl',
          layout: 'list',
          class: {
            fileTrailingButton: '-me-2'
          }
        },
        {
          variant: 'button',
          size: 'xs',
          class: {
            base: 'p-1'
          }
        },
        {
          variant: 'button',
          size: 'sm',
          class: {
            base: 'p-1.5'
          }
        },
        {
          variant: 'button',
          size: 'md',
          class: {
            base: 'p-1.5'
          }
        },
        {
          variant: 'button',
          size: 'lg',
          class: {
            base: 'p-2'
          }
        },
        {
          variant: 'button',
          size: 'xl',
          class: {
            base: 'p-2'
          }
        },
        {
          layout: 'grid',
          multiple: true,
          class: {
            files: 'grid grid-cols-2 md:grid-cols-3 gap-4 w-full',
            file: 'p-0 aspect-square'
          }
        },
        {
          layout: 'grid',
          multiple: false,
          class: {
            file: 'absolute inset-0 p-0'
          }
        },
        {
          interactive: true,
          disabled: false,
          class: 'hover:bg-elevated/25'
        }
      ],
      defaultVariants: {
        color: 'primary',
        variant: 'area',
        size: 'md'
      }
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nitro/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        fileUpload: {
          slots: {
            root: 'relative flex flex-col',
            base: [
              'w-full flex-1 bg-default border border-default flex flex-col gap-2 items-stretch justify-center rounded-lg focus-visible:outline-2',
              'transition-[background]'
            ],
            wrapper: 'flex flex-col items-center justify-center text-center',
            icon: 'shrink-0',
            avatar: 'shrink-0',
            label: 'font-medium text-default mt-2',
            description: 'text-muted mt-1',
            actions: 'flex flex-wrap gap-1.5 shrink-0 mt-4',
            files: '',
            file: 'relative',
            fileLeadingAvatar: 'shrink-0',
            fileWrapper: 'flex flex-col min-w-0',
            fileName: 'text-default truncate',
            fileSize: 'text-muted truncate',
            fileTrailingButton: ''
          },
          variants: {
            color: {
              primary: '',
              secondary: '',
              accent: '',
              success: '',
              info: '',
              warning: '',
              error: '',
              neutral: ''
            },
            variant: {
              area: {
                wrapper: 'px-4 py-3',
                base: 'p-4'
              },
              button: {}
            },
            size: {
              xs: {
                base: 'text-xs',
                icon: 'size-4',
                file: 'text-xs px-2 py-1 gap-1',
                fileWrapper: 'flex-row gap-1'
              },
              sm: {
                base: 'text-xs',
                icon: 'size-4',
                file: 'text-xs px-2.5 py-1.5 gap-1.5',
                fileWrapper: 'flex-row gap-1'
              },
              md: {
                base: 'text-sm',
                icon: 'size-5',
                file: 'text-xs px-2.5 py-1.5 gap-1.5'
              },
              lg: {
                base: 'text-sm',
                icon: 'size-5',
                file: 'text-sm px-3 py-2 gap-2',
                fileSize: 'text-xs'
              },
              xl: {
                base: 'text-base',
                icon: 'size-6',
                file: 'text-sm px-3 py-2 gap-2'
              }
            },
            layout: {
              list: {
                root: 'gap-2 items-start',
                files: 'flex flex-col w-full gap-2',
                file: 'min-w-0 flex items-center border border-default rounded-md w-full',
                fileTrailingButton: 'ms-auto'
              },
              grid: {
                fileWrapper: 'hidden',
                fileLeadingAvatar: 'size-full rounded-lg',
                fileTrailingButton: 'absolute -top-1.5 -end-1.5 p-0 rounded-full border-2 border-bg'
              }
            },
            position: {
              inside: '',
              outside: ''
            },
            dropzone: {
              true: 'border-dashed data-[dragging=true]:bg-elevated/25'
            },
            interactive: {
              true: ''
            },
            highlight: {
              true: ''
            },
            multiple: {
              true: ''
            },
            disabled: {
              true: 'cursor-not-allowed opacity-75'
            }
          },
          compoundVariants: [
            {
              color: 'primary',
              class: 'focus-visible:outline-primary'
            },
            {
              color: 'primary',
              highlight: true,
              class: 'border-primary'
            },
            {
              color: 'neutral',
              class: 'focus-visible:outline-inverted'
            },
            {
              color: 'neutral',
              highlight: true,
              class: 'border-inverted'
            },
            {
              size: 'xs',
              layout: 'list',
              class: {
                fileTrailingButton: '-me-1'
              }
            },
            {
              size: 'sm',
              layout: 'list',
              class: {
                fileTrailingButton: '-me-1.5'
              }
            },
            {
              size: 'md',
              layout: 'list',
              class: {
                fileTrailingButton: '-me-1.5'
              }
            },
            {
              size: 'lg',
              layout: 'list',
              class: {
                fileTrailingButton: '-me-2'
              }
            },
            {
              size: 'xl',
              layout: 'list',
              class: {
                fileTrailingButton: '-me-2'
              }
            },
            {
              variant: 'button',
              size: 'xs',
              class: {
                base: 'p-1'
              }
            },
            {
              variant: 'button',
              size: 'sm',
              class: {
                base: 'p-1.5'
              }
            },
            {
              variant: 'button',
              size: 'md',
              class: {
                base: 'p-1.5'
              }
            },
            {
              variant: 'button',
              size: 'lg',
              class: {
                base: 'p-2'
              }
            },
            {
              variant: 'button',
              size: 'xl',
              class: {
                base: 'p-2'
              }
            },
            {
              layout: 'grid',
              multiple: true,
              class: {
                files: 'grid grid-cols-2 md:grid-cols-3 gap-4 w-full',
                file: 'p-0 aspect-square'
              }
            },
            {
              layout: 'grid',
              multiple: false,
              class: {
                file: 'absolute inset-0 p-0'
              }
            },
            {
              interactive: true,
              disabled: false,
              class: 'hover:bg-elevated/25'
            }
          ],
          defaultVariants: {
            color: 'primary',
            variant: 'area',
            size: 'md'
          }
        }
      }
    })
  ]
})
Some colors in compoundVariants are omitted for readability. Check out the source code on GitLab.

Changelog

No recent changes