Usage
Use the v-model directive to control the value of the FileUpload.
<script setup lang="ts">
const value = ref(null)
</script>
<template>
<B24FileUpload v-model="value" class="w-96 min-h-48" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
const value = ref(null)
</script>
<template>
<B24FileUpload v-model="value" class="w-96 min-h-48" />
</template>
Multiple
Use the multiple prop to allow multiple files to be selected.
<template>
<B24FileUpload multiple class="w-96 min-h-48" />
</template>
Dropzone
Use the dropzone prop to enable/disable the droppable area. Defaults to true.
<template>
<B24FileUpload :dropzone="false" class="w-96 min-h-48" />
</template>
Interactive
Use the interactive prop to enable/disable the clickable area. Defaults to true.
<template>
<B24FileUpload :interactive="false" class="w-96 min-h-48" />
</template>
Accept
Use the accept prop to specify 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). Defaults to * (all file types).
<template>
<B24FileUpload accept="image/*" class="w-96 min-h-48" />
</template>
Label
Use the label prop to set the label of the FileUpload.
<template>
<B24FileUpload label="Drop your image here" class="w-96 min-h-48" />
</template>
Description
Use the description prop to set the description of the FileUpload.
<template>
<B24FileUpload
label="Drop your image here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
class="w-96 min-h-48"
/>
</template>
Icon
Use the icon prop to set the Icon of the FileUpload.
<script setup lang="ts">
import RocketIcon from '@bitrix24/b24icons-vue/main/RocketIcon'
</script>
<template>
<B24FileUpload
:icon="RocketIcon"
label="Drop your image here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
class="w-96 min-h-48"
/>
</template>
Color
Use the color prop to change the color of the FileUpload.
<template>
<B24FileUpload
color="air-primary-alert"
highlight
label="Drop your image here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
class="w-96 min-h-48"
/>
</template>
highlight prop is used here to show the focus state. It's used internally when a validation error occurs.Variant
Use the variant prop to change the variant of the FileUpload.
<template>
<B24FileUpload variant="button" />
</template>
Size
Use the size prop to change the size of the FileUpload.
<template>
<B24FileUpload
size="lg"
variant="area"
label="Drop your image here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
/>
</template>
Layout
Use the layout prop to change how the files are displayed in the FileUpload. Defaults to grid.
variant is area.<template>
<B24FileUpload
layout="list"
multiple
label="Drop your images here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
class="w-96"
:b24ui="{
base: 'min-h-48'
}"
/>
</template>
Position
Use the position prop to change the position of the files in the FileUpload. Defaults to outside.
variant is area and when layout is list.<template>
<B24FileUpload
position="inside"
layout="list"
multiple
label="Drop your images here"
description="SVG, PNG, JPG or GIF (max. 2MB)"
class="w-96"
:b24ui="{
base: 'min-h-48'
}"
/>
</template>
Examples
With Form validation
You can use the FileUpload within a Form and FormField components to handle validation and error handling.
With default slot
You can use the default slot to make your own FileUpload component.
useFileUpload composable is what FileUpload itself is built on: const { open, isDragging, inputRef, dropzoneRef } = useFileUpload({ accept: 'image/*', multiple: true, onUpdate: files => {} }). Call open() to raise the file dialog, bind dropzoneRef to the element that accepts drops, and read isDragging for the hover state. The markup is yours — the composable renders nothing.inputRef is only needed if you also want a real <input type="file"> in the DOM, for native form submission or validation. It expects a component, not an element: it is read as inputRef.value.$el, and FileUpload binds it to a VisuallyHidden as="input". Bound to a plain <input> it silently does nothing — $el is undefined, so dropped files are never synced onto the input. Leave it unbound if you do not need one.accept reaches the dialog verbatim, but the drop zone only gets the MIME half of it — a drag exposes a type, not a filename. An accept written purely as extensions ('.pdf,.docx') therefore leaves the drop zone accepting anything, so validate by name in onUpdate when extensions are the contract.With files-bottom slot
You can use the files-bottom slot to add a Button under the files list to remove all files for example.
With files-top slot
You can use the files-top slot to add a Button above the files list to add new files for example.
API
Props
Slots
Emits
Expose
When accessing the component via a template ref, you can use the following:
Theme
export default {
slots: {
root: 'relative flex flex-col',
base: 'w-full flex-1 bg-(--b24ui-background)/90 border-(--b24ui-border-color) border-(length:--b24ui-border-width) text-(--b24ui-color) flex flex-col gap-2 items-stretch justify-center rounded-(--ui-border-radius-md) focus-visible:outline-2 transition-[background]',
wrapper: 'flex flex-col items-center justify-center text-center',
icon: 'shrink-0',
avatar: 'shrink-0',
label: 'mt-2 text-(--b24ui-color) font-(--ui-font-weight-medium)',
description: 'mt-1 text-description',
actions: 'mt-4 flex flex-wrap gap-1.5 shrink-0',
files: '',
file: 'relative',
fileLeadingAvatar: 'shrink-0',
fileWrapper: 'flex flex-col min-w-0',
fileName: 'text-(--b24ui-color) truncate',
fileSize: 'text-description truncate',
fileTrailingButton: ''
},
variants: {
color: {
'air-primary': {
root: 'light style-filled-inverted'
},
'air-primary-success': {
root: 'light style-filled-success-inverted'
},
'air-primary-alert': {
root: 'light style-filled-alert-inverted'
},
'air-primary-copilot': {
root: 'light style-filled-copilot-inverted'
},
'air-primary-warning': {
root: 'light style-filled-warning-inverted'
},
'air-secondary': {
root: 'style-outline'
},
'air-secondary-alert': {
root: 'light style-tinted-alert'
},
'air-secondary-accent': {
root: 'light style-tinted'
},
'air-secondary-accent-1': {
root: 'style-outline-accent-1'
},
'air-secondary-accent-2': {
root: 'style-outline-accent-2'
},
'air-tertiary': {
root: 'style-outline-no-accent'
}
},
variant: {
area: {
wrapper: 'px-4 py-3',
base: 'p-4'
},
button: {}
},
size: {
xs: {
base: 'text-(length:--ui-font-size-xs)',
icon: 'size-4',
file: 'text-(length:--ui-font-size-xs) px-2 py-1 gap-1',
fileWrapper: 'flex-row gap-1'
},
sm: {
base: 'text-(length:--ui-font-size-xs)',
icon: 'size-4',
file: 'text-(length:--ui-font-size-xs) px-2.5 py-1.5 gap-1.5',
fileWrapper: 'flex-row gap-1'
},
md: {
base: 'text-(length:--ui-font-size-sm)',
icon: 'size-5',
file: 'text-(length:--ui-font-size-xs) px-2.5 py-1.5 gap-1.5'
},
lg: {
base: 'ext-(length:--ui-font-size-sm)',
icon: 'size-5',
file: 'ext-(length:--ui-font-size-sm) px-3 py-2 gap-2',
fileSize: 'ext-(length:--ui-font-size-xs)'
}
},
layout: {
list: {
root: 'gap-2 items-start',
files: 'flex flex-col w-full gap-2',
file: 'min-w-0 w-full rounded-(--ui-border-radius-md) flex items-center border border-(--b24ui-border-color)',
fileTrailingButton: 'ms-auto'
},
grid: {
fileWrapper: 'hidden',
fileLeadingAvatar: 'size-full rounded-lg',
fileTrailingButton: 'absolute -top-1.5 -end-1.5 rounded-(--ui-border-radius-pill) border edge-light:[--ui-btn-background:var(--ui-color-base-white-fixed)] edge-dark:[--ui-btn-background:var(--ui-color-base-white-fixed)] edge-dark:hover:bg-(--ui-color-base-white-fixed)/90 edge-dark:[--ui-btn-color:var(--ui-color-g-content-grey-1)] dark:[--ui-btn-background:var(--ui-color-g-content-grey-1)] dark:hover:bg-(--ui-color-g-content-grey-1)/90 dark:[--ui-btn-color:var(--ui-color-g-content-grey-4)]'
}
},
position: {
inside: '',
outside: ''
},
dropzone: {
true: 'border-dashed data-[dragging=true]:bg-(--ui-color-accent-soft-grey-1)/90'
},
interactive: {
true: ''
},
highlight: {
true: ''
},
multiple: {
true: ''
},
disabled: {
true: 'cursor-not-allowed opacity-75'
}
},
compoundVariants: [
{
class: 'focus-visible:outline-(--ui-color-design-outline-focused-stroke)'
},
{
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'
}
},
{
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'
}
},
{
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: 'air-tertiary',
variant: 'area',
size: 'md'
}
}