# Modal

Diálogo centralizado para decisões, confirmação e conteúdo que bloqueia o fluxo.

> Documentação do Sinapse DS. Snippets shadcn usam os componentes do seu projeto (`@/components/ui/*`); snippets HTML usam as classes `itps-*` do CSS Sinapse (`/css/<versão>/`, veja `/comecar.md`). Componentes do site de documentação não fazem parte do DS.

- Categoria: Overlays
- Status: stable
- Relacionados: /componentes/side-panel.md, /componentes/toast.md

## Em cada stack

- HTML e Astro: Classes `itps-*` do CSS Sinapse, com todas as variantes desta página.
- React e Vue: Componentes padrão do shadcn (`dialog`) com o tema Sinapse: cores, fonte e raio vêm do tema, e as variantes `itps-*` desta página existem só no HTML. O Button é o Sinapse.

**React/Next (shadcn/ui)**

```tsx
import { Button } from '@/components/ui/button'
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog'

export function DialogExample() {
  return (
    <Dialog>
      <DialogTrigger asChild>
        <Button variant="itps">Abrir modal</Button>
      </DialogTrigger>
      <DialogContent>
        <DialogHeader>
          <DialogTitle>Confirmar exclusão</DialogTitle>
          <DialogDescription>Esta ação não pode ser desfeita.</DialogDescription>
        </DialogHeader>
        <p className="text-sm text-muted-foreground">
          O paciente e todos os dados associados serão removidos permanentemente.
        </p>
        <DialogFooter>
          <DialogClose asChild>
            <Button variant="ghost">Cancelar</Button>
          </DialogClose>
          <Button variant="itps">Confirmar</Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  )
}
```

**Vue 3/Nuxt 4 (shadcn-vue)**

```vue
<script setup lang="ts">
import { Button } from '@/components/ui/button'
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
  DialogTrigger,
} from '@/components/ui/dialog'
</script>

<template>
  <Dialog>
    <DialogTrigger as-child>
      <Button variant="itps">Abrir modal</Button>
    </DialogTrigger>
    <DialogContent>
      <DialogHeader>
        <DialogTitle>Confirmar exclusão</DialogTitle>
        <DialogDescription>Esta ação não pode ser desfeita.</DialogDescription>
      </DialogHeader>
      <p class="text-sm text-muted-foreground">
        O paciente e todos os dados associados serão removidos permanentemente.
      </p>
      <DialogFooter>
        <DialogClose as-child>
          <Button variant="ghost">Cancelar</Button>
        </DialogClose>
        <Button variant="itps">Confirmar</Button>
      </DialogFooter>
    </DialogContent>
  </Dialog>
</template>
```

**HTML (copy-first)**

```html
<button type="button" class="itps-btn itps-btn--primary" data-modal-open="confirm-modal">
  Abrir modal
</button>

<dialog id="confirm-modal" class="itps-modal" aria-labelledby="confirm-modal-title" aria-describedby="confirm-modal-description" data-modal>
  <header class="itps-modal__header">
    <div class="itps-modal__heading">
      <div class="itps-modal__copy">
        <h2 id="confirm-modal-title" class="itps-modal__title">Confirmar exclusão</h2>
        <p id="confirm-modal-description" class="itps-modal__description">Esta ação não pode ser desfeita.</p>
      </div>
    </div>
    <button type="button" class="itps-modal__close" aria-label="Fechar" data-modal-close>
      <i class="ti ti-x" aria-hidden="true"></i>
    </button>
  </header>

  <div class="itps-modal__body">
    <p>O paciente e todos os dados associados serão removidos permanentemente.</p>
  </div>

  <footer class="itps-modal__footer">
    <button type="button" class="itps-btn itps-btn--ghost" data-modal-close autofocus>
      <i class="ti ti-x" aria-hidden="true"></i>
      <span>Cancelar</span>
    </button>
    <div class="itps-modal__footer-actions">
      <button type="button" class="itps-btn itps-btn--primary">Confirmar</button>
    </div>
  </footer>
</dialog>
```

## Instalação

O comando instala o Dialog da sua stack e o Button Sinapse com o tema Sinapse. Configure o registry uma vez por projeto em [Comece por aqui](/comecar).

**terminal · React/Next**

```sh
pnpm dlx shadcn@latest add @sinapse/modal
```

**terminal · Vue 3/Nuxt 4**

```sh
pnpm dlx shadcn-vue@latest add https://dsystem.sinapse.org.br/r/vue/modal.json
```

Sem shadcn, não há instalação por componente: use o CSS Sinapse e as classes da API copy-first desta página.

## Uso

Em React/Next com shadcn/ui e Vue 3/Nuxt 4 com shadcn-vue, use os componentes compostos de `Dialog`. A stack gerencia foco, portal, Esc e acessibilidade.

Para HTML ou wrappers finos, use `<dialog>` nativo com `itps-modal`. Este site usa `src/scripts/overlays.ts` para inicializar `[data-modal]`, abrir gatilhos com `data-modal-open` e fechar controles com `data-modal-close`; o pacote não inclui esse script, então o produto precisa abrir e fechar o `<dialog>` por conta própria.

## Exemplos

### Estrutura

| Peça | React/Next e Vue 3/Nuxt 4 | HTML |
|------|--------------------------|------|
| Raiz | `Dialog` | `<dialog class="itps-modal" data-modal>` |
| Trigger | `DialogTrigger` | `[data-modal-open="id-do-modal"]` |
| Cabeçalho | `DialogHeader`, `DialogTitle`, `DialogDescription` | `itps-modal__header`, `aria-labelledby`, `aria-describedby` |
| Ações | `DialogFooter` | `itps-modal__footer` |
| Fechar | `DialogClose` | `itps-modal__close` ou `[data-modal-close]` |

### Status

Use `alert` quando a decisão pede atenção e `error` quando existe falha ou bloqueio.

**HTML (copy-first)**

```html
<button type="button" class="itps-btn itps-btn--secondary" data-modal-open="alert-modal">
  Modal de alerta
</button>

<dialog id="alert-modal" class="itps-modal itps-modal--alert" aria-labelledby="alert-modal-title" aria-describedby="alert-modal-description" data-modal>
  <header class="itps-modal__header">
    <div class="itps-modal__heading">
      <span class="itps-modal__icon" aria-hidden="true">
        <i class="ti ti-alert-triangle" aria-hidden="true"></i>
      </span>
      <div class="itps-modal__copy">
        <h2 id="alert-modal-title" class="itps-modal__title">Sem conexão</h2>
        <p id="alert-modal-description" class="itps-modal__description">Suas alterações foram salvas localmente.</p>
      </div>
    </div>
    <button type="button" class="itps-modal__close" aria-label="Fechar" data-modal-close>
      <i class="ti ti-x" aria-hidden="true"></i>
    </button>
  </header>
  <div class="itps-modal__body">
    <p>Confira a conexão antes de continuar o atendimento.</p>
  </div>
  <footer class="itps-modal__footer">
    <button type="button" class="itps-btn itps-btn--primary" data-modal-close>Entendi</button>
  </footer>
</dialog>

<button type="button" class="itps-btn itps-btn--secondary" data-modal-open="error-modal">
  Modal de erro
</button>

<dialog id="error-modal" class="itps-modal itps-modal--error" aria-labelledby="error-modal-title" aria-describedby="error-modal-description" data-modal>
  <header class="itps-modal__header">
    <div class="itps-modal__heading">
      <span class="itps-modal__icon" aria-hidden="true">
        <i class="ti ti-alert-circle" aria-hidden="true"></i>
      </span>
      <div class="itps-modal__copy">
        <h2 id="error-modal-title" class="itps-modal__title">Falha no envio</h2>
        <p id="error-modal-description" class="itps-modal__description">O arquivo excede 10 MB.</p>
      </div>
    </div>
    <button type="button" class="itps-modal__close" aria-label="Fechar" data-modal-close>
      <i class="ti ti-x" aria-hidden="true"></i>
    </button>
  </header>
  <div class="itps-modal__body">
    <p>Escolha outro arquivo ou compacte o documento antes de tentar novamente.</p>
  </div>
  <footer class="itps-modal__footer">
    <button type="button" class="itps-btn itps-btn--primary" data-modal-close>Tentar novamente</button>
  </footer>
</dialog>
```

## API copy-first

| Classe/atributo | Uso |
|-----------------|-----|
| `itps-modal` | Superfície visual do `<dialog>`. |
| `itps-modal--alert`, `itps-modal--error` | Status visual para atenção e bloqueio. |
| `itps-modal__header`, `itps-modal__body`, `itps-modal__footer` | Anatomia interna. |
| `itps-modal__close` | Botão de fechamento com nome acessível. |
| `data-modal` | Marca o dialog inicializável pelo script. |
| `data-modal-open`, `data-modal-close` | Abre por `id` e fecha o dialog atual. |
| `aria-labelledby`, `aria-describedby` | Conectam título e descrição ao dialog. |
| `aria-label` | Nomeia o botão de fechar quando ele usa só ícone. |

## Acessibilidade

Use modal para confirmação, decisão bloqueante ou tarefa curta. Para feedback transitório, prefira Toast; para conteúdo longo, Side Panel; para informação sem bloqueio, Callout ou Banner.

No shadcn, preserve `DialogTitle` e `DialogDescription`. Na API copy-first, conecte título e descrição com `aria-labelledby` e `aria-describedby`, use botão real para fechar com `aria-label="Fechar"` e deixe o `<dialog>` nativo controlar Esc e a modalidade.

Teclado e foco:

| Momento | Comportamento esperado |
|---------|------------------------|
| Abrir | O foco entra no modal. Por padrão vai para o primeiro controle focável; em confirmação destrutiva, comece na ação segura (`Cancelar`). |
| Aberto | Tab e Shift+Tab circulam só dentro do modal; o resto da página fica inerte. |
| Esc | Fecha o modal, como o botão Fechar. |
| Fechar | O foco volta ao botão que abriu o modal. Se esse botão sumiu (por exemplo, a linha foi excluída), mova o foco para um ponto próximo e estável. |

No `<dialog>` aberto com `showModal()`, o navegador já prende o foco, fecha com Esc e devolve o foco ao gatilho; use `autofocus` no controle que deve receber o foco inicial, como no exemplo acima. No shadcn, o `Dialog` faz o mesmo; para escolher o foco inicial, use `onOpenAutoFocus` (React) ou `@open-auto-focus` (Vue).
