Modal

DialoogGitHub
Een dialoogvenster dat kan worden gebruikt om een bericht weer te geven of om gebruikersinvoer te vragen.

Gebruik

Gebruik een Button of een ander onderdeel in de standaardsleuf van de modus.

Gebruik vervolgens de #content-sleuf om de inhoud toe te voegen die wordt weergegeven wanneer de modus is geopend.

<template>
  <UModal>
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <Placeholder class="h-48 m-4" />
    </template>
  </UModal>
</template>

U kunt ook de #header, #body en #footer slots gebruiken om de inhoud van de Modal aan te passen.

Titel

Gebruik de title prop om de titel van de header van de Modal in te stellen.

<template>
  <UModal title="Modal with title">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-48" />
    </template>
  </UModal>
</template>

Beschrijving

Gebruik de description prop om de beschrijving van de header van de Modal in te stellen.

<template>
  <UModal
    title="Modal with description"
    description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
  >
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-48" />
    </template>
  </UModal>
</template>

Sluiten

Gebruik de close prop om de sluitknop (met false-waarde) die wordt weergegeven in de kop van de modus aan te passen of te verbergen.

U kunt elke eigenschap van de Button component doorgeven om deze aan te passen.

<template>
  <UModal
    title="Modal with close button"
    :close="{
      color: 'primary',
      variant: 'outline',
      class: 'rounded-full'
    }"
  >
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-48" />
    </template>
  </UModal>
</template>
De knop Sluiten wordt niet weergegeven als de #content-sleuf wordt gebruikt omdat deze deel uitmaakt van de header.

Sluit pictogram

Gebruik de close-icon prop om de knop Sluiten aan te passen Icon. Standaard i-lucide-x.

<template>
  <UModal title="Modal with close button" close-icon="i-lucide-arrow-right">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-48" />
    </template>
  </UModal>
</template>
U kunt dit pictogram globaal aanpassen in uw app.config.ts onder ui.icons.close-sleutel.
U kunt dit pictogram globaal aanpassen in uw vite.config.ts onder ui.icons.close-sleutel.

Overgang

Gebruik de transition-prop om te bepalen of de modus geanimeerd is of niet. Standaard true.

<template>
  <UModal :transition="false" title="Modal without transition">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-48" />
    </template>
  </UModal>
</template>

Overlay

Gebruik de overlay prop om te bepalen of de Modal een overlay heeft of niet. Standaard true.

<template>
  <UModal :overlay="false" title="Modal without overlay">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-48" />
    </template>
  </UModal>
</template>

Modaal

Gebruik de modal prop om te bepalen of de Modal de interactie met externe inhoud blokkeert. Standaard true.

Wanneer modal is ingesteld op false, wordt de overlay automatisch uitgeschakeld en wordt externe inhoud interactief.
<template>
  <UModal :modal="false" title="Modal interactive">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-48" />
    </template>
  </UModal>
</template>

Ontvankelijk

Gebruik de dismissible-prop om te bepalen of de modus kan worden afgewezen wanneer u erbuiten klikt of op escape drukt. Standaard true.

Een close:prevent-gebeurtenis wordt uitgezonden wanneer de gebruiker deze probeert te sluiten.
Je kunt modal: false combineren met dismissible: false om de achtergrond van de Modal interactief te maken zonder deze te sluiten.
<template>
  <UModal :dismissible="false" title="Modal non-dismissible">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-48" />
    </template>
  </UModal>
</template>

Doorschuifbaar: badge{label="4.2+" class="align-text-top"}

Gebruik de scrollable prop om de inhoud van de Modal binnen de overlay te laten scrollen.

Omdat de overlay nodig is om te scrollen, is modal: false niet compatibel en verwijdert overlay: false alleen de achtergrond.
<template>
  <UModal scrollable title="Modal scrollable">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </UModal>
</template>
Er is een known issue waarbij klikken op de schuifbalk onbedoeld het dialoogvenster op sommige besturingssystemen kan sluiten.

Volledig scherm

Gebruik de fullscreen prop om de Modal volledig scherm te maken.

<template>
  <UModal fullscreen title="Modal fullscreen">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </UModal>
</template>

Ontkoppelen: badge{label="4.10+" class="align-text-top"}

Gebruik de unmount-on-hide prop om te voorkomen dat de inhoud van de Modal wordt verwijderd wanneer deze wordt gesloten. Standaard true.

<template>
  <UModal :unmount-on-hide="false" title="Modal">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-48" />
    </template>
  </UModal>
</template>
U kunt de DOM inspecteren om te zien dat de inhoud van de modus wordt weergegeven, zelfs als deze is gesloten.
Wanneer de portal prop is ingesteld op false, wordt de inhoud ook weergegeven op de server. Dit is handig om een open Modal tijdens SSR weer te geven zonder een flash bij het laden van de pagina, of om de inhoud ervan bloot te leggen voor SEO.

Voorbeelden

Control open staat

U kunt de open status regelen met behulp van de default-open prop of de v-model:open-richtlijn.

<script setup lang="ts">
const open = ref(false)

defineShortcuts({
  o: () => open.value = !open.value
})
</script>

<template>
  <UModal v-model:open="open">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <Placeholder class="h-48 m-4" />
    </template>
  </UModal>
</template>
<script setup lang="ts">
import { ref } from 'vue'

const open = ref(false)

defineShortcuts({
  o: () => open.value = !open.value
})
</script>

<template>
  <UModal v-model:open="open">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <Placeholder class="h-48 m-4" />
    </template>
  </UModal>
</template>
In dit voorbeeld, gebruikmakend van defineShortcuts, kunt u de modus wijzigen door op: kbd{value="O"} te drukken.
Hiermee kunt u de trigger buiten de modus verplaatsen of volledig verwijderen.

Programmatisch gebruik

U kunt de useOverlay composable gebruiken om een Modal programmatisch te openen.

Zorg ervoor dat u uw app omhult met de App -component die de OverlayProvider -component gebruikt.

Maak eerst een modale component die programmatisch wordt geopend:

ModalExample.vue
<script setup lang="ts">
defineProps<{
  count: number
}>()

const emit = defineEmits<{ close: [boolean] }>()
</script>

<template>
  <UModal
    :close="{ onClick: () => emit('close', false) }"
    :title="`This modal was opened programmatically ${count} times`"
  >
    <template #footer>
      <div class="flex gap-2">
        <UButton color="neutral" label="Dismiss" @click="emit('close', false)" />
        <UButton label="Success" @click="emit('close', true)" />
      </div>
    </template>
  </UModal>
</template>
We zenden een close-gebeurtenis uit wanneer de modal hier wordt gesloten of afgewezen. U kunt alle gegevens uitzenden via de close-gebeurtenis en die gegevens worden de opgeloste waarde van open(). De gebeurtenis moet worden uitgezonden om de belofte op te lossen.

Gebruik het vervolgens in uw app:

<script setup lang="ts">
import { LazyModalExample } from '#components'

const count = ref(0)

const toast = useToast()
const overlay = useOverlay()

const modal = overlay.create(LazyModalExample)

async function open() {
  const instance = modal.open({
    count: count.value
  })

  const shouldIncrement = await instance.result

  if (shouldIncrement) {
    count.value++

    toast.add({
      title: `Success: ${shouldIncrement}`,
      color: 'success',
      id: 'modal-success'
    })

    // Update the count
    modal.patch({
      count: count.value
    })
    return
  }

  toast.add({
    title: `Dismissed: ${shouldIncrement}`,
    color: 'error',
    id: 'modal-dismiss'
  })
}
</script>

<template>
  <UButton label="Open" color="neutral" variant="subtle" @click="open" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { LazyModalExample } from '#components'

const count = ref(0)

const toast = useToast()
const overlay = useOverlay()

const modal = overlay.create(LazyModalExample)

async function open() {
  const instance = modal.open({
    count: count.value
  })

  const shouldIncrement = await instance.result

  if (shouldIncrement) {
    count.value++

    toast.add({
      title: `Success: ${shouldIncrement}`,
      color: 'success',
      id: 'modal-success'
    })

    // Update the count
    modal.patch({
      count: count.value
    })
    return
  }

  toast.add({
    title: `Dismissed: ${shouldIncrement}`,
    color: 'error',
    id: 'modal-dismiss'
  })
}
</script>

<template>
  <UButton label="Open" color="neutral" variant="subtle" @click="open" />
</template>
U kunt de modal binnen de modale component sluiten door emit('close') uit te zenden.

Genestelde modals

Je kunt modalen in elkaar nestelen.

<script setup lang="ts">
const first = ref(false)
const second = ref(false)
</script>

<template>
  <UModal v-model:open="first" title="First modal" :ui="{ footer: 'justify-end' }">
    <UButton color="neutral" variant="subtle" label="Open" />

    <template #footer>
      <UButton label="Close" color="neutral" variant="outline" @click="first = false" />

      <UModal v-model:open="second" title="Second modal" :ui="{ footer: 'justify-end' }">
        <UButton label="Open second" color="neutral" />

        <template #footer>
          <UButton label="Close" color="neutral" variant="outline" @click="second = false" />
        </template>
      </UModal>
    </template>
  </UModal>
</template>
<script setup lang="ts">
import { ref } from 'vue'

const first = ref(false)
const second = ref(false)
</script>

<template>
  <UModal v-model:open="first" title="First modal" :ui="{ footer: 'justify-end' }">
    <UButton color="neutral" variant="subtle" label="Open" />

    <template #footer>
      <UButton label="Close" color="neutral" variant="outline" @click="first = false" />

      <UModal v-model:open="second" title="Second modal" :ui="{ footer: 'justify-end' }">
        <UButton label="Open second" color="neutral" />

        <template #footer>
          <UButton label="Close" color="neutral" variant="outline" @click="second = false" />
        </template>
      </UModal>
    </template>
  </UModal>
</template>

Met voetgangerssleuf

Gebruik de #footer-sleuf om inhoud toe te voegen na de body van de Modal.

<script setup lang="ts">
const open = ref(false)
</script>

<template>
  <UModal v-model:open="open" title="Modal with footer" description="This is useful when you want a form in a Modal." :ui="{ footer: 'justify-end' }">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-48" />
    </template>

    <template #footer="{ close }">
      <UButton label="Cancel" color="neutral" variant="outline" @click="close" />
      <UButton label="Submit" color="neutral" />
    </template>
  </UModal>
</template>
<script setup lang="ts">
import { ref } from 'vue'

const open = ref(false)
</script>

<template>
  <UModal v-model:open="open" title="Modal with footer" description="This is useful when you want a form in a Modal." :ui="{ footer: 'justify-end' }">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-48" />
    </template>

    <template #footer="{ close }">
      <UButton label="Cancel" color="neutral" variant="outline" @click="close" />
      <UButton label="Submit" color="neutral" />
    </template>
  </UModal>
</template>

Met opdrachtpalet

U kunt een CommandPalette -component gebruiken in de inhoud van de modus.

<script setup lang="ts">
const searchTerm = ref('')

const { data: users, status, execute } = await useLazyFetch('https://jsonplaceholder.typicode.com/users', {
  key: 'modal-command-palette-users',
  params: { q: searchTerm },
  transform: (data: { id: number, name: string, email: string }[]) => {
    return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}`, loading: 'lazy' as const } })) || []
  },
  immediate: false
})

const groups = computed(() => [{
  id: 'users',
  label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
  items: users.value || [],
  ignoreFilter: true
}])

function onOpen() {
  if (!users.value?.length) {
    execute()
  }
}
</script>

<template>
  <UModal @update:open="onOpen">
    <UButton
      label="Search users..."
      color="neutral"
      variant="subtle"
      icon="i-lucide-search"
    />

    <template #content>
      <UCommandPalette
        v-model:search-term="searchTerm"
        :loading="status === 'pending'"
        :groups="groups"
        placeholder="Search users..."
        class="h-80"
      />
    </template>
  </UModal>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'

const searchTerm = ref('')

const { data: users, status, execute } = await useLazyFetch('https://jsonplaceholder.typicode.com/users', {
  key: 'modal-command-palette-users',
  params: { q: searchTerm },
  transform: (data: { id: number, name: string, email: string }[]) => {
    return data?.map(user => ({ id: user.id, label: user.name, suffix: user.email, avatar: { src: `https://i.pravatar.cc/120?img=${user.id}`, loading: 'lazy' as const } })) || []
  },
  immediate: false
})

const groups = computed(() => [{
  id: 'users',
  label: searchTerm.value ? `Users matching “${searchTerm.value}”...` : 'Users',
  items: users.value || [],
  ignoreFilter: true
}])

function onOpen() {
  if (!users.value?.length) {
    execute()
  }
}
</script>

<template>
  <UModal @update:open="onOpen">
    <UButton
      label="Search users..."
      color="neutral"
      variant="subtle"
      icon="i-lucide-search"
    />

    <template #content>
      <UCommandPalette
        v-model:search-term="searchTerm"
        :loading="status === 'pending'"
        :groups="groups"
        placeholder="Search users..."
        class="h-80"
      />
    </template>
  </UModal>
</template>
Dit voorbeeld gebruikt useLazyFetch met immediate: false om alleen gegevens op te halen wanneer de modus wordt geopend.

API

Props

Prop Default Type
title
string
description
string
content
DialogContentProps & Partial<EmitsToProps<DialogContentImplEmits>>

The content of the modal.

overlay
true
boolean

Render an overlay behind the modal.

scrollable
false
boolean

When true, enables scrollable overlay mode where content scrolls within the overlay.

transition
true
boolean

Animate the modal when opening or closing.

fullscreen
false
boolean

When true, the modal will take up the full screen.

portal
true
string | false | true | HTMLElement

Render the modal in a portal.

close
true
boolean | Omit<ButtonProps, LinkPropsKeys>

Display a close button to dismiss the modal. { size: 'md', color: 'neutral', variant: 'ghost' }

closeIcon
appConfig.ui.icons.close
any

The icon displayed in the close button.

dismissible
true
boolean

When false, the modal will not close when clicking outside or pressing escape.

open
boolean

The controlled open state of the dialog. Can be binded as v-model:open.

defaultOpen
boolean

The open state of the dialog when it is initially rendered. Use when you do not need to control its open state.

modal
true
boolean

The modality of the dialog When set to true,
interaction with outside elements will be disabled and only dialog content will be visible to screen readers.

unmountOnHide
true
boolean

When set to false, the dialog content will not be unmounted when closed, but instead hidden with CSS.
Useful for SEO or when you want to improve performance by not remounting the component on every open.

ui
{ overlay?: SlotClass; content?: SlotClass; header?: SlotClass; wrapper?: SlotClass; body?: SlotClass; footer?: SlotClass; title?: SlotClass; description?: SlotClass; close?: SlotClass; }

Slots

Slot Type
default
{ open: boolean; }
content
{ close: () => void; }
header
{ close: () => void; }
title
{}
description
{}
actions
{}
close
{ ui: object; }
body
{ close: () => void; }
footer
{ close: () => void; }

Uitzendt

Event Type
leave
[]
after:leave
[]
enter
[]
after:enter
[]
close:prevent
[]
update:open
[value: boolean]

Thema

app.config.ts
export default defineAppConfig({
  ui: {
    modal: {
      slots: {
        overlay: 'fixed inset-0',
        content: 'bg-default divide-y divide-default flex flex-col focus:outline-none',
        header: 'flex items-center gap-1.5 p-4 sm:px-6 min-h-(--ui-header-height)',
        wrapper: '',
        body: 'flex-1 p-4 sm:p-6',
        footer: 'flex items-center gap-1.5 p-4 sm:px-6',
        title: 'text-highlighted font-semibold',
        description: 'mt-1 text-muted text-sm',
        close: 'absolute top-4 end-4'
      },
      variants: {
        transition: {
          true: {
            overlay: 'data-[state=open]:animate-[fade-in_200ms_var(--ease-out)] data-[state=closed]:animate-[fade-out_200ms_var(--ease-out)]',
            content: 'data-[state=open]:animate-[scale-in_200ms_var(--ease-out)] data-[state=closed]:animate-[scale-out_200ms_var(--ease-out)]'
          }
        },
        fullscreen: {
          true: {
            content: 'inset-0'
          },
          false: {
            content: 'w-[calc(100vw-2rem)] max-w-lg rounded-lg shadow-lg ring ring-default'
          }
        },
        overlay: {
          true: {
            overlay: 'bg-elevated/75'
          }
        },
        scrollable: {
          true: {
            overlay: 'overflow-y-auto',
            content: 'relative'
          },
          false: {
            content: 'fixed',
            body: 'overflow-y-auto'
          }
        }
      },
      compoundVariants: [
        {
          scrollable: true,
          fullscreen: false,
          class: {
            overlay: 'grid place-items-center p-4 sm:py-8'
          }
        },
        {
          scrollable: false,
          fullscreen: false,
          class: {
            content: 'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 max-h-[calc(100dvh-2rem)] sm:max-h-[calc(100dvh-4rem)] overflow-hidden'
          }
        }
      ]
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        modal: {
          slots: {
            overlay: 'fixed inset-0',
            content: 'bg-default divide-y divide-default flex flex-col focus:outline-none',
            header: 'flex items-center gap-1.5 p-4 sm:px-6 min-h-(--ui-header-height)',
            wrapper: '',
            body: 'flex-1 p-4 sm:p-6',
            footer: 'flex items-center gap-1.5 p-4 sm:px-6',
            title: 'text-highlighted font-semibold',
            description: 'mt-1 text-muted text-sm',
            close: 'absolute top-4 end-4'
          },
          variants: {
            transition: {
              true: {
                overlay: 'data-[state=open]:animate-[fade-in_200ms_var(--ease-out)] data-[state=closed]:animate-[fade-out_200ms_var(--ease-out)]',
                content: 'data-[state=open]:animate-[scale-in_200ms_var(--ease-out)] data-[state=closed]:animate-[scale-out_200ms_var(--ease-out)]'
              }
            },
            fullscreen: {
              true: {
                content: 'inset-0'
              },
              false: {
                content: 'w-[calc(100vw-2rem)] max-w-lg rounded-lg shadow-lg ring ring-default'
              }
            },
            overlay: {
              true: {
                overlay: 'bg-elevated/75'
              }
            },
            scrollable: {
              true: {
                overlay: 'overflow-y-auto',
                content: 'relative'
              },
              false: {
                content: 'fixed',
                body: 'overflow-y-auto'
              }
            }
          },
          compoundVariants: [
            {
              scrollable: true,
              fullscreen: false,
              class: {
                overlay: 'grid place-items-center p-4 sm:py-8'
              }
            },
            {
              scrollable: false,
              fullscreen: false,
              class: {
                content: 'top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 max-h-[calc(100dvh-2rem)] sm:max-h-[calc(100dvh-4rem)] overflow-hidden'
              }
            }
          ]
        }
      }
    })
  ]
})

Wijzigingsgelog

No recent changes