Buenas Prácticas de HTML
Un HTML sólido es la base de todo gran sitio web. Estos consejos te ayudan a escribir marcado semántico, robusto y preparado para el futuro que los navegadores y lectores de pantalla adoran.
Establecer atributos width y height explícitos en las imágenes permite al navegador reservar el espacio correcto antes de que la imagen cargue, eliminando el Cumulative Layout Shift (CLS), una de las Core Web Vitals de Google.
<!-- ❌ Mal: el navegador no sabe el tamaño hasta que la imagen carga --> <img src="hero.jpg" alt="Imagen hero"> <!-- ✅ Bien: espacio reservado inmediatamente --> <img src="hero.jpg" alt="Imagen hero" width="1200" height="630" loading="lazy">
aspect-ratio: auto en CSS y el navegador calculará la proporción correcta automáticamente, incluso cuando CSS redimensione la imagen.Un esquema de documento construido con encabezados anidados correctamente ayuda tanto a los motores de búsqueda a entender tu jerarquía de contenido como a los usuarios de lectores de pantalla a navegar la página eficientemente.
<h1>Título de Página (uno por página)</h1> <h2>Sección Principal</h2> <h3>Subsección</h3> <h3>Otra subsección</h3> <h2>Otra Sección Principal</h2>
Al abrir enlaces en una nueva pestaña con target="_blank", la página abierta puede acceder a tu página mediante window.opener. Añadir rel="noopener noreferrer" previene esta vulnerabilidad de seguridad y también evita que se envíe información del referrer.
<!-- ❌ Vulnerable --> <a href="https://ejemplo.es" target="_blank">Visitar</a> <!-- ✅ Seguro --> <a href="https://ejemplo.es" target="_blank" rel="noopener noreferrer">Visitar</a>
El atributo autocomplete indica a los navegadores y gestores de contraseñas cómo pre-rellenar campos de formulario. Esto mejora drásticamente las tasas de finalización de formularios en móvil.
<input type="text" autocomplete="given-name"> <input type="email" autocomplete="email"> <input type="tel" autocomplete="tel"> <input type="password" autocomplete="current-password"> <input type="text" autocomplete="postal-code">
Las meta etiquetas Open Graph controlan cómo aparece tu página al compartirse en plataformas sociales como Facebook, LinkedIn y Twitter/X. Sin ellas, las plataformas eligen contenido al azar.
<meta property="og:title" content="Título de Página"> <meta property="og:description" content="Descripción corta"> <meta property="og:image" content="https://tudominio.es/og.jpg"> <meta property="og:url" content="https://tudominio.es/pagina"> <meta property="og:type" content="website"> <!-- Twitter/X --> <meta name="twitter:card" content="summary_large_image">
Trucos y Consejos de CSS
El CSS moderno es increíblemente potente. Estos consejos cubren layout, propiedades personalizadas y patrones que te ahorran tiempo y mantienen tus hojas de estilo limpias.
Deja de luchar con hacks de posicionamiento. El atajo place-items de CSS Grid centra el contenido tanto horizontal como verticalmente en dos líneas — sin calc, sin transforms.
.centrar-todo { display: grid; place-items: center; } /* También funciona para centrar a página completa */ body { display: grid; place-items: center; min-height: 100vh; }
Las propiedades personalizadas de CSS (variables) te permiten definir tus tokens de diseño una vez y reutilizarlos en todas partes. También se actualizan en tiempo real, haciendo trivial el modo oscuro y la tematización.
:root { --color-primario: #6366f1; --color-fondo: #ffffff; --radio-md: 12px; --espaciado-md: 16px; } @media (prefers-color-scheme: dark) { :root { --color-fondo: #0d1117; } } .btn { background: var(--color-primario); border-radius: var(--radio-md); padding: var(--espaciado-md); }
clamp(min, preferred, max) permite que los tamaños de fuente escalen suavemente con el ancho del viewport — sin necesidad de media queries. El valor del medio suele ser una unidad vw.
/* clamp(mínimo, preferido, máximo) */ h1 { font-size: clamp(1.8rem, 5vw, 3.5rem); } p { font-size: clamp(0.9rem, 2vw, 1.1rem); line-height: 1.7; max-width: 65ch; /* ancho óptimo de lectura */ }
Habilita el scroll suave globalmente y usa scroll-margin-top en los destinos de anclaje para asegurar que las cabeceras fijas no se superpongan al contenido al que se hace scroll.
@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } } /* Offset para cabecera fija (ej. 70px de alto) */ [id] { scroll-margin-top: 90px; }
scroll-behavior: smooth en una media query prefers-reduced-motion: no-preference — algunos usuarios experimentan mareo por el scroll animado.El patrón auto-fill + minmax() crea una cuadrícula que ajusta automáticamente el número de columnas según el espacio disponible — sin necesidad de media queries.
.cuadricula-tarjetas { display: grid; grid-template-columns: repeat( auto-fill, minmax(280px, 1fr) ); gap: 24px; } /* 1 col en móvil → 2 → 3 → 4 automáticamente */
Nunca hagas outline: none globalmente — rompe la navegación por teclado. Usa :focus-visible para mostrar anillos de foco solo para usuarios de teclado, manteniendo el diseño limpio para usuarios de ratón.
/* ❌ Nunca hagas esto */ * { outline: none; } /* ✅ Oculta para ratón, muestra para teclado */ :focus:not(:focus-visible) { outline: none; } :focus-visible { outline: 2px solid #6366f1; outline-offset: 3px; border-radius: 4px; }
Consejos de JavaScript
Escribe JavaScript más limpio y eficiente con estos patrones modernos y APIs del navegador.
En lugar de escuchar el evento scroll (que se dispara cientos de veces por segundo), usa la API IntersectionObserver para reaccionar solo cuando los elementos entran o salen del viewport.
const observer = new IntersectionObserver( (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add('visible'); observer.unobserve(entry.target); // dejar de observar } }); }, { rootMargin: '0px 0px -50px 0px' } ); document.querySelectorAll('.animar-al-scroll') .forEach(el => observer.observe(el));
Eventos como resize e input se disparan muy rápidamente. El debounce retrasa la ejecución hasta que el usuario deja de interactuar, previniendo trabajo innecesario.
function debounce(fn, delay = 300) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), delay); }; } // Uso window.addEventListener('resize', debounce(() => { recalcularLayout(); }, 200) );
La API moderna navigator.clipboard está basada en promesas y es mucho más limpia que el viejo hack document.execCommand('copy').
async function copiarAlPortapapeles(texto) { try { await navigator.clipboard.writeText(texto); mostrarToast('¡Copiado!'); } catch (err) { console.error('Fallo al copiar:', err); } } // Uso document.querySelector('.btn-copiar') .addEventListener('click', () => copiarAlPortapapeles('Texto a copiar') );
localStorage puede lanzar errores en modo navegación privada o cuando el almacenamiento está lleno. Envuélvelo siempre en un helper try/catch.
const storage = { get(clave, fallback = null) { try { const item = localStorage.getItem(clave); return item ? JSON.parse(item) : fallback; } catch { return fallback; } }, set(clave, valor) { try { localStorage.setItem(clave, JSON.stringify(valor)); return true; } catch { return false; } } }; storage.set('tema', 'oscuro'); const tema = storage.get('tema', 'claro');
Para animaciones manejadas por JavaScript (ej. canvas, GSAP), comprueba la configuración prefers-reduced-motion del usuario y simplifica o deshabilita las animaciones en consecuencia.
const movimientoReducido = window .matchMedia('(prefers-reduced-motion: reduce)') .matches; if (!movimientoReducido) { iniciarAnimaciones(); } else { mostrarFallbackEstatico(); }
Consejos de Rendimiento
Los sitios web rápidos posicionan mejor, convierten más y mantienen a los usuarios contentos. Estos consejos cubren las mayores mejoras en rendimiento web.
Sin font-display: swap, los navegadores ocultan el texto hasta que la fuente personalizada carga (FOIT). Con él, la fuente del sistema se muestra inmediatamente y luego se intercambia, mejorando enormemente el Largest Contentful Paint (LCP).
<!-- Paso 1: preconnect --> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <!-- Paso 2: añade &display=swap a la URL --> <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;700&display=swap" rel="stylesheet">
Las imágenes WebP y AVIF son significativamente más pequeñas que JPEGs y PNGs con igual calidad. Usa el elemento <picture> para servir formatos modernos con un fallback JPEG para navegadores antiguos.
<picture> <source srcset="hero.avif" type="image/avif"> <source srcset="hero.webp" type="image/webp"> <img src="hero.jpg" alt="Imagen hero" width="1200" height="630" loading="lazy"> </picture>
Las etiquetas <script> normales bloquean el parseo HTML. defer ejecuta el script después de que el parseo termine (en orden). async ejecuta los scripts tan pronto como se descargan (orden no garantizado).
<!-- Bloquea el renderizado ❌ --> <script src="app.js"></script> <!-- Se ejecuta en orden, tras el DOM ✅ (usar para la mayoría de scripts) --> <script src="app.js" defer></script> <!-- Se ejecuta inmediatamente cuando está listo ✅ (analíticas, anuncios) --> <script src="analytics.js" async></script>
Limítate a animar transform y opacity — se ejecutan en el hilo compositor de la GPU sin disparar layout ni paint. Añade will-change solo si haces perfilado y ves un beneficio real.
/* ❌ Dispara layout (costoso) */ .mal { transition: width 0.3s, top 0.3s; } /* ✅ Compuesto por GPU (barato) */ .bien { transition: transform 0.3s, opacity 0.3s; } /* Usar con moderación, solo antes de animaciones complejas */ .modal-entrando { will-change: transform; } .modal-entrado { will-change: auto; } /* resetear después */
Usa <link rel="preload"> para decirle al navegador que obtenga recursos críticos antes — antes de que los descubra en CSS o JS. Ideal para imágenes hero, fuentes críticas y scripts clave.
<!-- Precargar imagen hero (elemento LCP) --> <link rel="preload" as="image" href="hero.webp" fetchpriority="high"> <!-- Precargar fuente crítica --> <link rel="preload" as="font" href="fuente.woff2" type="font/woff2" crossorigin>
Consejos de Accesibilidad
La accesibilidad no es una lista de verificación — es buen diseño. Estos consejos ayudan a que tus sitios sean utilizables por todos, incluyendo usuarios de lectores de pantalla y navegadores por teclado.
Los botones que contienen solo un icono no tienen texto visible para que los lectores de pantalla lo anuncien. Añade un aria-label para proporcionar una etiqueta descriptiva.
<!-- ❌ El lector de pantalla dice "botón" --> <button><i class="fa-solid fa-times"></i></button> <!-- ✅ El lector de pantalla dice "Cerrar diálogo" --> <button aria-label="Cerrar diálogo"> <i class="fa-solid fa-times" aria-hidden="true"></i> </button>
El atributo lang indica a los lectores de pantalla qué idioma usar, habilita la separación silábica correcta en CSS y ayuda a las herramientas de traducción a identificar el idioma del contenido. Para idiomas RTL, añade también dir="rtl".
<!-- Español --> <html lang="es"> <!-- Árabe (RTL) --> <html lang="ar" dir="rtl"> <!-- Francés --> <html lang="fr">
WCAG 2.1 AA requiere un ratio de contraste de al menos 4.5:1 para texto normal y 3:1 para texto grande (18px+ negrita o 24px+ normal). Usa un comprobador de contraste antes de publicar.
/* ❌ #6b7280 sobre #fff = 4.48:1 (falla AA para texto pequeño) */ .atenuado { color: #6b7280; background: #fff; } /* ✅ #4b5563 sobre #fff = 7.0:1 (pasa AA y AAA) */ .atenuado { color: #4b5563; background: #fff; }
A veces necesitas texto que solo los lectores de pantalla puedan oír — como un enlace de saltar al contenido o una etiqueta para un elemento visual. La clase de utilidad .sr-only lo oculta visualmente manteniéndolo accesible.
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; } <!-- Ejemplo de enlace de salto --> <a href="#main" class="sr-only">Saltar al contenido</a>
Cuando un modal está abierto, el foco del teclado debe confinarse dentro de él. De lo contrario, Tab enfocará elementos detrás de la superposición — haciendo el modal inutilizable para usuarios de teclado.
function atraparFoco(modal) { const enfocables = modal.querySelectorAll( 'a, button, input, select, textarea, [tabindex]:not([tabindex="-1"])' ); const primero = enfocables[0]; const ultimo = enfocables[enfocables.length - 1]; modal.addEventListener('keydown', e => { if (e.key !== 'Tab') return; if (e.shiftKey ? document.activeElement === primero : document.activeElement === ultimo) { e.preventDefault(); (e.shiftKey ? ultimo : primero).focus(); } }); primero.focus(); }
Consejos Responsive y Móvil
El tráfico móvil es dominante. Estos consejos aseguran que tus diseños funcionen perfectamente en todos los tamaños de pantalla — desde un teléfono de 320px hasta un monitor 4K.
Sin la etiqueta meta viewport, los navegadores móviles renderizan a ~980px de ancho de escritorio y luego alejan. Permite el escalado del usuario — deshabilitarlo es una barrera de accesibilidad importante y rompe el zoom del navegador.
<!-- ❌ Deshabilita el zoom del usuario (violación de accesibilidad) --> <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <!-- ✅ Correcto: permite escalar hasta 5× --> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=5">
Las guías de interfaz humana de Apple y WCAG 2.5.5 recomiendan un objetivo táctil mínimo de 44×44 píxeles CSS. Los objetivos pequeños frustran a los usuarios y llevan a toques erróneos. Puedes expandir el área cliqueable sin cambiar el tamaño visual usando padding o pseudo-elementos.
/* Expande el área táctil sin cambio visual */ .btn-icono { position: relative; width: 24px; height: 24px; } .btn-icono::before { content: ''; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); min-width: 44px; min-height: 44px; }
Las propiedades lógicas de CSS como margin-inline-start en lugar de margin-left invierten automáticamente la dirección según el modo de escritura del documento — haciendo el soporte RTL casi sin esfuerzo.
/* Físico (no se invierte para RTL) */ .icono { margin-right: 8px; } /* Lógico (se invierte automáticamente en RTL) */ .icono { margin-inline-end: 8px; } /* Más ejemplos de propiedades lógicas */ .tarjeta { padding-inline: 16px; /* izquierda + derecha */ padding-block: 12px; /* arriba + abajo */ border-start-start-radius: 12px; /* arriba-izquierda en LTR */ }
100vh en navegadores móviles incluye la interfaz del navegador (barra de direcciones), causando desbordamiento cuando la barra aparece. La nueva unidad dvh (altura de viewport dinámica) se ajusta cuando la interfaz del navegador se muestra u oculta.
/* ❌ Se desborda en móvil cuando aparece la interfaz del navegador */ .hero { height: 100vh; } /* ✅ Con fallback para navegadores antiguos */ .hero { height: 100vh; /* fallback */ height: 100dvh; /* altura de viewport dinámica */ }
svh (viewport pequeño, siempre excluye la interfaz) y lvh (viewport grande, siempre incluye el espacio de la interfaz).Más de ResponsiveCheckTool
Pon Estos Consejos en Práctica
Prueba cómo se ven tus cambios responsive en móvil, tablet y PC — al instante, gratis, sin necesidad de registro.
Prueba Tu Web Ahora