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, ocho tokens de color, una pila de fuentes, un conjunto de componentes de diseño y un conjunto de componentes de formulario. 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 una pantalla propia.
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, el encabezado del centro de ayuda |
| 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.
Una carpeta de diseño también puede traer su propia marca, igual que trae su propia paleta: resources/brand/tu-proyecto.php devuelve logo, icon y favicon (rutas dentro de public/), logo_on_dark cuando su menú lateral es oscuro, e installed_app (folder, theme_color, background_color) para los iconos, las pantallas de arranque y los colores de la app instalada. Se busca en la misma cadena que los componentes de diseño, y el genérico resources/brand/project.php responde con las dos claves de arriba, así que un producto sin archivo propio no ve ningún cambio. El de Weblabor Base, resources/brand/weblabor-base.php, es el ejemplo, y ningún otro producto lo lee.
La landing pública muestra el nombre como texto, no el logotipo. Su pie imprime un bloque "Desarrollado por Weblabor" desde el componente de diseño public-footer. Para cambiarlo, agrega tu propio public-footer.blade.php a tu carpeta de diseño, como explica "Componentes de diseño" más abajo, y ningún merge de la base lo toca.
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, declarados 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: los archivos CSS son la configuración.
| Token | Paleta por omisión | Papel |
|---|---|---|
primary |
teal | Tu marca: botones, enlaces, estados activos, la barra de progreso |
accent |
la misma que primary |
Un segundo color de marca para la landing, nunca el color de una acción |
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 y accent tienen además un valor sin tono, --color-primary y --color-accent, que usan clases como bg-primary y text-primary y la barra de progreso de carga de página.
Tu paleta y tu fuente
primary, accent y la fuente son tu marca, así que siguen la misma regla que los componentes de diseño: cada proyecto tiene los suyos, y los genéricos son el respaldo. app.css no nombra sus colores; los lee de variables --brand-*:
resources/css/themes/project.cssguarda los genéricos, la paleta teal e Inter, yapp.csssiempre lo importa.resources/css/themes/tu-proyecto.css, cuando existe, se carga después en cada página, así que lo que define gana. Se busca por la misma cadena que los componentes de diseño,design_layersenconfig/app.phpo si no tu proyecto, así que un producto construido sobre otro hereda la paleta de ése hasta que escribe la suya.
Para cambiar la marca, crea tu archivo con las variables que cambias y vuelve a compilar:
:root {
--brand-primary: var(--color-indigo-700);
--brand-primary-50: var(--color-indigo-50);
--brand-primary-100: var(--color-indigo-100);
/* ... hasta 950, y --brand-accent-* cuando tienes un segundo color */
--brand-font: "Tu Fuente", ui-sans-serif, system-ui, sans-serif;
}
npm run build
vite.config.js compila por separado cada archivo de resources/css/themes/, así que uno nuevo no necesita ninguna otra edición. 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 --brand-primary sin tono alineado con el tono que quieres que usen los botones. Una fuente que no está instalada en el dispositivo del lector hay que cargarla: un @import url(...) de su hoja de estilos al principio de tu archivo lo hace. El archivo propio de Weblabor Base, themes/weblabor-base.css, es un ejemplo de todo esto, y ningún otro producto lo carga.
No edites themes/project.css para cambiar la marca: es el aspecto genérico que cada producto fusiona desde la base. Los tokens semánticos, positive, negative, warning e info, son los mismos para todos los productos y se quedan en app.css.
Cambiar tu paleta cambia toda la interfaz porque nada más la conoce. 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 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.
Componentes de diseño
Los colores dicen qué tonos usa una pantalla; los componentes de diseño dicen cómo se acomoda. El marco de cada pantalla y las piezas que las pantallas repiten son componentes Blade que se llaman como <x-design::nombre>, y viven en resources/views/components/project/:
| Componente | Qué dibuja |
|---|---|
auth-page, auth-link |
Las pantallas de inicio de sesión, registro, contraseña y verificación y la de página no encontrada: logotipo, título, subtítulo, los enlaces debajo, la tarjeta y el pie |
form, submit-button, remember-row, identity-option, notice |
La pila de campos de un formulario, su botón grande de envío, "Recordarme" junto a "¿Olvidaste tu contraseña?", la elección de correo, teléfono o usuario al registrarse, y un mensaje en caja |
page-header |
El título de una pantalla, una línea opcional debajo y sus acciones |
section-header |
El encabezado centrado sobre una sección: una línea pequeña de color, un título grande y una línea opcional debajo |
landing-hero, landing-section-title, feature-card, landing-block, landing-cta |
La landing pública y las páginas de características: el título grande con su promesa, el encabezado de una sección de la landing, una característica con su icono, una imagen junto a un párrafo con un enlace opcional a una guía, y la llamada a la acción de cierre |
back-link, help-title, help-card, changelog-day |
El centro de ayuda: el regreso arriba de una página, el título de una página con la línea debajo, la tarjeta que lleva a una guía o a una categoría, y un día del registro de cambios |
legal-page, legal-section |
Las páginas de privacidad y términos: el título con la fecha de la última actualización, y una sección con su subtítulo y sus listas |
surface |
La tarjeta blanca sobre la que va una sección, con o sin relleno |
option-card |
Una forma de conseguir algo, en una caja bajo su título con su botón |
empty-state |
Lo que muestra una lista cuando no tiene nada, con un icono y una acción opcional |
usage-card, usage-bar |
Cuánto se usa de una asignación: normal, cerca del límite, sobre el límite y seleccionada |
table, section-label |
Una tabla con borde y su fila de encabezado, y la etiqueta pequeña en mayúsculas sobre una cifra |
app-topbar, user-menu, user-menu-link, menu-divider, page-container |
La barra superior, el menú de la cuenta y el ancho del contenido del área con sesión |
topbar-search, drawer-close, breadcrumbs |
El buscador de la barra superior, el botón que cierra el menú lateral en el teléfono, y la ruta desde el inicio de la sección hasta la pantalla actual |
plan-box, sidebar-card |
La caja del menú lateral con el plan en uso y su enlace para mejorarlo, y el enlace en caja que lleva a otra área |
sidebar-panel, sidebar-brand, sidebar-group-toggle |
La superficie sobre la que va el menú lateral, el logotipo o icono en su parte superior, y el encabezado que pliega un grupo de sus enlaces |
dropdown-panel, count-badge, notification-item |
El panel que abre un botón de la barra superior, el contador en su esquina, y una notificación en una lista |
stat-card, trend-card |
Una cifra en su propia tarjeta, y una con icono y cuánto cambió |
tag, status-tag |
Una etiqueta corta sobre un registro, y una que dice un estado: hecho, fallido, requiere atención, en progreso |
modal-header, modal-footer |
El título de un modal y la franja que sostiene sus botones |
table-action, field-error, danger-row |
Un botón de icono en una fila de tabla, el mensaje debajo de un campo, y una fila de zona de peligro con su botón |
folder-tile, progress-bar |
Una carpeta en la cuadrícula de medios, y cuánto lleva una subida |
text-button, resend-code, pin-input |
Una acción mostrada como texto, la cuenta atrás antes de poder pedir un código nuevo, y las cuatro casillas de un PIN |
public-header, public-footer, help-header |
La parte de arriba y de abajo de las páginas públicas y la de arriba del centro de ayuda |
Un proyecto cambia cualquiera de ellos agregando un archivo con el mismo nombre a resources/views/components/tu-proyecto/: esa carpeta se busca primero y project es el respaldo, así que lo que no redefines conserva el aspecto genérico. La cadena, y cómo la lista un producto construido sobre otro, está en Hazlo tuyo. Conserva los atributos y los slots del componente que reemplazas, y usa dentro los tokens de color, para que siga a un cambio de marca. Un superadministrador ve cada uno en uso, en cada uno de sus estados, en /admin/design.
Weblabor Base hace exactamente esto para su propio aspecto, el de su manual de interfaz: su encabezado y su pie públicos y sus componentes de landing están redefinidos en resources/views/components/weblabor-base/, que además guarda landing-button, product-preview, icon-strip e icon-strip-item, componentes que sólo tiene la base. Tu proyecto no lee esa carpeta salvo que liste weblabor-base en su cadena.
Los componentes de WireUI de abajo, el botón, el campo, la tarjeta, la alerta y el modal, no son componentes de diseño: su aspecto sale de los tokens de color y de config/wireui.php.

Fuentes e iconos
--font-sans lee --brand-font, que el genérico resources/css/themes/project.css define con Inter primero y luego las fuentes del sistema. El archivo genérico no carga Inter, así que el navegador usa la fuente del sistema hasta que agregues la fuente tú, con un @import url(...) o un @font-face alojado por ti en tu propio archivo de tema, como explica "Tu paleta y tu fuente" arriba. El archivo propio de Weblabor Base carga Inter desde Google Fonts en los cuatro pesos que usa su manual.
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:
<x-input>
Campos de texto y número.
Atributos principales: 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.
Atributos principales: label, autocomplete.
<x-password :label="__('Password')" wire:model="password" />
<x-select>
Lista desplegable con búsqueda.
Atributos principales: 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.
Atributos principales: label, options.
<x-native-select :label="__('Size')" :options="['S', 'M']" wire:model="size" />
<x-textarea>
Texto de varias líneas.
Atributos principales: label, rows.
<x-textarea :label="__('Notes')" wire:model="notes" />
<x-checkbox>, <x-toggle>, <x-radio>
Booleanos y opciones.
Atributos principales: label, value, left-label.
<x-toggle :label="__('Send me email')" wire:model="send_mail" />
<x-datetime-picker>
Calendario con hora.
Atributos principales: ver <x-date-input> abajo.
Prefiere <x-date-input>.
<x-phone>
Número telefónico con máscara por país.
Atributos principales: label, placeholder.
Prefiere <x-phone-input>.
<x-button>
Botones y enlaces.
Atributos principales: 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.
Atributos principales: title.
<x-card :title="__('Deployment')">...</x-card>
<x-alert>, <x-badge>
Mensajes y etiquetas.
Atributos principales: title, info, positive, negative, warning.
<x-alert :title="__('Saved')" positive />
<x-icon>
Heroicon.
Atributos principales: 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.
<x-date-input>
Fecha, hora o ambas, guardadas en UTC y mostradas en la zona horaria del usuario.
Atributos principales: 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.
Atributos principales: 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.
Atributos principales: 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.
Atributos principales: 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.
Atributos principales: 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-file-uploader>, <x-image-uploader>
Archivos guardados en la biblioteca de medios. El archivo se sube en cuanto se elige, con una barra de progreso y Cancelar; el registro se relaciona con él solo cuando el formulario se guarda. <x-image-uploader> acepta solo imágenes y las muestra como miniaturas grandes.
Atributos principales: model (el registro), relation, folder, general, owner, accept, max-size en KB, multiple. El formulario recibe qué hacer con los archivos y lo aplica después de guardar el registro con saveMediaChanges().
<x-image-uploader wire:model="image" :model="$product" folder="Products" />
Cómo lo usa la foto de perfil está en Archivos, imágenes y almacenamiento; cada atributo y los inputs de Laravel Front están en las notas de la biblioteca de medios de la documentación para desarrolladores.
<x-country-select>, <x-division-select>
País, luego estado, luego ciudad, del paquete weblabormx/world-ui.
Atributos principales: 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.
Atributos principales: 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-design::loading-overlay /> |
El spinner de pantalla completa, colocado una vez en el layout base; ver la siguiente sección |
<x-design::system-warnings /> |
El banner de advertencia del área con sesión: le recuerda a un usuario sin PIN que configure uno cuando el PIN está activo y, mientras la facturación está activa, avisa de un pago fallido, un saldo pendiente o una cuenta sin plan. Un aviso puede llevar una línea de letra pequeña bajo su mensaje, con un enlace opcional |
<x-design::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 |
Estados de carga
Una capa de pantalla completa en <x-design::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')" />
Un campo de archivo hecho con <x-file-uploader> o <x-image-uploader> muestra su propia barra de progreso y no necesita spinner. Solo un campo de archivo escrito a mano lo necesita, y ahí 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 los componentes 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.