Tema
BaseModal e BaseConfirmModal
BaseModal abre uma janela sobre a tela e prende o foco nela até a pessoa terminar ou fechar. BaseConfirmModal é a confirmação pronta: pergunta o que vai acontecer, pede a justificativa quando o fluxo exige e deixa claro o botão que decide. É o modal das aprovações (devolver medição, cancelar aditivo).
Uso
- Use modal só quando a pessoa precisa decidir ou informar algo antes de seguir. Para avisar, use um aviso; para editar muita coisa, use uma página.
- O título é uma pergunta ou uma ação com o objeto: "Devolver a medição 29?", não "Atenção" nem "Tem certeza?".
- A descrição diz o que acontece e com quem: "A medição volta para Paulo Ramos com a sua justificativa."
- O botão que decide repete o verbo do título: "Devolver medição", nunca "Sim" ou "OK". O botão de voltar vem antes, à esquerda.
dangersó para o que não tem volta, como excluir. Devolver para correção não é perigoso: use o botão primário e o ícone de atenção.- Com
danger, a descrição diz com palavras que não dá para desfazer ("Não dá para desfazer."). A cor vermelha sozinha não avisa quem não enxerga cores. - Com
require-reason, a justificativa é obrigatória e vai junto no eventoconfirm. O erro só aparece depois que a pessoa tenta confirmar. O modal fica persistente, para um clique fora não apagar o texto. - A justificativa é apagada sempre que o modal fecha, então um único
BaseConfirmModalreaproveitado numa lista nunca leva o motivo de uma medição para a próxima. Para abrir com um texto pronto, preenchav-model:reasonantes de abrir. - Se a ação remove o botão que abriu o modal (uma linha da tabela, por exemplo), passe
return-focuscom o elemento ou o seletor que deve receber o foco, como o título da lista. Sem ele, o foco fica no início da página. - Quem abre o modal também fecha: depois de
confirm, salve, troqueloadinge feche comv-model. Assim o botão mostra o progresso enquanto a ação roda.
Fechar pelo fundo
Por padrão, clicar no fundo escuro fecha. Use persistent quando o modal tem um formulário que a pessoa perderia por um clique fora. Esc e o botão de fechar continuam funcionando.
Tamanhos
Estilo
| Propriedade | Token |
|---|---|
| Fundo da tela atrás | --tj-overlay |
| Fundo do modal | --tj-layer-01; rodapé --tj-layer-02 com borda --tj-border-subtle-01 |
| Sombra | --tj-shadow-3 |
| Raio | --tj-radius-lg (12 px) |
| Título | --tj-text-xl, --tj-weight-semibold, --tj-text-primary |
| Descrição | --tj-text-secondary |
| Ícone | --tj-support-warning-text sobre --tj-support-warning-subtle; com danger, --tj-support-error-text sobre --tj-support-error-subtle |
| Espaçamento | --tj-space-6 nas laterais (--tj-space-4 à direita do cabeçalho, por causa do botão de fechar), --tj-space-4 entre cabeçalho e corpo |
| Corpo com rolagem, em foco | --tj-ring por dentro da borda |
| Entrada | --tj-duration-moderate-02 (240 ms) com --tj-easing-standard, como no protótipo; sem animação com movimento reduzido |
Código
vue
<BaseConfirmModal
v-model="devolverAberto"
v-model:reason="justificativa"
title="Devolver a medição 29?"
description="A medição volta para Paulo Ramos com a sua justificativa."
icon="arrow-return-left"
confirm-label="Devolver medição"
require-reason
reason-helper-text="Fica no histórico da medição."
:loading="salvando"
@confirm="(motivo) => devolverMedicao(motivo)"
/>
<BaseModal v-model="editarAberto" title="Editar fiscal do contrato" persistent>
<BaseField label="Fiscal" required><BaseInput v-model="fiscal" /></BaseField>
<template #footer="{ close }">
<BaseButton kind="secondary" @click="close">Cancelar</BaseButton>
<BaseButton @click="salvarFiscal">Salvar fiscal</BaseButton>
</template>
</BaseModal>BaseModal
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
v-model | boolean | false | Aberto ou fechado |
title | string | — | Título e nome acessível (obrigatório) |
description | string | — | Linha abaixo do título, lida junto com ele |
size | 'sm' | 'md' | 'lg' | 'md' | Largura: 400, 480 ou 720 px |
icon | string | — | Ícone do Bootstrap Icons, sem bi-, ao lado do título |
danger | boolean | false | Ícone em vermelho, para ação sem volta |
persistent | boolean | false | Clicar no fundo não fecha |
loading | boolean | false | Ação em andamento: nada fecha o modal |
closeLabel | string | 'Fechar' | Nome do botão de fechar |
role | 'dialog' | 'alertdialog' | 'dialog' | alertdialog para confirmações que interrompem o trabalho |
returnFocus | HTMLElement | string | — | Recebe o foco ao fechar quando o botão que abriu não existe mais (elemento ou seletor CSS) |
Atributos extras, como class e data-testid, vão para o elemento do modal (role="dialog").
Slots: default (corpo) e footer, que recebe close. Eventos: update:modelValue e close (a pessoa fechou pelo Esc, pelo fundo, pelo botão de fechar ou pelo close do rodapé).
BaseConfirmModal
| Propriedade | Tipo | Padrão | Descrição |
|---|---|---|---|
v-model | boolean | false | Aberto ou fechado |
v-model:reason | string | '' | Texto da justificativa |
title, description, size, icon, danger, loading, closeLabel, returnFocus | Como no BaseModal | ||
persistent | boolean | false | Clicar no fundo não fecha; sempre ligado com requireReason |
confirmLabel | string | — | Verbo e objeto da decisão (obrigatório) |
cancelLabel | string | 'Voltar' | Botão que fecha sem decidir |
requireReason | boolean | false | Pede a justificativa antes de confirmar |
reasonLabel | string | 'Justificativa' | Rótulo do campo |
reasonHelperText | string | — | Ajuda abaixo do campo |
reasonInvalidText | string | 'Escreva a justificativa para continuar' | Erro quando a pessoa confirma sem escrever |
reasonPlaceholder | string | — | Exemplo dentro do campo |
Eventos: confirm, com a justificativa sem espaços nas pontas (vazia quando não há campo); close, quando a pessoa fecha sem decidir (voltar, Esc, fundo ou botão de fechar); update:modelValue e update:reason.
Acessibilidade
- O
BaseModalsegue o padrão Dialog (Modal) do WAI-ARIA APG:role="dialog",aria-modal="true",aria-labelledbyno título earia-describedbyna descrição. - O
BaseConfirmModalsegue o padrão Alert Dialog:role="alertdialog", com a descrição ligada poraria-describedby, para o leitor de tela anunciar a confirmação como urgente. - Ao abrir, o foco vai para o elemento marcado com
data-autofocus; se não houver, para o primeiro campo do corpo (a justificativa, nas aprovações); depois, para o primeiro botão do rodapé (noBaseConfirmModal, o de voltar; noBaseModal, ponha a ação menos destrutiva primeiro); por fim, para o próprio modal. - Tab e Shift+Tab circulam só dentro do modal, seguindo as paradas reais: um grupo de rádios conta uma vez, e o que está oculto ou desativado fica de fora. Se o foco sair do modal (pela barra de endereço, por exemplo), ele volta. Esc fecha, a menos que um menu dentro do modal já tenha usado a tecla para se fechar.
- Ao fechar, o foco volta para o botão que abriu o modal. Se ele não existe mais, vai para
return-focus; semreturn-focus, fica no início da página. - Quando o corpo tem mais texto do que cabe, ele entra na ordem do Tab como região com o nome do título, para as setas rolarem o texto.
- A página atrás não rola enquanto o modal está aberto; modais um sobre o outro dividem a mesma trava.
- Com
loading, o botão de confirmar mostra progresso (aria-busy) sem perder o foco, e Esc, o fundo e o botão de fechar esperam a ação terminar. Se o foco estava num botão que fica desativado, ele vai para o próprio modal e não cai para fora. - O erro da justificativa tem ícone e texto, não só a borda vermelha, e o foco volta para o campo.
- A animação de entrada some com
prefers-reduced-motion. Em alto contraste do Windows, o modal ganha borda. - Todos os estados passam no axe em jsdom. Contraste e rolagem não são medidos ali, porque jsdom não desenha a tela: eles são conferidos nas páginas desta documentação.