Modal
Diálogo centrado sobre un overlay tipo glass — el fondo detrás es el
contenido real de la app, desenfocado (backdrop-filter: blur en web,
sin token nuevo vía color-mix para el scrim; en Kotlin, una capa
capturada del árbol vía OrbitTheme — un caller que nunca envolvió su
app en OrbitTheme cae a un scrim plano en su lugar, ya que no hay nada
que desenfocar). Cuerpo color.surface.card, radius.md (8px). En web
envuelve un <dialog> real (focus trap, Escape, stacking por top-layer,
todo gratis); en Kotlin es un Dialog de Compose. Sombra solo acá y en
OrbitDrawer — el resto de los contenedores de Orbit son planos.
Siempre tres regiones apiladas, nunca un slot único: un title opcional
(texto plano, no un slot — negrita, 16px fijo), el content con scroll
propio, y un footer — el único lugar donde van las acciones/botones,
siempre alineado al borde final. El ancho es min(maxWidth, ancho de la ventana - 2 × space.lg) — un ancho fijo y cómodo por defecto, que solo
se achica en una ventana angosta.
Uso
Eliminar proyecto
Esta acción no se puede deshacer.
<orbit-button id="open">Eliminar proyecto</orbit-button><orbit-modal id="modal" heading="Eliminar proyecto"> <p>Esta acción no se puede deshacer.</p> <div slot="footer" style="display: flex; gap: 12px;"> <orbit-button variant="ghost" id="cancel">Cancelar</orbit-button> <orbit-button variant="destructive">Eliminar</orbit-button> </div></orbit-modal>
<script> open.addEventListener('click', () => modal.setAttribute('open', '')); cancel.addEventListener('click', () => modal.removeAttribute('open'));</script>var showModal by remember { mutableStateOf(false) }OrbitButton(text = "Eliminar proyecto", onClick = { showModal = true })OrbitModal( visible = showModal, onDismissRequest = { showModal = false }, title = "Eliminar proyecto", footer = { OrbitButton( text = "Cancelar", onClick = { showModal = false }, variant = OrbitButtonVariant.Ghost, ) OrbitButton( text = "Eliminar", onClick = { /* ... */ }, variant = OrbitButtonVariant.Destructive, ) },) { Text("Esta acción no se puede deshacer.")}Al hacer click afuera del diálogo también se cierra — en web porque el
click cae sobre el propio <dialog> (no sobre su contenido) y llama a
.close(); en Kotlin porque Dialog llama a onDismissRequest con el
comportamiento por defecto de la plataforma.
Props
| Prop | Tipo | Default | Descripción |
|---|---|---|---|
visible (Kotlin) / attr open (web) | boolean | false | Controla si el modal está abierto. |
onDismissRequest (Kotlin) / evento close (web) | () => void | — | Se dispara al cerrar (click afuera, Escape, o programático). |
title (Kotlin) / attr heading (web) | String? | null | Encabezado en negrita; requerido si no se pasa contentDescription/no hay title visible, para lectores de pantalla. |
contentDescription (Kotlin) | String? | null | Label accesible cuando no hay title. |
footer (Kotlin) / slot footer (web) | slot | — | Acciones del diálogo, siempre alineadas al borde final, con un divisor arriba. |
content (Kotlin) / children (web) | slot | — | Contenido del diálogo, con scroll propio si crece de más. |
contentPadding (Kotlin) | Dp | space.lg | Padding interno de título/contenido/footer. |
maxWidth (Kotlin) / custom property --modal-max-width (web) | Dp / css length | 480.dp / 480px | Ancho máximo de la tarjeta. |