vaiperui

componentes / modal

Modal

Diálogo sobre un fondo oscurecido con foco atrapado, bloqueo de scroll, cierre por Esc/fondo y role="dialog".

Cómo usar

Renderizado en un portal con `aria-modal`, nombrado por `title`. El foco entra al abrir y vuelve al disparador al cerrar; el Tab queda dentro y la página de atrás no se desplaza. Pasa `trigger` en el caso no controlado, o `open` + `onOpenChange` para controlarlo. Envuelve un botón del pie en `ModalClose` para cerrar.

preview
Modal.tsx
import { Button, Modal, ModalClose } from "vaiperui";
<Modal
trigger={<Button>Abrir</Button>}
title="¿Eliminar proyecto?"
footer={
<>
<ModalClose><Button variant="outlined">Cancelar</Button></ModalClose>
<ModalClose><Button variantColor="danger">Eliminar</Button></ModalClose>
</>
}
>
Esta acción no se puede deshacer.
</Modal>

Tamaños

preview
sm
md
lg
tamanos.tsx
<Modal size="sm" title="sm" trigger={<Button>sm</Button>}>...</Modal>
<Modal size="md" title="md" trigger={<Button>md</Button>}>...</Modal>
<Modal size="lg" title="lg" trigger={<Button>lg</Button>}>...</Modal>

Controlado

El padre controla `open` y reacciona a la respuesta.

preview
controlado
controlado.tsx
const [open, setOpen] = useState(false);
<Button onClick={() => setOpen(true)}>Open</Button>
<Modal open={open} onOpenChange={setOpen} title="..." footer={...}>...</Modal>

Props

PropValoresPredeterminadoDescripción
titleReactNodeobligatorioTítulo y nombre accesible (aria-labelledby)
triggerReactElementningunoElemento que lo abre; innecesario si es controlado
open / defaultOpen / onOpenChangeboolean / boolean / (open) => voidningunoEstado abierto controlado o no controlado
showClose / closeLabelboolean / stringtrue / "Close"Botón × en el encabezado y su nombre accesible
closeOnEscape / closeOnBackdropClickbooleantrueSi Esc o clic en el fondo lo cierran
sizesm | md | lgsmAncho máximo: 480, 640 u 800px
showHeaderDivider / showFooterDividerbooleantrueLíneas entre encabezado, cuerpo y pie
radius / animationnone | sm | md | lg | full / none | smooth | fast | bouncelg / smoothRedondeo y velocidad de la transición de entrada

Accesibilidad

  • El panel se renderiza en un portal en `document.body` con `role="dialog"` y `aria-modal="true"`.
  • `title` se renderiza como `h2` y se enlaza con `aria-labelledby`; el cuerpo se enlaza con `aria-describedby`.
  • Al abrir, el foco va al primer elemento enfocable del panel (o al propio panel) y vuelve al elemento enfocado antes al cerrar, si sigue en el DOM.
  • Tab y Shift+Tab quedan atrapados dentro del panel mientras está abierto, y el scroll del body se bloquea.
  • El disparador recibe `aria-haspopup="dialog"` y `aria-expanded`.
  • El botón de cerrar tiene `aria-label` de `closeLabel` (por defecto "Close"); el fondo es `aria-hidden`.

Teclado

TeclaAcción
Tab / Shift+TabMueve el foco entre los elementos del panel, volviendo al inicio/fin
EscCierra (se desactiva con `closeOnEscape={false}`)

Ten en cuenta

  • `title` es obligatorio y debe describir el diálogo; traduce `closeLabel`, ya que su valor por defecto está en inglés.
  • La página de fondo no se marca como `inert` ni `aria-hidden`; depende de `aria-modal` y de la trampa de Tab. Hacer clic en el fondo cierra, salvo con `closeOnBackdropClick={false}`.