Vendix Sticky Header Pattern
CRITICAL RULE - Los headers pegajosos deben estar SIEMPRE en la raíz del formulario o contenedor principal, y NUNCA dentro de un contenedor con padding interno.
🚨 The Sticky Problem
Cuando un header tiene sticky top-0 y su contenedor padre tiene padding, el header se quedará pegado al inicio del padding del padre, NO al inicio de la pantalla. Esto causa un "salto" o desplazamiento visual durante el scroll.
❌ WRONG (The Padded Parent)
<div class="p-4 md:p-6"> <!-- ⬅️ Padding here breaks sticky! -->
<div class="sticky top-0 z-30 bg-white shadow-sm">
<h1>Header Title</h1>
</div>
<div class="content">...</div>
</div>
✅ CORRECT (The Clean Parent)
<div class="min-h-screen"> <!-- ⬅️ No padding on parent -->
<div class="sticky top-0 z-30 bg-white/80 backdrop-blur-md border-b p-4 md:px-6 shadow-sm">
<div class="max-w-[1600px] mx-auto flex items-center justify-between">
<h1>Header Title</h1>
<div class="actions">...</div>
</div>
</div>
<div class="p-4 md:p-6"> <!-- ⬅️ Padding here is safe -->
<div class="max-w-[1600px] mx-auto">
<div class="content">...</div>
</div>
</div>
</div>
🛠️ Implementation Checklist
- Sticky Container:
sticky top-0 z-30 - Aesthetics:
bg-white/80+backdrop-blur-md(Premium glassmorphism)border-b border-gray-200shadow-smrounded-b-xl(Opcional, para estilo de tarjeta flotante)
- Actions: Mover botones de "Guardar" y "Cancelar" al header (lado derecho).
- Badges: Colocar badges de estado (Estado de Orden, Modo Edición) al lado del título.
- Layout: Usar un contenedor interno
max-w-[1600px] mx-autopara mantener consistencia.
🦖 Recommended Styles (Tailwind)
Para un header profesional y "Premium":
<div class="sticky top-0 z-30 bg-white/80 backdrop-blur-md border-b border-gray-200 p-4 md:px-6 md:py-4 shadow-sm mb-4 rounded-b-xl">
<div class="max-w-[1600px] mx-auto flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<!-- Left: Info & Badge -->
<div class="flex items-center gap-4">
<div class="w-12 h-12 rounded-xl bg-primary-50 flex items-center justify-center border border-primary-100">
<app-icon name="box" size="24" class="text-primary-600"></app-icon>
</div>
<div>
<div class="flex items-center gap-3">
<h1 class="text-xl font-bold text-gray-900">Module Title</h1>
<span class="px-2 py-1 bg-green-100 text-green-700 text-[10px] font-bold uppercase rounded-lg">Activo</span>
</div>
<p class="text-sm text-gray-500">Subtítulo descriptivo del módulo</p>
</div>
</div>
<!-- Right: Actions -->
<div class="flex items-center gap-3">
<app-button variant="outline" size="sm">Cancelar</app-button>
<app-button variant="primary" size="sm">Guardar Cambios</app-button>
</div>
</div>
</div>
📋 Common Error Prevention
- Z-index: Siempre usar
z-30o superior para evitar que el contenido del scroll pase por encima del header. - Form Scope: Si el header tiene botones que disparan el
submitdel formulario, asegúrate de que el<form>envuelva a todo el header. - Negative Margins: EVITA usar márgenes negativos (
-mx-4) para "compensar" el padding del padre. Es mejor mover el padding del padre a los hijos del contenido.
🚀 Reusable Component Pattern
Para evitar duplicación y mantener consistencia, usa el componente app-sticky-header disponible en shared/components.
API del Componente
Inputs
| Input | Tipo | Required | Default |
|---|---|---|---|
title | string | Sí | - |
subtitle | string | No | '' |
icon | string | No | 'box' |
variant | 'default' | 'glass' | No | 'glass' |
showBackButton | boolean | No | false |
backRoute | string | string[] | No | '/' |
badgeText | string | No | '' |
badgeColor | 'green' | 'blue' | 'yellow' | 'gray' | 'red' | No | 'blue' |
actions | StickyHeaderActionButton[] | No | [] |
Outputs
| Output | Payload |
|---|---|
actionClicked | string (ID del botón) |
Interfaces
interface StickyHeaderActionButton {
id: string; // ID único para emitir en actionClicked
label: string; // Texto del botón
variant: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
icon?: string; // Icono Lucide opcional
loading?: boolean; // Estado loading
disabled?: boolean; // Deshabilitado
visible?: boolean; // Mostrar/ocultar
}
Ejemplo de Uso
<app-sticky-header
[title]="isEditMode ? 'Editar Producto' : 'Nuevo Producto'"
[subtitle]="isEditMode ? 'Gestiona la información...' : 'Registra un nuevo producto...'"
[icon]="isEditMode ? 'package' : 'plus-circle'"
[showBackButton]="true"
backRoute="/admin/products"
[actions]="productHeaderActions()"
(actionClicked)="onHeaderAction($event)">
</app-sticky-header>
readonly productHeaderActions = computed<StickyHeaderActionButton[]>(() => [
{ id: 'cancel', label: 'Cancelar', variant: 'outline' },
{ id: 'save', label: this.isEditMode() ? 'Guardar Cambios' : 'Crear Producto', variant: 'primary', loading: this.isSubmitting() },
]);
onHeaderAction(actionId: string): void {
if (actionId === 'cancel') this.onCancel();
else if (actionId === 'save') this.onSubmit();
}
Related Skills
vendix-frontend-component- Basic component rulesvendix-frontend-theme- Branding and color patternsvendix-naming-conventions- Proper naming for classes and files
