Toast
Aviso pasajero en una esquina de la pantalla, para lo que acaba de pasar: guardado, enviado, no salió bien. Se apila, se pausa cuando el puntero o el foco entra y lo anuncia el lector de pantalla. Para lo que sigue vigente mientras la persona está en la pantalla, usa el aviso.
Ejemplos
Cada tipo tiene color, icono y duración propios. El error se queda más tiempo porque la persona necesita leerlo y, muchas veces, actuar; el cargando no se cierra solo, porque quien lo termina es el fin de la operación.
Tipos
info, success, warning, error y loading.
No se cierra solo
duration: null lo deja en la pantalla hasta que alguien haga clic en la X.
Duración propia
duration en milisegundos, en lugar de la predeterminada del tipo.
Pila
Hasta tres a la vista, apilados; el puntero o el foco los abre en abanico. El quinto cierra el más antiguo.
| Tipo | Se queda en pantalla | Se anuncia como |
|---|---|---|
info | 4 s | role="status", región cortés |
success | 3,5 s | role="status", región cortés |
warning | 6 s | role="status", región cortés |
error | 8 s | role="alert", región asertiva |
loading | hasta el fin de la operación | role="status", región cortés |
En el toast el rojo es el tipo error. En las clases del aviso y de la etiqueta, el mismo tono se llama
is-danger.
Cómo usar
No hay nada que marcar en el HTML: el toast se llama cuando algo pasa. El contenedor de cada posición se crea la primera vez que se usa.
Tucano.toast('Guardado'); // info
Tucano.toast.success('Registro guardado');
Tucano.toast.error('No se pudo guardar', { title: 'Error 500' });
Tucano.toast({
type: 'success',
title: 'Contrato eliminado',
text: 'Todavía se puede deshacer.',
action: { text: 'Deshacer', onClick: (t) => restore() },
position: 'bottom-end',
duration: null, // no se cierra solo
closable: true, // la X
});Los atajos info, success, warning, error y
loading reciben el texto y, después, cualquier otra opción. La acción es un botón del sistema: llama a
onClick con el toast y se cierra. Todo devuelve la instancia, que es lo mismo que new Tucano.Toast({ ... })
— este recibe solo las opciones, sin objetivo.
const t = Tucano.toast('Exportando...');
t.close();
// El evento sale de la propia tarjeta después de que deja la pantalla; no se propaga.
t.node.addEventListener('tucano:toast-closed', () => console.log('cerrado'));Cargando y promesa
El toast de loading se convierte en el resultado en la misma tarjeta, en lugar de cerrar uno y abrir otro:
la pila no se reorganiza y la vista no pierde el aviso que ya estaba leyendo.
// Control manual: guarda la instancia y cambia lo que necesites.
const t = Tucano.toast.loading('Guardando contrato...');
await save();
t.update({ type: 'success', text: 'Contrato guardado' });
// O entrega la promesa y deja los tres estados en sus manos.
Tucano.toast.promise(fetch(url), {
loading: 'Enviando...',
success: (response) => `Enviado (${response.status})`,
error: 'No se pudo enviar',
position: 'top-center', // el resto vale para el toast
});update() cambia tipo, título, texto y acción sin recrear la tarjeta. Al cambiar de tipo sin
duration, vale la duración del tipo nuevo, y el reloj vuelve a empezar: sin eso el cargando, que no se cierra, se convertiría
en un "guardado" eterno en la pantalla.
En toast.promise(), success y error aceptan texto o una función que recibe el
resultado o el error. Sin ellos, se usan los textos por defecto de la biblioteca: "Carregando...", "Pronto" y "Algo deu errado" en portugués, o los que definas para toda la página con Tucano.setTexts({ toast: { loading, success, error } }). La función devuelve la misma
promesa que recibió, para no estorbar a quien ya encadenaba sobre ella — si se rechaza, sigue rechazada, y el error se
trata donde ya se trataría.
null quiere decir "no se cierra", y no "usa el valor predeterminado"
La duración predeterminada se resuelve con in, y no con ??. El null del cargando es un valor: con ?? caería en los 4 segundos predeterminados, y el toast desaparecería en medio de la operación.
Posiciones
Seis esquinas y centros, con bottom-end como predeterminada. Cada posición tiene su propia pila.
En el móvil, por debajo de 40rem, el toast ocupa el ancho de la pantalla y entra por el eje vertical, sea cual sea la posición: pegado a una esquina quedaba con márgenes distintos a cada lado, y deslizar en horizontal una tarjeta tan ancha como la pantalla es un recorrido demasiado largo para un aviso corto. El borde respeta el indicador de inicio del iPhone y el recorte de la cámara.
max limita los toasts abiertos en la misma posición, 4 por defecto; el que se pasa cierra el más antiguo. En la pila,
a partir del tercero desaparecen de la vista, porque una pila de diez no ayuda a nadie.
Mensajes de Django
El framework de messages se convierte en toast sin una línea de JavaScript. Pon el bucle una vez, en la
plantilla base: cada <div> se convierte en un toast y sale de la página.
{% for m in messages %}
<div data-tuc-toast data-type="{{ m.level_tag }}">{{ m }}</div>
{% endfor %}messages.success(request, "Contrato guardado")
return redirect("contracts")| Atributo | Para qué |
|---|---|
data-tuc-toast | Marca el elemento; su texto se convierte en el texto del toast |
data-type | El tipo. debug se convierte en info; info, success, warning y error pasan tal cual |
data-title | Título |
data-text | Texto, en lugar del contenido del elemento |
data-duration | Milisegundos; false no se cierra solo |
data-position | Una de las seis posiciones |
m.level_tag, y no m.tags, si usas extra_tags
Del data-type vale solo la primera palabra. El m.tags de Django escribe las extra_tags antes del nivel, y entonces la primera palabra deja de ser el tipo. Sin extra_tags, los dos dan lo mismo.
Por la cabecera de HTMX
En una respuesta de HTMX no hay redirect ni plantilla base para el bucle de mensajes. El servidor dispara el
toast por la cabecera HX-Trigger, y el script del CDN ya está escuchando.
import json
def save(request, pk):
...
return HttpResponse(headers={
"HX-Trigger": json.dumps({
"tucano:toast": {"type": "success", "text": "Contrato guardado"},
}),
})El detalle acepta cualquier opción del toast, o solo un texto: {"tucano:toast": "Guardado"}. HTMX dispara el
evento tucano:toast en el elemento de la petición, este sube hasta el <body>, y ahí se convierte en toast.
El mismo evento sirve desde cualquier otro código:
document.body.dispatchEvent(new CustomEvent('tucano:toast', {
detail: { type: 'success', text: 'Contrato guardado' },
}));Por el CDN, listenForEvents() se activa junto con la inicialización. Importando tucano desde
npm nada se ejecuta solo: llama a listenForEvents(), o importa tucano/auto.
Accesibilidad
El toast no roba el foco: avisa sin sacar a la persona de lo que estaba haciendo. Quien lo anuncia es el lector de pantalla.
La región se crea antes del mensaje
El aria-live está en el contenedor, y no en el toast. Una región viva tiene que existir en el DOM antes de que llegue el contenido; creada junto con el mensaje, el lector de pantalla no la anuncia. El error habla en una región assertive y el resto en una polite, y las dos comparten el mismo escenario: posicionadas cada una por su cuenta, se volverían dos pilas paralelas en la pantalla y el límite contaría el doble.
Cuando update() cambia un toast a error, o de error a otro tipo, este cambia de
región, y eso es lo que hace que el lector de pantalla anuncie el cambio. En la pantalla nada se mueve, porque la posición viene del escenario.
El reloj se pausa cuando el puntero entra en el toast o algo dentro de él recibe el foco: nadie consigue leer algo que desaparece
mientras intenta hacer clic en "Deshacer". Por el mismo motivo la pila se abre en abanico también con el foco, y no solo con el puntero. La X
es un botón de verdad, con aria-label="Fechar" (la etiqueta por defecto de la biblioteca, en portugués hasta que la cambies con Tucano.setTexts({ toast: { close } })). Con prefers-reduced-motion, entrar y salir se reducen
a un fundido.
API
Generada a partir del código en cada build — si algo no está aquí, no existe.
[data-tuc-toast]new Tucano.Toast(opcoes) Tucano.toast() Tucano.listenForEvents()data-duration data-position data-text data-title data-typeupdate closetucano:toast-closedOpciones
Las notas de esta tabla salen de los comentarios del código fuente, que están escritos en portugués.
| Opción | Por defecto | Para qué |
|---|---|---|
type | 'info' | 'info' | 'success' | 'warning' | 'error' | 'loading' |
title | null | |
text | '' | |
duration | undefined | ms. null nao fecha sozinho. Padrao depende do tipo |
position | 'bottom-end' | top-start|top-center|top-end|bottom-start|bottom-center|bottom-end |
closable | true | |
action | null | { text, onClick } |
max | 4 | toasts simultaneos na mesma posicao |