Personalización de temas en PrestaShop 8 con Tailwind CSS y Alpine.js
Introducción: La nueva era del frontend en PrestaShop 8
El ecosistema de PrestaShop ha evolucionado significativamente. Con la llegada de PrestaShop 8, basado en Symfony 5.4 y con un motor de temas más flexible, los desarrolladores tienen la oportunidad de modernizar el frontend de sus tiendas sin sacrificar rendimiento ni mantenibilidad. Tailwind CSS y Alpine.js se presentan como el dúo perfecto para reemplazar el clásico Bootstrap + jQuery, ofreciendo un enfoque utility-first y reactivo sin la pesadez de frameworks completos.
Este artículo es una guía técnica, paso a paso, para integrar Tailwind CSS PrestaShop y Alpine.js PrestaShop en tu tema personalizado, optimizando tanto la experiencia de desarrollo como la velocidad de carga del frontend eCommerce.
¿Por qué Tailwind CSS y Alpine.js para tu tema PrestaShop?
Antes de sumergirnos en la configuración, es crucial entender las ventajas de esta combinación frente a las soluciones tradicionales.
Ventajas de Tailwind CSS en PrestaShop
- Utility-first: No escribes CSS personalizado para cada componente. Construyes directamente en el HTML con clases predefinidas (
flex,pt-4,text-center,shadow-lg). Esto acelera el desarrollo y elimina la duplicación de estilos. - Tamaño de CSS mínimo: Al purgar las clases no utilizadas con PurgeCSS (integrado en Tailwind), el archivo final de producción suele pesar menos de 10 KB. Esto es crítico para el rendimiento de un frontend eCommerce.
- Consistencia de diseño: Utilizas un sistema de diseño (spacing, colores, tipografía) que se aplica de forma homogénea en todo el tema.
Ventajas de Alpine.js en PrestaShop
- Reactividad ligera: No necesitas Vue.js o React para añadir interactividad. Alpine.js (7 KB) te permite manejar dropdowns, modales, carritos dinámicos y filtros con directivas en el HTML (
x-data,x-show,x-model). - Sin build step complejo: Aunque se puede integrar con npm, Alpine.js también funciona mediante CDN, lo que facilita la adopción gradual en temas existentes.
- DOM virtual inteligente: Solo re-renderiza las partes del DOM que cambian, evitando recargas completas de la página. Ideal para actualizar el contador del carrito o el estado de un producto.
[TIP] Esta combinación es perfecta para tiendas que buscan un frontend rápido, moderno y fácil de mantener, sin la sobrecarga de un SPA completo.
Requisitos y configuración inicial del tema
Para seguir esta guía, asumiremos que tienes un tema base de PrestaShop 8 (puede ser el tema clásico o uno vacío personalizado). Necesitarás:
- Node.js 16+ y npm/yarn instalados en tu máquina de desarrollo.
- Un tema PrestaShop 8 con estructura de carpetas estándar (carpeta
_devpara assets). - Conocimientos básicos de la estructura de Smarty (plantillas
.tpl) y Webpack (o Vite, según prefieras).
Estructura recomendada de carpetas
Dentro de tu tema (themes/mi-tema), organiza los assets de la siguiente forma:
mi-tema/
├── _dev/
│ ├── css/
│ │ └── app.css # Punto de entrada para Tailwind
│ ├── js/
│ │ └── app.js # Punto de entrada para Alpine.js y JS personalizado
│ └── tailwind.config.js # Configuración de Tailwind
├── assets/
│ ├── css/
│ │ └── theme.css # CSS compilado (output)
│ └── js/
│ └── theme.js # JS compilado (output)
├── templates/
│ └── ... (plantillas Smarty)
└── config/
└── theme.yml
Instalación de dependencias
Navega a la carpeta _dev de tu tema y ejecuta:
cd themes/mi-tema/_dev
npm init -y
npm install tailwindcss alpinejs postcss autoprefixer cssnano
[INFO] Si prefieres usar Vite en lugar de Webpack, puedes instalar
viteyvite-plugin-laravel(adaptado para PrestaShop). La lógica de Tailwind es la misma.
Configuración de Tailwind CSS
Crea el archivo tailwind.config.js en _dev/:
module.exports = {
content: [
'../templates/**/*.tpl',
'../**/*.php',
'../assets/js/**/*.js',
'./node_modules/alpinejs/**/*.js',
],
theme: {
extend: {
colors: {
primary: '#2fb5d2',
secondary: '#ff6600',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
},
},
plugins: [],
};
[WARNING] La clave
contentes crítica. Debes incluir todas las rutas donde uses clases de Tailwind (plantillas TPL, archivos PHP, JS). Si no lo haces, Tailwind purgará clases que estás usando y se romperá el diseño.
Compilación con PostCSS
Crea un archivo postcss.config.js:
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
...(process.env.NODE_ENV === 'production' ? { cssnano: {} } : {}),
},
};
Y en tu package.json, añade los scripts:
"scripts": {
"dev": "postcss css/app.css -o ../assets/css/theme.css --watch",
"build": "NODE_ENV=production postcss css/app.css -o ../assets/css/theme.css"
}
El archivo app.css contendrá:
@tailwind base;
@tailwind components;
@tailwind utilities;
Ahora, ejecuta npm run dev para iniciar el watch. Cada cambio en las plantillas TPL se reflejará automáticamente en theme.css.
Integración de Alpine.js en el tema
Alpine.js se puede incluir de dos formas: mediante CDN (rápido pero menos control) o mediante npm (recomendado para producción y para usar con build tools).
Opción 1: CDN (rápida para pruebas)
En el archivo templates/_partials/head.tpl, añade justo antes de </head>:
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
Opción 2: npm + bundle (recomendado)
Crea el archivo _dev/js/app.js:
import Alpine from 'alpinejs';
window.Alpine = Alpine;
Alpine.start();
Luego, configura un bundler (por ejemplo, Webpack o Vite) que compile este JS a ../assets/js/theme.js. Un ejemplo simple con Webpack:
// webpack.config.js
module.exports = {
entry: './js/app.js',
output: {
path: path.resolve(__dirname, '../assets/js'),
filename: 'theme.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
],
},
};
Finalmente, carga el script en el footer de tu tema (templates/_partials/footer.tpl):
<script src="{$urls.theme_assets}js/theme.js"></script>
[TIP] Si usas Vite, el proceso es similar pero con recarga en caliente (HMR) para JS y CSS simultáneamente.
Personalización de componentes clave del frontend eCommerce
Con Tailwind y Alpine integrados, pasemos a ejemplos prácticos de personalización en temas PrestaShop.
1. Menú de navegación responsivo con dropdown
Reemplaza el menú clásico de Bootstrap por uno con Alpine.js. En templates/_partials/header.tpl:
<nav x-data="{ open: false, submenu: null }" class="bg-white shadow">
<div class="max-w-7xl mx-auto px-4">
<div class="flex justify-between h-16">
<!-- Logo -->
<div class="flex items-center">
<a href="{$urls.base_url}" class="text-xl font-bold text-primary">Mi Tienda</a>
</div>
<!-- Menú escritorio -->
<div class="hidden md:flex space-x-8 items-center">
{foreach from=$menu.items item=item}
{if $item.children|@count > 0}
<div x-on:mouseenter="submenu = {$item.id}" x-on:mouseleave="submenu = null" class="relative">
<a href="{$item.url}" class="text-gray-700 hover:text-primary px-3 py-2 text-sm font-medium">
{$item.title}
</a>
<div x-show="submenu === {$item.id}" x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 translate-y-1"
x-transition:enter-end="opacity-100 translate-y-0"
class="absolute z-10 left-0 mt-2 w-48 bg-white rounded-md shadow-lg">
{foreach from=$item.children item=child}
<a href="{$child.url}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
{$child.title}
</a>
{/foreach}
</div>
</div>
{else}
<a href="{$item.url}" class="text-gray-700 hover:text-primary px-3 py-2 text-sm font-medium">
{$item.title}
</a>
{/if}
{/foreach}
</div>
<!-- Botón menú móvil -->
<div class="md:hidden flex items-center">
<button @click="open = !open" class="text-gray-500 hover:text-gray-700">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
</svg>
</button>
</div>
</div>
<!-- Menú móvil -->
<div x-show="open" x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
class="md:hidden">
{* Aquí irían los items del menú móvil *}
</div>
</div>
</nav>
2. Carrito de compras dinámico con Alpine.js
El carrito en el header es uno de los elementos más críticos en frontend eCommerce. Con Alpine.js, podemos actualizarlo sin recargar la página.
En templates/_partials/header.tpl:
<div x-data="carrito()" class="relative">
<button @click="toggleCarrito()" class="flex items-center space-x-1 text-gray-700 hover:text-primary">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 100 4 2 2 0 000-4z"/>
</svg>
<span x-text="totalItems" class="bg-primary text-white rounded-full px-2 py-0.5 text-xs font-bold"></span>
</button>
<!-- Dropdown carrito -->
<div x-show="abierto" @click.away="abierto = false"
class="absolute right-0 mt-2 w-80 bg-white rounded-md shadow-xl z-50">
<template x-for="item in items" :key="item.id">
<div class="flex items-center p-3 border-b">
<div class="ml-3 flex-1">
<p class="text-sm font-medium" x-text="item.name"></p>
<p class="text-xs text-gray-500" x-text="`Cant: ${item.quantity}`"></p>
</div>
<p class="text-sm font-bold" x-text="`€${item.total}`"></p>
</div>
</template>
<div class="p-3">
<a href="{$urls.pages.cart}" class="block text-center bg-primary text-white py-2 rounded-md hover:bg-primary-dark">
Ver carrito
</a>
</div>
</div>
</div>
<script>
function carrito() {
return {
abierto: false,
items: [],
totalItems: 0,
async init() {
// Cargar carrito inicial desde la API de PrestaShop
const response = await fetch('/module/ps_shoppingcart/ajax');
const data = await response.json();
this.items = data.products;
this.totalItems = data.products.length;
},
toggleCarrito() {
this.abierto = !this.abierto;
},
};
}
</script>
[INFO] Para una integración más robusta, puedes usar el módulo
ps_shoppingcarty su endpoint AJAX. Alpine.js se encargará de actualizar el DOM cuando el usuario añada o elimine productos.
3. Filtros de producto interactivos (sin recarga)
En la página de categoría (templates/catalog/listing/product-list.tpl), podemos implementar filtros que se muestren/oculten con Alpine y actualicen la URL mediante History API:
<div x-data="{ filtrosAbiertos: false, precioMax: 100 }">
<button @click="filtrosAbiertos = !filtrosAbiertos" class="md:hidden bg-gray-200 px-4 py-2 rounded">
Filtros
</button>
<div x-show="filtrosAbiertos || window.innerWidth >= 768" class="space-y-4 mt-4">
<!-- Filtro por precio -->
<div>
<h3 class="font-semibold text-gray-700">Precio máximo</h3>
<input type="range" x-model="precioMax" min="0" max="500" class="w-full"
@input="filtrarProductos(precioMax)">
<p class="text-sm text-gray-500" x-text="`Hasta €${precioMax}`"></p>
</div>
<!-- Más filtros... -->
</div>
</div>
Optimización para producción
Una vez que hayas terminado de personalizar tu tema, es hora de prepararlo para producción.
Purga de CSS
Asegúrate de que el script build de Tailwind esté configurado para purgar el CSS. En el ejemplo anterior, ya incluimos cssnano para minificar. Ejecuta:
npm run build
Esto generará un theme.css optimizado, con solo las clases que realmente usas.
Minificación de JS
Si usas Webpack, añade el plugin TerserPlugin en modo producción. Con Vite, la minificación es automática.
Carga asíncrona de Alpine.js
Para no bloquear el renderizado, asegúrate de que Alpine.js se cargue con defer o async. Si lo incluiste mediante CDN, ya lo tienes. Si lo bundleaste, coloca el script al final del <body>.
Verificación de rendimiento
Usa herramientas como Google PageSpeed Insights o Lighthouse. Con Tailwind y Alpine, deberías ver mejoras significativas en:
- First Contentful Paint (FCP): Menos CSS bloqueante.
- Time to Interactive (TTI): Alpine.js es ligero y se hidrata rápido.
- Cumulative Layout Shift (CLS): Al no usar jQuery y tener un DOM más predecible.
