Saltar al contenido principal

Core · Avisos y estado

Diálogo modal

Pide tomar una decisión antes de continuar, mediante un elemento dialog nativo.

Esta traducción la ha escrito una IA y todavía no la ha revisado ninguna persona. Leer esta página en inglés

Ejemplo

Un botón que abre un diálogo para preguntar si se debe eliminar un borrador.

Delete this draft?

You cannot undo this. The draft and its comments are deleted.

A native <dialog>.

Escape closes it and focus goes back to the button that opened it. Nothing is deleted here.

Problemas conocidos

Algunos navegadores compatibles aún no admiten los atributos command y commandfor. En esos navegadores, si JavaScript está desactivado, el botón de apertura no hace nada.

Ofrece a quien lee otra forma de realizar la misma acción.

Código

Copiar el marcado

Un dialog sin clase no necesita ninguna: tiene una superficie y está centrado.

HTML
<button type="button" commandfor="rename" command="show-modal">Rename project</button>

<dialog id="rename" aria-labelledby="rename-title">
  <h2 id="rename-title">Rename project</h2>
  <form method="dialog">
    <label for="rename-name">New name</label>
    <input id="rename-name" type="text" autofocus>
    <button type="button" commandfor="rename" command="close">Cancel</button>
    <button value="rename">Rename project</button>
  </form>
</dialog>

.luz-modal añade la cabecera con su botón de cerrar, el cuerpo y el pie, la disposición en pantallas pequeñas y el desvanecimiento. Con la clase:

HTML
<button class="luz-btn luz-btn--primary" type="button" commandfor="rename" command="show-modal">Rename project</button>

<dialog class="luz-modal" id="rename" aria-labelledby="rename-title">
  <div class="luz-modal__header">
    <button class="luz-modal__close" type="button" commandfor="rename" command="close" aria-label="Close dialog">×</button>
    <h2 class="luz-modal__title" id="rename-title">Rename project</h2>
  </div>
  <form method="dialog">
    <div class="luz-modal__body">
      <div class="luz-field">
        <label class="luz-field__label" for="rename-name">New name</label>
        <input class="luz-input" id="rename-name" type="text" autofocus>
      </div>
    </div>
    <div class="luz-modal__footer">
      <button class="luz-btn luz-btn--ghost" type="button" commandfor="rename" command="close">Cancel</button>
      <button class="luz-btn luz-btn--primary" value="rename">Rename project</button>
    </div>
  </form>
</dialog>
JS
import { initModals } from "@luz-ds/core/modal";
initModals();

Variantes y opciones

Un tamaño y ningún modificador: hasta 600 px de ancho y el 90 % de la altura de la ventana gráfica, con desplazamiento interno si el contenido es más largo. Hasta 640 px de ancho, o con menos de 30 rem de alto, ocupa toda la pantalla.

Clase o atributoQué hace
.luz-modal
La superficie del diálogo, con un fondo atenuado y ligeramente difuminado
.luz-modal__header, __title
La fila del título, con una línea debajo
.luz-modal__close
El botón ×, al final de la fila del título, de al menos 28 px (44 px cuando el diálogo ocupa toda la pantalla)
.luz-modal__body
El contenido
.luz-modal__footer
Los botones, al final de la fila, con una línea por encima
command="show-modal", command="close"
Abre y cierra el diálogo indicado en commandfor

Cómo funciona

Un elemento nativo <dialog> con estilos de components/modal.css, que se abre como diálogo modal.

  • El navegador se encarga de lo difícil: la página del fondo queda inerte, el foco permanece dentro, Escape lo cierra y el foco vuelve al botón que lo abrió.
  • Los botones lo abren y lo cierran con command y commandfor, sin scripts.
  • initModals() de modal.js gestiona los mismos dos comandos en los navegadores que aún no los admiten, y no hace nada en los demás. luz.js lo ejecuta.
  • Al enviar un <form method="dialog"> que esté dentro, el diálogo se cierra y su returnValue toma el valor del botón pulsado.
  • Hasta 640 px de ancho, o con menos de 30 rem de alto (un teléfono en horizontal), ocupa toda la pantalla: sin margen, radio ni sombra.
  • Entonces es lo único que se desplaza: la cabecera con el cierre queda arriba y el pie abajo (en una ventana baja se desplaza). El relleno respeta los márgenes de las áreas seguras.

Sin JavaScript

Todo funciona en los navegadores que admiten command. En los demás, el botón de apertura no hace nada: consulta «Problemas conocidos».

Teclado y ARIA

Tab y Mayús+Tab permiten moverse solo dentro del diálogo; Escape lo cierra.

  • Al abrirlo, el foco pasa al primer elemento que lo admite o al que tenga autofocus.
  • Asigna un nombre al diálogo con aria-labelledby, que apunte a su título.
  • El botón × necesita aria-label="Close dialog": su texto es un símbolo.

El contraste y el foco están en la pestaña Diseño.

Diseño

Cuándo usarlo

Usa un diálogo modal para pedir una decisión o una breve respuesta que deba resolverse antes de continuar.

  • Para confirmar una acción que no se puede deshacer.
  • Para pedir uno o dos datos, como un nombre nuevo.

Cuándo no usarlo

No uses un diálogo modal para algo que se pueda ignorar o retomar más tarde.

  • Para información sobre la página, usa una alerta.
  • Para una confirmación que no requiere respuesta, usa un aviso emergente.
  • Para un formulario largo, usa una página propia.
  • No abras nunca un diálogo modal al cargar la página.

Qué hacer y qué no

Hazlo Formula la pregunta en el título y repite la acción en el botón principal.
No lo hagas Pongas «¿Estás seguro?» con «Cancelar» y «Continuar»: quien lee tendría que leer el cuerpo para saber qué hace «Continuar».

Cómo escribir el contenido

El título formula la pregunta o nombra la tarea: «¿Eliminar este borrador?».

  • El cuerpo explica lo que ocurrirá en una o dos frases.
  • El botón principal repite la acción: «Eliminar borrador». El otro dice «Cancelar».
  • Nunca uses «Aceptar», «Sí» ni «Confirmar».

Contraste y foco

El texto aparece sobre la superficie del diálogo, por encima de una capa oscura que atenúa la página.

ParteColor sobre el fondoClaroOscuroAlto contrasteRequisitos
Título y cuerpo
--luz-text-primary on --luz-surface
16.5:1
14.0:1
21.0:1
4.5:1
Botón de cierre
--luz-text-secondary on --luz-surface
6.4:1
6.9:1
19.3:1
4.5:1
Anillo de foco
--luz-focus-ring on --luz-surface
5.9:1
7.2:1
21.0:1
3:1

Medido a partir de los valores de los tokens de cada modo, redondeado hacia abajo. Los requisitos son los mínimos de WCAG 2.2 AA: 4,5:1 para el texto y 3:1 para los anillos de foco, los bordes y los iconos.

  • Foco: al abrirse, pasa al diálogo y, al cerrarse, vuelve al botón que lo abrió. El botón de cierre tiene un contorno --luz-focus-ring de 2 px, a 2 px de distancia.
  • Bordes: el diálogo tiene un borde y una sombra, así que también queda separado de la página con colores forzados.
  • Área interactiva: el botón de cierre mide al menos 28 px cuadrados, y 44 px cuando el diálogo ocupa toda la pantalla.

Tokens que usa

Los 22 tokens que lee components/modal.css, agrupados por familia. Cada familia enlaza a su página, con los valores de cada modo. En Figma son variables con los mismos nombres: consulta Tokens en Figma.

Spacing and sizing
--luz-control-lg, --luz-control-sm, --luz-space-2xs, --luz-space-lg, --luz-space-md, --luz-space-sm, --luz-space-xl, --luz-space-xs
Typography
--luz-text-2xl, --luz-text-lg
Radius
--luz-radius-md
Shadows
--luz-shadow-lg
Motion
--luz-duration-base, --luz-duration-fast, --luz-easing-enter

El teclado y ARIA están en la pestaña Código.