Modal

DialogGitLab
Uma janela de diálogo que pode ser usada para exibir uma mensagem ou solicitar a entrada do usuário.

Uso

Use a Button or any other component in the default slot of the Modal.

Depois, use o slot #content para adicionar o conteúdo exibido quando o Modal está aberto.

<template>
  <NModal>
    <NButton label="Open" color="neutral" variant="subtle" />

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

Você também pode usar os slots #header, #body e #footer para personalizar o conteúdo do Modal.

Título

Use a prop title para definir o título do cabeçalho do Modal.

<template>
  <NModal title="Modal with title">
    <NButton label="Open" color="neutral" variant="subtle" />

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

Descrição

Use a prop description para definir a descrição do cabeçalho do Modal.

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

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

Fechar

Use a prop close para personalizar ou ocultar o botão de fechar (com o valor false) exibido no cabeçalho do Modal.

Você pode passar qualquer propriedade do componente Button para personalizá-lo.

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

    <template #body>
      <Placeholder class="h-48" />
    </template>
  </NModal>
</template>
O botão de fechar não é exibido se o slot #content for usado, pois ele faz parte do cabeçalho.

Ícone de fechar

Use a prop close-icon para personalizar o Icon do botão de fechar. O padrão é i-lucide-x.

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

    <template #body>
      <Placeholder class="h-48" />
    </template>
  </NModal>
</template>
Você pode personalizar esse ícone globalmente no seu app.config.ts na chave ui.icons.close.
Você pode personalizar esse ícone globalmente no seu vite.config.ts na chave ui.icons.close.

Transição

Use a prop transition para controlar se o Modal é animado ou não. O padrão é true.

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

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

Overlay

Use a prop overlay para controlar se o Modal tem um overlay ou não. O padrão é true.

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

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

Use a prop modal para controlar se o Modal bloqueia a interação com o conteúdo externo. O padrão é true.

Quando modal é definido como false, o overlay é automaticamente desabilitado e o conteúdo externo se torna interativo.
<template>
  <NModal :modal="false" title="Modal interactive">
    <NButton label="Open" color="neutral" variant="subtle" />

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

Dispensável

Use a prop dismissible para controlar se o Modal pode ser dispensado ao clicar fora dele ou pressionar escape. O padrão é true.

Um evento close:prevent será emitido quando o usuário tentar fechá-lo.
Você pode combinar modal: false com dismissible: false para tornar o fundo do Modal interativo sem fechá-lo.
<template>
  <NModal :dismissible="false" title="Modal non-dismissible">
    <NButton label="Open" color="neutral" variant="subtle" />

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

Rolável 4.2+

Use a prop scrollable para tornar o conteúdo do Modal rolável dentro do overlay.

Como o overlay é necessário para a rolagem, modal: false não é compatível, e overlay: false apenas remove o fundo.
<template>
  <NModal scrollable title="Modal scrollable">
    <NButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </NModal>
</template>
Há um problema conhecido em que clicar na barra de rolagem pode fechar o diálogo sem querer em alguns sistemas operacionais.

Tela cheia

Use a prop fullscreen para deixar o Modal em tela cheia.

<template>
  <NModal fullscreen title="Modal fullscreen">
    <NButton label="Open" color="neutral" variant="subtle" />

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

Desmontar Soon

Use a prop unmount-on-hide para evitar que o conteúdo do Modal seja desmontado quando ele é fechado. O padrão é true.

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

    <template #body>
      <Placeholder class="h-48" />
    </template>
  </NModal>
</template>
Você pode inspecionar o DOM para ver o conteúdo do Modal sendo renderizado mesmo enquanto ele está fechado.
Quando a prop portal é definida como false, o conteúdo também é renderizado no servidor. Isso é útil para renderizar um Modal aberto durante o SSR sem uma piscada no carregamento da página, ou para expor seu conteúdo para SEO.

Exemplos

Controlar o estado de abertura

Você pode controlar o estado de abertura usando a prop default-open ou a diretiva v-model:open.

Neste exemplo, aproveitando o defineShortcuts, você pode abrir e fechar o Modal pressionando O.
Isso permite mover o gatilho para fora do Modal ou removê-lo por completo.

Uso programático

Você pode usar o composable useOverlay para abrir um Modal programaticamente.

Certifique-se de envolver seu app com o componente App, que usa o componente OverlayProvider.

Primeiro, crie um componente de modal que será aberto programaticamente:

undefined.vue
Aqui emitimos um evento close quando o modal é fechado ou dispensado. Você pode emitir qualquer dado por meio do evento close, porém o evento precisa ser emitido para que o valor de retorno seja capturado.

Depois, use-o no seu app:

Você pode fechar o modal dentro do componente de modal emitindo emit('close').

Modais aninhados

Você pode aninhar modais uns dentro dos outros.

Com slot de rodapé

Use o slot #footer para adicionar conteúdo após o corpo do Modal.

Com paleta de comandos

Você pode usar um componente CommandPalette dentro do conteúdo do Modal.

Este exemplo usa useLazyFetch com immediate: false para buscar os dados apenas quando o Modal abre.

API

Props

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

The content of the modal.

overlaytrueboolean

Render an overlay behind the modal.

scrollablefalseboolean

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

transitiontrueboolean

Animate the modal when opening or closing.

fullscreenfalseboolean

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

portaltrue string | false | true | HTMLElement

Render the modal in a portal.

closetrueboolean | Omit<ButtonProps, LinkPropsKeys>

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

closeIconappConfig.ui.icons.closeany

The icon displayed in the close button.

dismissibletrueboolean

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

openboolean

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

defaultOpenboolean

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

modaltrueboolean

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.

unmountOnHidetrueboolean

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; actions?: SlotClass; footer?: SlotClass; title?: SlotClass; description?: SlotClass; close?: SlotClass; loading?: SlotClass; spinner?: SlotClass; }

Slots

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

Emits

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

Tema

app.config.ts
export default defineAppConfig({
  ui: {
    modal: {
      slots: {
        overlay: 'fixed inset-0',
        content: 'bg-default flex flex-col focus:outline-none',
        header: 'flex items-center gap-1.5 p-4 sm:px-6 min-h-16',
        wrapper: '',
        body: 'flex-1 p-4 sm:p-6',
        actions: 'flex items-center justify-between gap-1.5 p-4 sm:px-6 border-default border-t',
        footer: 'flex items-center gap-1.5 p-4 sm:px-6',
        title: 'text-xl font-semibold text-secondary',
        description: 'mt-1 text-muted text-sm',
        close: 'absolute top-4 end-4',
        loading: 'p-5 flex items-center justify-center',
        spinner: ''
      },
      variants: {
        transition: {
          true: {
            overlay: 'data-[state=open]:animate-[fade-in_200ms_ease-out] data-[state=closed]:animate-[fade-out_200ms_ease-in]',
            content: 'data-[state=open]:animate-[scale-in_200ms_ease-out] data-[state=closed]:animate-[scale-out_200ms_ease-in]'
          }
        },
        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-secondary/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 '@nitro/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        modal: {
          slots: {
            overlay: 'fixed inset-0',
            content: 'bg-default flex flex-col focus:outline-none',
            header: 'flex items-center gap-1.5 p-4 sm:px-6 min-h-16',
            wrapper: '',
            body: 'flex-1 p-4 sm:p-6',
            actions: 'flex items-center justify-between gap-1.5 p-4 sm:px-6 border-default border-t',
            footer: 'flex items-center gap-1.5 p-4 sm:px-6',
            title: 'text-xl font-semibold text-secondary',
            description: 'mt-1 text-muted text-sm',
            close: 'absolute top-4 end-4',
            loading: 'p-5 flex items-center justify-center',
            spinner: ''
          },
          variants: {
            transition: {
              true: {
                overlay: 'data-[state=open]:animate-[fade-in_200ms_ease-out] data-[state=closed]:animate-[fade-out_200ms_ease-in]',
                content: 'data-[state=open]:animate-[scale-in_200ms_ease-out] data-[state=closed]:animate-[scale-out_200ms_ease-in]'
              }
            },
            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-secondary/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'
              }
            }
          ]
        }
      }
    })
  ]
})

Changelog

No recent changes