Marca e interfaz
El nombre, el logotipo, los colores, las fuentes y los componentes de formulario con los que se construye cada pantalla.
Cada pantalla del kit se construye con las mismas pocas cosas: un nombre, dos archivos de imagen, siete tokens de color, una pila de fuentes y un conjunto de componentes Blade. Cámbialos una vez y toda la interfaz los sigue, panel de administración y correos incluidos. Esta guía dice dónde vive cada uno, qué lo lee y qué componente usar cuando construyas un formulario propio.
Nombre, logotipo e icono
| Qué | Dónde se define | Valor por omisión | Dónde aparece |
|---|---|---|---|
| Nombre | APP_NAME en .env |
Laravel |
Título del navegador, textos alt de imágenes, el menú y el pie públicos, el nombre de la PWA, los correos |
| Logotipo | logo en config/app.php |
images/logo.svg |
El encabezado de cada correo, el panel lateral móvil del área con sesión |
| Icono | icon en config/app.php |
images/icon.svg |
El favicon, el menú lateral de escritorio y la barra superior del área con sesión, cada pantalla de inicio de sesión, la imagen por omisión de una notificación, la base de los iconos de la PWA |
Ambas rutas son relativas a public/, así que el cambio más rápido es reemplazar los dos archivos y conservar las claves. SVG es lo que viene; cualquier formato que un navegador renderice funciona. El logotipo se dibuja a unos 36 píxeles de alto y el icono entre 36 y 96, así que dales a ambos fondo transparente y suficiente contraste sobre blanco.
La landing pública muestra el nombre como texto, no el logotipo. Su pie imprime un bloque "Desarrollado por Weblabor" desde resources/views/layouts/web.blade.php, que es un archivo compartido: editarlo funciona, y vas a resolver esa edición otra vez en cada merge de la base.
Los iconos de la PWA
Cuando APP_PWA=true el navegador pide además un juego de iconos PNG y pantallas de inicio. config/laravelpwa.php los lista bajo public/images/icons/, junto con el theme_color y el background_color que usa la app instalada. Un comando genera todos los archivos a partir de una sola imagen:
php artisan pwa:generate-assets
Pregunta por la imagen de origen, con la ruta de config/app.php como valor por omisión, y por dos colores hexadecimales para el degradado de las pantallas de inicio. El origen debe ser cuadrado y de al menos 512 por 512 píxeles, en un formato que la biblioteca GD lea, así que dale un PNG y no el icono SVG. El resto del manifiesto está en Web push y la PWA.
Colores
Las vistas nunca nombran una paleta de Tailwind. Usan tokens semánticos, y cada token se asigna a una paleta una sola vez, en resources/css/app.css, dentro del bloque @theme inline. El proyecto corre Tailwind 4, así que no hay tailwind.config.js: el archivo CSS es la configuración.
| Token | Paleta por omisión | Papel |
|---|---|---|
primary |
teal | Tu marca: botones, enlaces, estados activos, la barra de progreso |
secondary |
gray | Controles apagados y botones secundarios |
default |
slate | Texto, bordes y superficies de las páginas públicas |
positive |
emerald | Éxito: insignias de verificado, confirmaciones |
negative |
red | Errores, acciones destructivas, mensajes de validación |
warning |
amber | Advertencias: el banner de recordatorio del PIN, una guía sin traducir |
info |
blue | Información neutral |
Cada token tiene los once tonos de 50 a 950, así que bg-primary-600, text-negative-600 o border-warning-400 existen todos. primary tiene además un valor sin tono, --color-primary, que usan clases como bg-primary y text-primary y la barra de progreso de carga de página.
Para cambiar la marca, edita la asignación y vuelve a compilar:
--color-primary: var(--color-indigo-700);
--color-primary-50: var(--color-indigo-50);
--color-primary-100: var(--color-indigo-100);
/* ... hasta 950 */
npm run build
Cualquier paleta de Tailwind funciona del lado derecho, y también un color literal como #0f766e cuando la marca no tiene paleta. Mantén el --color-primary sin tono alineado con el tono que quieres que usen los botones.
Cambiar la asignación cambia toda la interfaz porque nada más conoce la paleta. Los componentes de WireUI comparten los mismos nombres, primary, secondary, positive, negative, warning e info, y config/wireui.php fija primary como su color por omisión, así que <x-button positive> y una insignia bg-positive-600 toman los mismos tonos. El panel de administración, los correos y tus propias vistas heredan el cambio sin edición propia.
La interfaz tiene un solo tema claro. Hay una variante dark declarada en el CSS, pero ningún interruptor agrega la clase dark a la página, así que las clases dark: no se usan en ningún lado y no necesitas escribirlas. El área con sesión descansa sobre #fafafe; las páginas públicas sobre blanco.
Fuentes e iconos
--font-sans en resources/css/app.css nombra Inter primero y luego las fuentes del sistema. Ninguna hoja de estilos carga Inter, así que el navegador usa la fuente del sistema hasta que agregues la fuente tú, ya sea con un <link> en el <head> de resources/views/layouts/base.blade.php o con un @font-face alojado por ti en app.css.
Los iconos vienen de dos fuentes. En todo el área con sesión y el panel de administración, <x-icon name="user" /> dibuja un Heroicon a través de WireUI, con variant="solid", mini y class como atributos habituales. La landing pública carga además Material Symbols Outlined y los dibuja como <span class="material-symbols-outlined">bolt</span>.
Componentes de formulario
Los formularios se escriben con componentes, nunca con elementos <input> crudos, para que cada campo tenga la misma etiqueta, mensaje de error, anillo de foco y enlace con Livewire. Existen dos familias: los componentes de WireUI sobre los que está construido el kit, y los componentes que el kit agrega donde WireUI no tiene respuesta.
De WireUI
wireui/wireui está instalado sin prefijo, así que sus componentes se llaman como <x-input>. config/wireui.php guarda sus valores por omisión: una sombra base, esquinas redondeadas medianas y primary como color. Los que más vas a usar:
| Componente | Propósito | Atributos principales | Ejemplo |
|---|---|---|---|
<x-input> |
Campos de texto y número | label, placeholder, type, hint, icon, prefix, suffix |
<x-input :label="__('Name')" wire:model="name" /> |
<x-password> |
Contraseña con botón de mostrar u ocultar | label, autocomplete |
<x-password :label="__('Password')" wire:model="password" /> |
<x-select> |
Lista desplegable con búsqueda | label, options, option-key-value, option-label, option-value, multiselect, clearable |
<x-select :label="__('Plan')" wire:model="plan" :options="$plans" option-key-value /> |
<x-native-select> |
<select> simple |
label, options |
<x-native-select :label="__('Size')" :options="['S', 'M']" wire:model="size" /> |
<x-textarea> |
Texto de varias líneas | label, rows |
<x-textarea :label="__('Notes')" wire:model="notes" /> |
<x-checkbox>, <x-toggle>, <x-radio> |
Booleanos y opciones | label, value, left-label |
<x-toggle :label="__('Send me email')" wire:model="send_mail" /> |
<x-datetime-picker> |
Calendario con hora | ver <x-date-input> abajo |
prefiere <x-date-input> |
<x-phone> |
Número telefónico con máscara por país | label, placeholder |
prefiere <x-phone-input> |
<x-button> |
Botones y enlaces | label, primary, positive, negative, flat, outline, icon, href, spinner, full, lg |
<x-button type="submit" primary :label="__('Save')" /> |
<x-card> |
Sección en caja con título y slot de pie | title |
<x-card :title="__('Deployment')">...</x-card> |
<x-alert>, <x-badge> |
Mensajes y etiquetas | title, info, positive, negative, warning |
<x-alert :title="__('Saved')" positive /> |
<x-icon> |
Heroicon | name, variant, mini |
<x-icon name="check-circle" class="h-4 w-4" /> |
<x-notifications /> y <x-dialog /> se colocan una sola vez, en el layout base, y reciben lo que un componente manda con $this->notification()->success(...) o $this->dialog()->confirm([...]) después de use WireUi\Traits\WireUiActions;. Los modales se abren a través del paquete wire-elements/modal con $this->dispatch('openModal', 'component.name', [...]).
Del kit
Éstos viven en resources/views/components/, con su clase PHP en app/View/Components/ y, donde necesitan estado, un subcomponente Livewire en app/Livewire/Shared/Inputs/. Cada uno funciona sólo con wire:model; todos los demás atributos son opcionales.
| Componente | Propósito | Atributos principales | Ejemplo |
|---|---|---|---|
<x-date-input> |
Fecha, hora o ambas, guardadas en UTC y mostradas en la zona horaria del usuario | label; type (date, time, datetime-local para un campo nativo, omitido para el calendario de WireUI); min, max, step; para el calendario también without-time, interval, time-format, clearable, disable-past-dates, parse-format, display-format |
<x-date-input :label="__('Birth date')" wire:model="user.birth_date" type="date" /> |
<x-email-input> |
Correo con insignia de Verificado / No verificado y un enlace Verificar ahora que abre el modal de OTP | label, placeholder |
<x-email-input :label="__('Email')" wire:model.live="email" /> |
<x-phone-input> |
Teléfono con máscara por país, la misma insignia y el enlace de verificar cuando se exige | label, placeholder, validation-required, verified-at, dispatch-context |
<x-phone-input :label="__('Phone')" wire:model.live="phone" :validation-required="true" :verified-at="$user->phone_verified_at" /> |
<x-domain-input> |
Un nombre de host y su TLD en dos campos, armados en un solo valor | label, name, placeholder; wire:model o value |
<x-domain-input :label="__('Website')" wire:model="website" name="website" /> |
<x-categories> |
Una o varias categorías de la tabla de categorías | type (limita las opciones a un tipo de categoría), is-multiple, show-label, initial-value |
<x-categories wire:model="category_ids" type="posts" :is-multiple="true" /> |
<x-country-select>, <x-division-select> |
País, luego estado, luego ciudad, del paquete weblabormx/world-ui |
label, placeholder; id de la división padre en <x-division-select> |
<x-division-select wire:model.live="state" :id="$country" /> |
<x-audio-player> |
Reproductor con forma de onda, barra de avance, velocidad y volumen | audio (URL; no renderiza nada cuando está vacío), label, compact (sólo botón de reproducir, tiempos en un tooltip) |
<x-audio-player audio="https://your-project.test/voice.mp3" :label="__('Voice message')" /> |
Los campos verificados disparan un evento Livewire identity-verified cuando el OTP tiene éxito; el de correo lo hace con su columna de wire:model como contexto, el de teléfono con dispatch-context cuando se le da. El flujo, los proveedores de SMS y correo y la carga del evento están en Verifica correo y teléfono.
<x-date-input> usa <x-datetime-picker> cuando se omite type y un <x-input type="date"> nativo en cualquier otro caso. Ambos leen y escriben el valor por el mismo enlace, así que la propiedad Livewire recibe una cadena que el cast datetime del modelo convierte a UTC. No mezcles un <input type="date"> crudo: es lo que el componente existe para reemplazar.
<x-audio-player.play-button size="md" /> y <x-audio-player.volume-icon /> pueden usarse por su cuenta dentro de un elemento que tenga x-data="audioPlayer(url)".
Componentes de layout
| Componente | Qué hace |
|---|---|
<x-language-switcher /> |
Los enlaces EN / ES de los idiomas de config/app.php. Se renderiza sólo para visitantes: una cuenta con sesión lee su idioma de su perfil |
<x-loading-overlay /> |
El spinner de pantalla completa, colocado una vez en el layout base; ver la siguiente sección |
<x-system-warnings /> |
El banner de advertencia del área con sesión: hoy le recuerda a un usuario sin PIN que configure uno cuando el PIN está activo |
<x-sidebar-menu :items :groups /> |
El menú lateral de las áreas de administración y cuenta: items son enlaces de primer nivel (name, url, icon, exact, show, order) y groups secciones plegables que llenan los recursos de administración |
<x-landing.hero>, <x-landing.block>, <x-landing.cta> |
Los bloques de una página de característica: un título con una promesa, una imagen con un párrafo y un enlace opcional a una guía, y la llamada a la acción de cierre |
Estados de carga
Una capa de pantalla completa en <x-loading-overlay /> cubre la página mientras algo está en curso, controlada por resources/js/app.js. Aparece por sí sola durante la navegación entre páginas y durante cualquier formulario enviado con wire:submit. Para una acción fuera de un formulario, agrega el atributo spinner al elemento que dispara la petición:
<x-button wire:click="confirmMigration" spinner :label="__('Confirm')" />
Para una subida de archivo el atributo va en el propio <input type="file">, no en el botón que abre el selector, porque la petición empieza cuando el campo cambia.
No agregues spinner a campos wire:model.live, filtros, cajas de búsqueda ni cambios de pestaña: eso debe sentirse instantáneo y nunca bloquear la pantalla. Si la capa se queda más de tres segundos muestra un botón Recargar, para que una pantalla nunca se quede atorada sin salida.
Escribe tu propio campo
Antes de escribir uno, revisa la tabla de arriba. Cuando ninguno encaje, conserva el mismo contrato: wire:model es el único atributo obligatorio y todo lo demás tiene un valor por omisión, para que quien lo use nunca arme formatos, zonas horarias u opciones a mano. Un componente sin estado en el servidor es un solo archivo Blade que envuelve <x-input {{ $attributes }} />. Un componente que consulta cosas o guarda estado sigue a <x-date-input>: un archivo Blade en resources/views/components/, una clase en app/View/Components/ que resuelve el valor actual desde la ruta de wire:model, y un subcomponente Livewire en app/Livewire/Shared/Inputs/ que renderiza el campo y escribe de regreso por el enlace. Usa dentro los tokens semánticos de color y seguirá cada cambio de marca junto con el resto.