Desenvolupem la teva web presencial SPA DES DE 300€. Si, es una bogeria. Web SPA DES DE 300€ — Si, es una bogeria. Parlem →

Modals i notificacions sense Bootstrap ni Tailwind, amb CSS pur

Modals i notificacions sense Bootstrap ni Tailwind, amb CSS pur

Pintar un diàleg i un avís emergent és de les coses que més ràpid es resolen portant un framework sencer, i de les que pitjor es paguen després. Aquí veuràs què cal de debò perquè un modal funcioni bé, on és la feina real, que no és l'aspecte, i com es resol tot amb variables CSS i un grapat de línies de JavaScript, sense lligar-te a cap sistema de disseny. El CSS i el JavaScript són estàndard i funcionen a qualsevol lloc; només la integració opcional del final va en Laravel, un dels stacks amb què treballem a AndorraDev.

Un modal són tres coses i cap no necessita un framework

Quan algú necessita un diàleg i un avís emergent, la reacció habitual és portar-se una llibreria que a sobre arrossega el seu propi sistema de disseny. I el que cal de veritat és bastant menys.

Un modal és una capa que tapa, un panell que apareix i un focus que es queda a dins. Un avís és un element que entra, espera i se'n va. Cap d'aquestes quatre coses exigeix un framework CSS: exigeixen unes quantes regles i una mica de disciplina.

El problema de resoldre-ho amb el que ja portes posat és que et lliga al framework. Si vas amb Bootstrap, el seu modal ve de franc i la seva estètica també, i treure'l d'aquí és reescriure'l. Si vas amb Tailwind, carregar Bootstrap només pel diàleg són dos sistemes de disseny convivint a la mateixa pàgina, amb els seus dos resets barallant-se.

El JavaScript d'un modal cap en dues-centes línies

Això sorprèn fins que ho expliques. El sistema complet de modals i avisos que mantenim són 363 línies de JavaScript, 157 per als avisos i 206 per als diàlegs, sense ni una sola dependència de npm. Els dos fitxers són funcions autoexecutades, sense imports, sense build obligatori.

Hi cap perquè la major part de la feina no és JavaScript. És CSS.

Tot l'estat viu en un atribut, i el CSS fa la resta

La majoria d'implementacions casolanes acaben amb el JavaScript escrivint estils directament, i és aquí on es tornen impossibles de tematitzar. Hi ha una alternativa que costa el mateix d'escriure: que el JavaScript només declari en quin estat és el diàleg i deixi l'aspecte sencer al CSS.

modal.setAttribute('data-wm-state', state);
modal.setAttribute('aria-hidden', state === 'closed' ? 'true' : 'false');

L'opacitat, la visibilitat, la transformació d'entrada, el desplaçament intern i el comportament al mòbil els resolen selectors del tipus [data-wm-state="open"]. Això té tres conseqüències pràctiques.

Pots canviar l'animació sense tocar el JavaScript. És una regla CSS més.

L'estat és inspeccionable. Obres les eines del navegador i veus si el diàleg està obert sense posar un punt de ruptura.

I al mòbil no cal lògica. Per sota de 640px el panell passa a pantalla completa amb una media query, no amb una comprovació d'amplada en JavaScript.

Andie recomana

L'embolcall del modal ja porta wire:ignore.self, que és el que impedeix que un redibuixat de Livewire trepitgi l'atribut d'estat mentre l'usuari escriu en un camp. És un detall petit i és la diferència entre un diàleg que aguanta un formulari reactiu i un que no. Si algun dia construeixes el teu a mà, aquesta és la línia que no pots oblidar.

El focus és la part que gairebé ningú implementa

Un diàleg accessible ha de fer tres coses amb el teclat, i la majoria d'implementacions casolanes en fan zero.

Atrapar el tabulador dins del panell, perquè no te'n vagis navegant per la pàgina del darrere. Retornar el focus a l'element que el va obrir quan es tanca. I respectar un autofocus explícit si n'hi ha, en comptes de saltar sempre al primer element enfocable.

Les tres estan resoltes: la trampa de focus intercepta Tab i Shift+Tab sobre el modal més alt de la pila, i l'element d'origen es desa en un WeakMap per restaurar-lo en tancar. És la mena de detall que no es veu en una demo i es nota quan algú navega sense ratolí.

Els missatges s'injecten amb textContent, no amb innerHTML

Un avís emergent mostra text que sovint ve de fora: el nom d'un fitxer, un missatge d'error, una cosa que va escriure un usuari. N'hi ha prou amb una línia per decidir si allò es pinta com a text o s'executa com a HTML:

if (title) toast.querySelector('.wt-title').textContent = title;
toast.querySelector('.wt-body').textContent = message;

Això vol dir que no pots posar negreta en un avís, i és intencionat. A canvi, un missatge que vingui de dades de l'usuari no pot executar res. En un sistema on els avisos solen mostrar missatges de validació construïts amb el que l'usuari ha escrit, aquesta decisió estalvia un problema sencer.

Les icones també van a dins, com a constants: no hi ha dependència d'una llibreria d'icones ni una petició extra per mostrar un avís.

Un sol atribut alinea tota la interfície

La part que més rendiment dona en el dia a dia és el sistema de temes en dues capes.

Hi ha meta-variables compartides per tota la família de components, del tipus --wire-bg, --wire-text, --wire-accent, --wire-radius, --wire-font. I després cada component defineix les seves derivant-les:

:root {
    --wire-accent: #2563eb;
    --wire-radius: 10px;
}

.wt-container {
    --wt-radius: var(--wire-radius);
    --wt-bg: var(--wire-bg-elevated);
}

El resultat pràctic és que escrius data-wire-theme="studio" a l'etiqueta <html> i els diàlegs, els avisos, el bàner de galetes i el widget d'incidències s'alineen sols. I per posar-hi la teva marca no cal sobreescriure selectors barallant-se amb l'especificitat: redefineixes mitja dotzena de variables.

Hi ha onze temes inclosos, però l'important no és el catàleg. És que la superfície de personalització siguin variables CSS i no classes, perquè això és el que fa que puguis canviar l'aspecte sense tocar el paquet ni bloquejar l'actualització.

Livewire opcional vol dir comprovar la classe abans de registrar res

Declarar una dependència com a opcional al composer.json és només la meitat de la feina. L'altra meitat és que el codi es comporti de debò com si pogués no ser-hi, i és aquí on fallen força paquets que s'anuncien com a agnòstics.

Livewire no és a les dependències obligatòries, hi és com a suggeriment. I el proveïdor de serveis comprova que la classe existeixi abans de registrar la drecera:

if (! class_exists(\Livewire\Component::class)) {
    return;
}

\Livewire\Component::macro('notify', function (...) { ... });

Amb Livewire escrius $this->notify('Guardado', 'success') des del component PHP. Sense Livewire, crides window.notify(...) o dispares el mateix esdeveniment des d'Alpine, i el paquet funciona igual. La macro no fa màgia: tradueix a un despatx d'esdeveniment que el JavaScript escolta a window. Aquest és tot l'acoblament.

Què hi ha al mercat i en què es diferencia

Convé ser exacte amb els compromisos de cada opció, perquè totes són raonables en el seu context.

El modal de Bootstrap és sòlid i està provat en milions de llocs. Si ja carregues Bootstrap sencer, no té sentit afegir-hi res. El compromís apareix quan no el carregues: portar-lo pel diàleg són el seu JavaScript i el seu reset d'estils complets, i el seu aspecte és recognoscible, així que personalitzar-lo a fons és feina.

SweetAlert2 fa molt més que un diàleg i ho fa bé. A canvi imposa el seu propi llenguatge visual i la seva personalització passa per barallar-se amb les seves classes i les seves opcions. És l'elecció natural si vols diàlegs amb molta funcionalitat de sèrie i no t'importa que es noti d'on venen.

Toastr cobreix els avisos i fa anys que funciona, però depèn de jQuery, que avui és una dependència que molts projectes ja no tenen. Notyf no depèn de res i és minúscul, i la seva superfície de personalització es limita a un grapat d'opcions.

La diferència d'enfocament que a nosaltres ens val és una altra: els avisos i els diàlegs comparteixen sistema de variables, així que s'estilen una vegada i queden coherents, i el mateix interruptor de tema arrossega també el bàner de galetes i el widget d'incidències. Quan el producte té set plantilles diferents, això és la diferència entre un canvi i set.

Com es veu en producció

A Crowd Legal hi ha 267 crides als avisos repartides en 58 fitxers i 24 diàlegs en 24 vistes, tots sota el mateix tema declarat una vegada a l'etiqueta <html>. A Abodara són 636 crides en 173 fitxers.

I una dada que respon la pregunta òbvia: a Crowd Legal el CSS d'aquests components s'importa abans de les directives de Tailwind, al mateix fitxer. Conviuen sense col·lisionar perquè cadascun fa servir el seu prefix de classe. Que siguin independents del framework no vol dir que no els puguis fer servir al costat d'un.

I si no treballes amb Laravel

Els criteris es traslladen, i serveixen per jutjar qualsevol llibreria de diàlegs que estiguis valorant:

  • Que l'estat visqui en un atribut i l'aspecte en CSS. Si la llibreria escriu estils en línia des de JavaScript, personalitzar-la serà una baralla.
  • Que la personalització sigui per variables, no per classes. Sobreescriure selectors aliens es trenca a cada actualització.
  • Que atrapi el focus i el retorni. Sense això el diàleg no és accessible, per bonic que sigui.
  • I que el contingut s'injecti com a text. Si la teva llibreria d'avisos accepta HTML per defecte, ja tens un vector obert tan bon punt un missatge inclogui dades de l'usuari.

On és el codi

Els dos paquets són a Packagist (wiretoast, wiremodal), el codi a GitHub (wiretoast, wiremodal) i la documentació dels paquets és a wiretoast i wiremodal. Hi ha una demo interactiva per veure els temes sense instal·lar res. Són MIT.

Si estàs muntant una aplicació i no vols que la interfície et lligui a un framework d'estils, aquesta és una de les decisions que surt més barata prendre bé al principi. La treballem a disseny web i aplicacions web, i si ens expliques el projecte et diem què hi encaixa.

Escrit per
Edu Lazaro
Edu Lazaro
Founder & Lead Developer en AndorraDev

Desenvolupador full-stack amb més de 15 anys d'experiència en Laravel, React, Node.js i arquitectures cloud. Ajudo empreses a Andorra a construir la seva presència digital.

Partner de diseño · ionospace.
Necessites ajuda? ×
Andie by AndorraDev
Assistent IA + equip humà
Assistent IA d'AndorraDev
Andie
Hola! Soc Andie, l'assistent IA d'AndorraDev. En què et puc ajudar? Si necessites parlar amb Edu, només demana-ho.
17:27