Componentes
Modal
Dialog 800px com header (ícone + title + description + close), body slot livre, footer (cancel + primary). Native `<dialog>`, ESC e click-outside fecham.
Default
Dispara via data-modal-open="<id>" em qualquer elemento. Slot default = body.
<Button data-modal-open="confirm-1">Abrir modal</Button>
<Modal id="confirm-1" title="Confirmar exclusão" description="Esta ação não pode ser desfeita.">
<p>O paciente e todos os dados associados serão removidos permanentemente.</p>
</Modal>
<Button data-modal-open="confirm-1">Abrir modal</Button>
<Modal id="confirm-1" title="Confirmar exclusão" description="Esta ação não pode ser desfeita.">
<p>O paciente e todos os dados associados serão removidos permanentemente.</p>
</Modal> <template>
<button @click="open = true" class="itps-btn itps-btn--primary">Abrir modal</button>
<dialog v-if="open" open class="rounded-card border border-border bg-card text-foreground p-6 max-w-2xl">
<header class="flex items-start gap-3">
<h2 class="text-lg font-semibold">Confirmar exclusão</h2>
<button @click="open = false" class="ml-auto itps-btn itps-btn--ghost itps-btn--icon">
<span class="material-symbols-outlined">close</span>
</button>
</header>
<p class="mt-2 text-sm text-muted-foreground">Esta ação não pode ser desfeita.</p>
<p class="mt-4">O paciente e todos os dados associados serão removidos permanentemente.</p>
<footer class="mt-6 flex justify-end gap-2">
<button @click="open = false" class="itps-btn itps-btn--ghost">Cancelar</button>
<button class="itps-btn itps-btn--primary">Confirmar</button>
</footer>
</dialog>
</template>
<script setup>
import { ref } from 'vue'
const open = ref(false)
</script>
<template>
<button @click="open = true" class="itps-btn itps-btn--primary">Abrir modal</button>
<dialog v-if="open" open class="rounded-card border border-border bg-card text-foreground p-6 max-w-2xl">
<header class="flex items-start gap-3">
<h2 class="text-lg font-semibold">Confirmar exclusão</h2>
<button @click="open = false" class="ml-auto itps-btn itps-btn--ghost itps-btn--icon">
<span class="material-symbols-outlined">close</span>
</button>
</header>
<p class="mt-2 text-sm text-muted-foreground">Esta ação não pode ser desfeita.</p>
<p class="mt-4">O paciente e todos os dados associados serão removidos permanentemente.</p>
<footer class="mt-6 flex justify-end gap-2">
<button @click="open = false" class="itps-btn itps-btn--ghost">Cancelar</button>
<button class="itps-btn itps-btn--primary">Confirmar</button>
</footer>
</dialog>
</template>
<script setup>
import { ref } from 'vue'
const open = ref(false)
</script> import { useState } from 'react';
export function ConfirmModal() {
const [open, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen(true)} className="itps-btn itps-btn--primary">Abrir modal</button>
{open && (
<dialog open className="rounded-card border border-border bg-card text-foreground p-6 max-w-2xl">
<header className="flex items-start gap-3">
<h2 className="text-lg font-semibold">Confirmar exclusão</h2>
<button onClick={() => setOpen(false)} className="ml-auto itps-btn itps-btn--ghost itps-btn--icon">
<span className="material-symbols-outlined">close</span>
</button>
</header>
<p className="mt-2 text-sm text-muted-foreground">Esta ação não pode ser desfeita.</p>
<p className="mt-4">O paciente e todos os dados associados serão removidos permanentemente.</p>
<footer className="mt-6 flex justify-end gap-2">
<button onClick={() => setOpen(false)} className="itps-btn itps-btn--ghost">Cancelar</button>
<button className="itps-btn itps-btn--primary">Confirmar</button>
</footer>
</dialog>
)}
</>
);
}
import { useState } from 'react';
export function ConfirmModal() {
const [open, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen(true)} className="itps-btn itps-btn--primary">Abrir modal</button>
{open && (
<dialog open className="rounded-card border border-border bg-card text-foreground p-6 max-w-2xl">
<header className="flex items-start gap-3">
<h2 className="text-lg font-semibold">Confirmar exclusão</h2>
<button onClick={() => setOpen(false)} className="ml-auto itps-btn itps-btn--ghost itps-btn--icon">
<span className="material-symbols-outlined">close</span>
</button>
</header>
<p className="mt-2 text-sm text-muted-foreground">Esta ação não pode ser desfeita.</p>
<p className="mt-4">O paciente e todos os dados associados serão removidos permanentemente.</p>
<footer className="mt-6 flex justify-end gap-2">
<button onClick={() => setOpen(false)} className="itps-btn itps-btn--ghost">Cancelar</button>
<button className="itps-btn itps-btn--primary">Confirmar</button>
</footer>
</dialog>
)}
</>
);
} <!-- Preline: data-hs-overlay para trigger/close -->
<button type="button" data-hs-overlay="#hs-confirm" class="itps-btn itps-btn--primary">Abrir modal</button>
<div id="hs-confirm" class="hs-overlay hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div class="rounded-card border border-border bg-card text-foreground p-6 max-w-2xl w-full mx-4">
<header class="flex items-start gap-3">
<h2 class="text-lg font-semibold">Confirmar exclusão</h2>
<button type="button" data-hs-overlay="#hs-confirm" class="ml-auto itps-btn itps-btn--ghost itps-btn--icon">
<span class="material-symbols-outlined">close</span>
</button>
</header>
<p class="mt-2 text-sm text-muted-foreground">Esta ação não pode ser desfeita.</p>
<p class="mt-4">O paciente e todos os dados associados serão removidos permanentemente.</p>
<footer class="mt-6 flex justify-end gap-2">
<button type="button" data-hs-overlay="#hs-confirm" class="itps-btn itps-btn--ghost">Cancelar</button>
<button type="button" class="itps-btn itps-btn--primary">Confirmar</button>
</footer>
</div>
</div>
<!-- Preline: data-hs-overlay para trigger/close -->
<button type="button" data-hs-overlay="#hs-confirm" class="itps-btn itps-btn--primary">Abrir modal</button>
<div id="hs-confirm" class="hs-overlay hidden fixed inset-0 z-50 flex items-center justify-center bg-black/50">
<div class="rounded-card border border-border bg-card text-foreground p-6 max-w-2xl w-full mx-4">
<header class="flex items-start gap-3">
<h2 class="text-lg font-semibold">Confirmar exclusão</h2>
<button type="button" data-hs-overlay="#hs-confirm" class="ml-auto itps-btn itps-btn--ghost itps-btn--icon">
<span class="material-symbols-outlined">close</span>
</button>
</header>
<p class="mt-2 text-sm text-muted-foreground">Esta ação não pode ser desfeita.</p>
<p class="mt-4">O paciente e todos os dados associados serão removidos permanentemente.</p>
<footer class="mt-6 flex justify-end gap-2">
<button type="button" data-hs-overlay="#hs-confirm" class="itps-btn itps-btn--ghost">Cancelar</button>
<button type="button" class="itps-btn itps-btn--primary">Confirmar</button>
</footer>
</div>
</div> Statuses no header
alert (amarelo warning) e error (coral) trocam o ícone do header.
<Button data-modal-open="alert-modal">Modal alert</Button>
<Button data-modal-open="error-modal">Modal error</Button>
<Modal id="alert-modal" status="alert" title="Sem conexão" description="Suas alterações foram salvas localmente." />
<Modal id="error-modal" status="error" title="Falha no upload" description="O arquivo excede 10MB." primaryText="Tentar novamente" />
<Button data-modal-open="alert-modal">Modal alert</Button>
<Button data-modal-open="error-modal">Modal error</Button>
<Modal id="alert-modal" status="alert" title="Sem conexão" description="Suas alterações foram salvas localmente." />
<Modal id="error-modal" status="error" title="Falha no upload" description="O arquivo excede 10MB." primaryText="Tentar novamente" /> Footer com primary + cancel
Footer aparece automaticamente quando há primaryText. Cancel default = “cancelar” (ghost).
<Button data-modal-open="save-modal">Salvar prontuário</Button>
<Modal id="save-modal" title="Salvar e fechar?" description="Suas alterações serão aplicadas." primaryText="Salvar" cancelText="Continuar editando">
<p>Você pode reabrir o prontuário a qualquer momento na lista.</p>
</Modal>
<Button data-modal-open="save-modal">Salvar prontuário</Button>
<Modal id="save-modal" title="Salvar e fechar?" description="Suas alterações serão aplicadas." primaryText="Salvar" cancelText="Continuar editando">
<p>Você pode reabrir o prontuário a qualquer momento na lista.</p>
</Modal> Sem footer
Modal informativo, fecha só pelo X / ESC / click-outside.
<Button data-modal-open="info-modal">Sobre o sistema</Button>
<Modal id="info-modal" title="Sinapse Design System" description="Versão 1.0" showFooter={false}>
<p>Sistema de design construído com Astro, Tailwind v4 e tokens semânticos.</p>
</Modal>
<Button data-modal-open="info-modal">Sobre o sistema</Button>
<Modal id="info-modal" title="Sinapse Design System" description="Versão 1.0" showFooter={false}>
<p>Sistema de design construído com Astro, Tailwind v4 e tokens semânticos.</p>
</Modal> Como funciona
- Renderiza
<dialog>nativo —showModal()automatiza foco, backdrop, ESC - Trigger via
data-modal-open="<id>"em qualquer elemento clicável - Click no backdrop fecha (handler global)
- Width 800px, max-w 100% - 2rem (responsivo)
Props
| Prop | Tipo | Default | Descrição |
|---|---|---|---|
id | string | obrigatório | ID único, usado pelo trigger. |
title | string | obrigatório | Título do header. |
description | string | — | Subtítulo opcional. |
status | 'default' | 'alert' | 'error' | 'default' | Cor/ícone do header. |
showIcon | boolean | false | Força exibir ícone (default só em alert/error). |
icon | string | por status | Material Symbol custom. |
cancelText | string | 'cancelar' | Texto do botão cancel. |
primaryText | string | — | Texto do botão primary. Sem isso, footer some. |
primaryHref | string | — | Faz primary virar <a>. |
secondaryText | string | — | Botão secundário entre cancel e primary. |
showFooter | boolean | true | Suprime footer inteiro. |
class | string | — | Classes adicionais. |