HTML, CSS y JavaScript, explicados y listos para pedir
Apuntes visuales, catálogo de capacidades y manual de peticiones a la IA — todo en una sola página, en español e inglés, sin conexión necesaria.
Esta página tiene tres usos: aprender (cada concepto está explicado para alguien que nunca ha programado), consultar (qué existe exactamente en HTML, CSS y JavaScript, con ejemplos funcionando en vivo) y pedir (cada tarjeta incluye la frase exacta, en lenguaje natural, con la que pedirle ese elemento a una IA como Claude). Usa el índice o el buscador para saltar a cualquier punto, y el botón «Copiar» de cada frase 🗣 para llevártela a tu conversación con la IA.
HTMLCSSJavaScript🗣 Manual de peticiones a la IA
1Índice completo
Todo el contenido de la página, ordenado y enlazado. Escribe en el buscador para filtrar en vivo: el índice se reduce a lo que coincida con tu búsqueda.
No hay resultados para esa búsqueda. Prueba con otra palabra (por ejemplo: «input», «flexbox», «evento»).
2Cómo hablarle a la IA
No necesitas saber programar para conseguir buen código: necesitas saber pedir. Estas recomendaciones marcan la diferencia entre una respuesta mediocre y una página profesional a la primera. Todas las frases 🗣 de esta guía siguen estos principios.
Consejos que cambian el resultado
1 · Define el objetivo antes de pedir nada
Antes de escribir el primer mensaje, ten claro qué debe conseguir la página: ¿vender?, ¿recoger datos?, ¿informar? Dile a la IA el objetivo y el público («una página para que padres apunten a sus hijos a un campamento»), no solo la pieza («hazme un formulario»). Con contexto, la IA toma cientos de pequeñas decisiones bien; sin él, las toma al azar.
2 · Pide calidad desde el primer mensaje, no después
Estructura semántica, accesibilidad, diseño adaptable (responsive) y validación se piden al principio. Añadirlas después obliga a rehacer el trabajo. Una coletilla que lo resuelve: «con HTML semántico, accesible, adaptado a móvil y con validación de formularios».
3 · Pide todos los estados visuales
Un botón no es solo un botón: tiene estado normal, al pasar el ratón (hover), con foco de teclado (focus), pulsado (active) y deshabilitado (disabled). Un campo puede ser válido o inválido. Pide «con todos los estados visuales y mensajes claros de error, éxito y carga» y la interfaz se sentirá terminada, no a medias.
4 · Pide código limpio y comentado en tu idioma
Di explícitamente: «código limpio, organizado en funciones reutilizables y con comentarios breves en español». Así, cuando quieras aprender del resultado o pedir cambios, entenderás qué hace cada parte.
5 · Pide etiquetas visibles, contraste y tamaño táctil
Tres detalles que separan lo amateur de lo profesional: cada campo con su etiqueta (label) visible, texto con contraste suficiente para leerse al sol, y botones de al menos 44 píxeles para pulsarlos con el dedo sin fallar. Pídelos por su nombre.
6 · Pide que se contemplen los cuatro casos
Toda interfaz vive cuatro momentos: vacío (aún no hay datos), cargando (esperando respuesta), error (algo falló) y éxito. Si no los pides, la IA suele entregar solo el caso feliz, y la página se rompe en cuanto algo se tuerce.
7 · Pide implementación completa y decisiones explicadas
Cierra tus peticiones con «implementación completa, sin partes pendientes» para evitar entregas a medias con comentarios tipo «aquí iría…». Y añade «explícame brevemente las decisiones importantes»: aprenderás el porqué, no solo el qué.
8 · Itera en pasos pequeños y concretos
Tras la primera versión, pide cambios de uno en uno y señalando el lugar exacto: «en el formulario de contacto, haz que el mensaje de error aparezca debajo del campo, en rojo». Las peticiones vagas («mejóralo») producen cambios impredecibles.
Qué evitar al pedir
«Divs para todo». Si no pides estructura semántica, puedes recibir una sopa de <div> sin significado. Peor para accesibilidad, buscadores y mantenimiento.
ARIA innecesaria. Más atributos ARIA no es más accesibilidad; usados sin criterio la empeoran. Pide «ARIA solo donde HTML no llegue».
Validar solo con JavaScript. La validación nativa de HTML (required, type="email"…) funciona incluso si el JS falla. Pide siempre «validación nativa de HTML reforzada con JavaScript», no sustituida.
El placeholder como única etiqueta. El texto gris de dentro del campo desaparece al escribir. Sin label visible, el usuario olvida qué iba en cada campo. Pide labels siempre visibles.
Animaciones excesivas. Las microinteracciones sutiles dan calidad; las animaciones largas y llamativas cansan y marean. Pide «transiciones sutiles que respeten la preferencia de movimiento reducido».
Pedir sin contexto. «Hazme una web» obliga a la IA a inventarse el 90 %. Dedica dos frases a explicar para qué es, para quién y qué debe pasar cuando funcione.
Mensajes iniciales listos para copiar
Así se empieza bien una conversación con Claude Code (o cualquier IA que programe). Cópialos tal cual y cambia lo que va entre corchetes.
Genérico — para cualquier página
Quiero crear una página web para [objetivo: p. ej., presentar mi taller de bicicletas y que me escriban clientes]. El público es [quién la usará]. Hazla con HTML semántico, accesible, adaptada a móvil y ordenador, con un diseño limpio y profesional. Usa HTML, CSS y JavaScript en archivos separados, con código comentado en español. Implementación completa, sin partes pendientes, y explícame brevemente las decisiones importantes.
Formulario
Necesito un formulario de [registro / contacto / pedido] con estos campos: [lista de campos]. Cada campo con su etiqueta visible, validación nativa de HTML reforzada con JavaScript en tiempo real, mensajes de error claros debajo de cada campo, y estados de carga, éxito y error al enviar. Botones con buen tamaño táctil y foco visible al navegar con teclado. Al enviar, simula la petición al servidor y muéstrame cómo quedaría con un servidor real.
Landing completa
Quiero una landing page para [producto o servicio] cuyo objetivo es que el visitante [acción: reserve, compre, se apunte]. Estructura: cabecera con navegación, sección principal con titular y llamada a la acción, tres beneficios, testimonios, preguntas frecuentes y pie de página. Diseño sobrio y profesional con una sola paleta de colores, tema claro y oscuro, tipografía del sistema y sin dependencias externas. Adaptada primero a móvil. Todo el texto en español.
Componente concreto
Créame un componente de [tarjeta de producto / ventana modal / menú desplegable] reutilizable. Debe funcionar con teclado y lector de pantalla, tener todos los estados visuales (normal, hover, foco, activo, deshabilitado) y adaptarse al ancho disponible. Dame el HTML, el CSS y el JavaScript por separado, con un ejemplo de uso y comentarios en español explicando cómo personalizarlo.
Revisar y mejorar lo que ya tengo
Te paso mi página actual. Revísala como un desarrollador senior: dime qué está mal o incompleto en estructura, accesibilidad, diseño adaptable y validación, ordenado de más a menos importante. Después aplica solo las tres mejoras más importantes, explicándome cada cambio en una frase. No cambies el diseño general sin avisarme antes.
3HTML — la estructura
HTML es el esqueleto de toda página web: define qué es cada cosa (un título, un párrafo, un botón), no cómo se ve. Cada tarjeta de esta sección sigue el mismo patrón de cuatro partes: qué es (explicado sin tecnicismos), el código, una demo en vivo funcionando de verdad y la frase 🗣 con la que pedírselo a una IA hablando.
Base del documento
La plantilla mínima: <!DOCTYPE>, <html lang>, <head> y <body>
Todo documento HTML empieza igual, como una carta empieza con fecha y saludo. <!DOCTYPE html> le dice al navegador «esto es HTML moderno»; lang="es" declara el idioma (clave para lectores de pantalla y traductores); en el <head> va lo invisible (título de la pestaña, codificación de caracteres, ajuste a pantallas móviles) y en el <body> todo lo que se ve. meta charset="UTF-8" garantiza que tildes y eñes se muestren bien, y meta viewport hace que la página no se vea diminuta en el móvil.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mi página</title>
</head>
<body>
<!-- Aquí va todo el contenido visible -->
</body>
</html>
Demo en vivo
Esta misma página usa exactamente esa plantilla. Datos reales de este documento:
Idioma declarado:
Codificación:
Título de la pestaña:
🗣«Créame la estructura básica de una página HTML en español, preparada para verse bien en el móvil y con el título "Mi página" en la pestaña.»
Estructura semántica
Las etiquetas semánticas dicen qué papel juega cada zona de la página. Es la diferencia entre una casa con habitaciones rotuladas (cocina, baño, dormitorio) y una nave diáfana llena de cajas iguales: el navegador, los buscadores y los lectores de pantalla entienden la primera al instante.
<header> — la cabecera
Es la parte de arriba de la página (o de una sección): logo, título y navegación principal. Como la portada de una revista, presenta lo que viene. No confundir con <head>, que es invisible y solo contiene metadatos.
<header>
<h1>Panadería Sol</h1>
<p>Pan artesano desde 1982</p>
</header>
Demo en vivo
Panadería Sol
Pan artesano desde 1982
🗣«Ponle a la página una cabecera con el nombre del negocio bien grande y un lema debajo.»
<nav> — la navegación
Agrupa los enlaces principales para moverse por el sitio, como el índice de un libro o los carteles de un centro comercial. Los lectores de pantalla permiten saltar directamente a ella, así que úsala solo para navegación de verdad, no para cualquier lista de enlaces.
🗣«Añade un menú de navegación con enlaces a Inicio, Productos y Contacto que me lleven a cada sección de la página al pulsarlos.»
<main> — el contenido principal
Envuelve el contenido central y único de la página: lo que el visitante vino a ver. Solo puede haber uno por página. Es el escenario del teatro: cabecera y pie son el marco, <main> es la función.
🗣«Organiza la página con su cabecera arriba, el contenido principal en el centro y el pie abajo, usando las etiquetas semánticas correctas.»
<section> y <article> — bloques con sentido
<section> agrupa contenido relacionado bajo un mismo tema, como los capítulos de un libro; casi siempre lleva su propio título. <article> es una pieza completa y autónoma que tendría sentido por sí sola fuera de la página: una noticia, una receta, una tarjeta de producto, una reseña. Truco: si podrías compartirlo suelto por WhatsApp y se entendería, es un <article>.
<section>
<h2>Recetas de la semana</h2>
<article>
<h3>Pan de centeno</h3>
<p>Ingredientes: harina, agua, sal…</p>
</article>
<article>
<h3>Hogaza rústica</h3>
<p>Fermentación lenta de 12 horas…</p>
</article>
</section>
Demo en vivo
Recetas de la semana
Pan de centeno
Ingredientes: harina, agua, sal…
Hogaza rústica
Fermentación lenta de 12 horas…
🗣«Crea una sección de recetas con su título, y dentro cada receta como una tarjeta independiente con su nombre y su descripción.»
<aside> — contenido secundario
Contenido relacionado pero prescindible: la nota al margen de un libro de texto. Barras laterales, datos curiosos, enlaces relacionados, publicidad. Si lo quitas, el contenido principal sigue entendiéndose igual.
<article>
<h2>Cómo hacer masa madre</h2>
<p>El proceso empieza mezclando…</p>
<aside>
<p>💡 ¿Sabías que…? La masa madre más antigua
documentada tiene más de 120 años.</p>
</aside>
</article>
Demo en vivo
El proceso empieza mezclando harina y agua a partes iguales…
🗣«Junto al artículo, añade un recuadro lateral con un dato curioso que se distinga visualmente del texto principal.»
<footer> — el pie
El cierre de la página (o de una sección): contacto, aviso legal, redes sociales, copyright. Como la contraportada de un libro, es donde el lector busca los datos de la editorial.
🗣«Ponle a la página un pie con el copyright, la dirección y un enlace para volver arriba.»
<figure> y <figcaption> — imagen con pie
Una imagen (o gráfico, o bloque de código) con su leyenda, unidas oficialmente. Como la foto de un periódico con su pie explicativo: si las separas, la foto pierde contexto. El navegador y los lectores de pantalla saben que ese texto describe esa imagen.
<figure>
<img src="hogaza.jpg" alt="Hogaza de pan recién horneada">
<figcaption>Nuestra hogaza rústica, premio local 2025.</figcaption>
</figure>
Demo en vivo
Nuestra hogaza rústica, premio local 2025.
🗣«Inserta la foto del producto con un pie de foto debajo que diga qué es, de forma que imagen y texto queden asociados.»
<details> y <summary> — el desplegable sin JavaScript
Un bloque que se abre y se cierra al pulsar, sin una sola línea de JavaScript. Perfecto para preguntas frecuentes o información opcional. <summary> es el texto siempre visible que hace de botón; el resto aparece al desplegarlo. Funciona con teclado por sí solo.
<details>
<summary>¿Hacéis envíos a domicilio?</summary>
<p>Sí, de martes a sábado por la mañana,
con pedido mínimo de 10 €.</p>
</details>
Demo en vivo
¿Hacéis envíos a domicilio?
Sí, de martes a sábado por la mañana, con pedido mínimo de 10 €.
¿Tenéis opciones sin gluten?
Sí, horneadas en obrador separado los jueves.
🗣«Añade una sección de preguntas frecuentes donde cada pregunta se despliegue al pulsarla para mostrar su respuesta, sin usar JavaScript.»
Texto y contenido
<h1>–<h6> — los títulos y su jerarquía
Seis niveles de título, como el esquema de unos apuntes: <h1> es el tema del día (solo uno por página), <h2> los grandes apartados, <h3> los subapartados… La regla de oro: no saltarse niveles (de h2 a h4) y no elegir el título por su tamaño — el tamaño se cambia con CSS, el nivel dice la importancia. Los lectores de pantalla navegan saltando de título en título, así que una buena jerarquía es un buen mapa.
<h1>Panadería Sol</h1> <!-- único en la página -->
<h2>Nuestros panes</h2>
<h3>De masa madre</h3>
<h3>Integrales</h3>
<h2>Dónde estamos</h2>
Demo en vivo
Panadería Sol
Nuestros panes
De masa madre
Integrales
Dónde estamos
🗣«Organiza los títulos de la página en jerarquía: un único título principal y luego apartados y subapartados bien anidados, sin saltarse niveles.»
<p> — el párrafo
La unidad básica de texto: cada idea, su párrafo. El navegador añade espacio entre ellos automáticamente. Dentro puedes resaltar con <strong> (importancia real, se lee con énfasis) y <em> (énfasis de entonación), que no son lo mismo que «negrita» y «cursiva» decorativas.
<p>Abrimos <strong>todos los días</strong> a las 7:00,
incluso <em>festivos</em>.</p>
<p>Los domingos solo hasta mediodía.</p>
Demo en vivo
Abrimos todos los días a las 7:00, incluso festivos.
Los domingos solo hasta mediodía.
🗣«Escribe la información en párrafos cortos y resalta en negrita solo lo verdaderamente importante, como el horario.»
<ol>, <ul> y <li> — listas ordenadas y sin orden
<ul> es la lista de la compra (el orden da igual, con viñetas); <ol> es la receta (los pasos van numerados y el orden importa). Cada elemento va en su <li>. Se pueden anidar listas dentro de listas para crear subniveles.
<h3>Pasos</h3>
<ol>
<li>Mezclar harina y agua</li>
<li>Reposar 30 minutos</li>
<li>Añadir la sal</li>
</ol>
<h3>Necesitarás</h3>
<ul>
<li>Un bol grande</li>
<li>Una báscula</li>
</ul>
Demo en vivo
Pasos
Mezclar harina y agua
Reposar 30 minutos
Añadir la sal
Necesitarás
Un bol grande
Una báscula
🗣«Pon los pasos de la receta como lista numerada y los ingredientes como lista de puntos.»
<a> — el enlace
La pieza que convirtió los documentos en «web»: un texto pulsable que lleva a otro lugar. href es el destino (otra página, una sección de esta misma con #id, un correo con mailto:). target="_blank" lo abre en pestaña nueva — y cuando se usa, conviene añadir rel="noopener noreferrer" por seguridad y privacidad. El texto del enlace debe decir a dónde va: «ver horarios» sí, «pincha aquí» no.
🗣«Añade un enlace que lleve a la sección de contacto, y otro a la web del proveedor que se abra en una pestaña nueva de forma segura.»
<img> — la imagen
Inserta una imagen. Es una etiqueta «vacía»: no se cierra. src es la ruta del archivo y alt es el texto alternativo: lo que se lee en voz alta a personas ciegas y lo que aparece si la imagen no carga. Escribe en alt lo que dirías por teléfono para describir la foto; si es puramente decorativa, se deja vacío (alt="") para que el lector de pantalla la salte.
<img src="imagenes/hogaza.jpg"
alt="Hogaza de pan sobre una tabla de madera"
width="400" height="300">
Demo en vivo
Imagen SVG incrustada (sin archivos externos). Su texto alternativo: «Hogaza de pan sobre una tabla de madera».
🗣«Inserta la foto de la hogaza con un texto alternativo que la describa para personas que usan lector de pantalla, y evita que la página salte mientras carga.»
<table> — la tabla de datos
Para datos que se leen en filas y columnas, como una hoja de cálculo: precios, horarios, comparativas. <caption> es su título, <th> las celdas de cabecera (con scope para que el lector de pantalla sepa a qué se refiere cada dato) y <td> los datos. Importante: las tablas son para datos, nunca para maquetar la página — para eso está CSS.
🗣«Monta una tabla de precios con dos columnas, producto y precio, con su fila de cabecera y accesible para lectores de pantalla.»
Formularios a fondo
El formulario es la conversación entre tu página y quien la visita: la web pregunta, la persona responde y los datos viajan al servidor. Es la zona donde más se nota la diferencia entre una página amateur y una profesional, así que la tratamos pieza a pieza.
<form> — el contenedor y sus atributos
Es el sobre que agrupa los campos y sabe a dónde enviarlos. Sus atributos son las instrucciones de envío: action (la dirección del servidor que recibe los datos), method (get pone los datos en la URL — solo para búsquedas; post los envía en privado — para todo lo demás), enctype="multipart/form-data" (obligatorio si se suben archivos), autocomplete (permite al navegador rellenar datos conocidos), novalidate (desactiva la validación nativa — útil solo para probar la tuya), target (dónde abrir la respuesta), name (identifica el formulario), rel (relación con el destino) y accept-charset (codificación del envío, casi siempre UTF-8).
Al enviar, esta demo muestra los datos que viajarían al servidor (aquí el envío está simulado: no sale nada de tu navegador).
🗣«Crea un formulario que envíe los datos de forma privada al servidor, preparado para subir archivos y con autocompletado del navegador activado.»
<label> — la etiqueta del campo
El rótulo visible que dice qué va en cada campo. Se asocia con su campo mediante for (en el label) e id (en el campo): al asociarlos, pulsar el texto activa el campo — regalo enorme en móvil — y el lector de pantalla anuncia el nombre correcto al entrar en él. Regla de oro: todo campo tiene su label visible; el placeholder no lo sustituye, porque desaparece al escribir.
🗣«Pon a cada campo del formulario su etiqueta visible encima, bien asociada, para que al tocar el texto se active el campo.»
<fieldset> y <legend> — grupos de campos
Agrupan campos relacionados bajo un título común, como los apartados de un impreso oficial («Datos personales», «Dirección de envío»). Imprescindible con radios y checkboxes: la <legend> da el contexto («¿Tamaño?») que cada opción individual no tiene. Un lector de pantalla lee «¿Tamaño? — Grande», no solo «Grande».
<fieldset>
<legend>Tamaño de la hogaza</legend>
<label><input type="radio" name="tam" value="m"> Mediana</label>
<label><input type="radio" name="tam" value="g"> Grande</label>
</fieldset>
Demo en vivo
🗣«Agrupa las opciones de tamaño en un recuadro con su título, para que se entienda que pertenecen a la misma pregunta.»
Todos los tipos de <input>
Un mismo elemento, <input>, se transforma en 22 controles distintos según su atributo type. Elegir el tipo correcto te regala validación, teclado móvil adecuado y controles nativos (calendario, paleta de color…) sin escribir nada más.
type="text" — texto libre
El campo comodín: una línea de texto sin reglas. Para nombres, apellidos, ciudades… Si existe un tipo más específico (email, tel…), úsalo antes que este.
<input type="text" name="nombre">
🗣«Añade al formulario un campo de texto para el nombre, con su etiqueta visible.»
type="email" — correo electrónico
Exige formato de correo (algo@algo) sin que escribas ninguna regla, y en el móvil muestra el teclado con la @ a mano.
<input type="email" name="correo" required>
🗣«Añade un campo de correo electrónico obligatorio que avise si el formato no es válido.»
type="password" — contraseña
Oculta lo escrito con puntos para que nadie lo lea por encima del hombro. Ojo: solo lo oculta en pantalla; la seguridad real la pone el servidor y el candado HTTPS.
🗣«Añade un campo de contraseña de mínimo 8 caracteres, con un botón para mostrarla u ocultarla.»
type="tel" — teléfono
En el móvil abre el teclado numérico de marcar. No valida el formato por sí solo (cada país es distinto): combínalo con pattern si necesitas uno concreto.
🗣«Añade un campo para la página web del cliente que compruebe que es una dirección válida y completa.»
type="search" — búsqueda
Como un campo de texto, pero declarado como buscador: muchos navegadores añaden la ✕ para vaciarlo de un toque y el teclado móvil muestra «buscar».
<input type="search" name="q">
🗣«Pon una barra de búsqueda arriba que se pueda vaciar de un toque.»
type="number" — número
Solo acepta números, con flechitas para subir y bajar. min, max y step definen los límites y el salto. Para códigos que «parecen» números pero no lo son (código postal), mejor texto con inputmode="numeric".
🗣«Añade un campo de cantidad que solo acepte números del 1 al 12, con flechas para subir y bajar.»
type="range" — deslizador
Un control deslizante para valores aproximados (volumen, presupuesto). Como no muestra el número elegido, acompáñalo siempre de un <output> que lo enseñe en vivo.
🗣«Añade un deslizador del 0 al 10 que muestre al lado el valor elegido mientras se mueve.»
type="date" — fecha
Abre el calendario nativo del dispositivo al pulsarlo: cero errores de formato. min y max acotan las fechas elegibles (por ejemplo, nada anterior a hoy).
🗣«Añade un selector de hora entre las 7:00 y las 20:00, en intervalos de 15 minutos.»
type="datetime-local" — fecha y hora
Fecha y hora juntas en un solo control, en la zona horaria del usuario. Ideal para citas y reservas.
<input type="datetime-local" name="cita">
🗣«Añade un campo para elegir día y hora de la cita en un mismo control.»
type="month" — mes y año
Selector de mes + año, sin día. Típico de la caducidad de tarjetas o informes mensuales.
<input type="month" name="caducidad">
🗣«Añade un campo para elegir mes y año, sin día concreto, como el de caducidad de una tarjeta.»
type="week" — semana
Selecciona una semana concreta del año (semana 32 de 2026). Poco frecuente, pero perfecto para planificaciones semanales. El soporte varía entre navegadores: comprueba cómo se ve en el tuyo.
<input type="week" name="semana">
🗣«Añade un selector de semana del año para planificar el turno, y dime qué pasa en navegadores que no lo soporten.»
type="color" — color
Abre la paleta de colores del sistema y devuelve el color en formato hexadecimal (#rrggbb). Útil para personalizaciones.
<input type="color" name="color" value="#0e7466">
🗣«Deja que el usuario elija un color con la paleta del sistema y usa ese color en la vista previa.»
type="file" — archivo
Abre el selector de archivos del dispositivo. accept limita los tipos (por ejemplo, solo imágenes), multiple permite varios y en móvil capture puede abrir directamente la cámara. El formulario necesita enctype="multipart/form-data" para enviarlos.
🗣«Añade un botón para adjuntar fotos, que solo acepte imágenes y permita seleccionar varias a la vez.»
type="checkbox" — casilla
Casillas independientes: puedes marcar ninguna, una o varias. Como marcar platos en un menú. Para «acepto las condiciones», una casilla obligatoria con required.
<label><input type="checkbox" name="extra"
value="semillas"> Con semillas</label>
Extras (marca los que quieras)
🗣«Añade casillas para elegir extras, permitiendo marcar varios a la vez, y una casilla obligatoria de aceptar las condiciones.»
type="radio" — opción única
Opciones excluyentes: elegir una desmarca las demás, como los botones de una radio antigua. El truco está en el name: todas las opciones del mismo grupo comparten el mismo.
🗣«Añade las formas de pago como opciones donde solo se pueda elegir una, con la pregunta como título del grupo.»
type="hidden" — campo oculto
Un dato que viaja con el formulario sin que el usuario lo vea ni lo toque: el número interno del producto, un código de campaña. Como la etiqueta cosida por dentro de una prenda. Nunca guardes ahí secretos: cualquiera puede verlo mirando el código de la página.
Aquí hay un campo oculto de verdad con el valor «HOGAZA-42». No se ve: esa es su función. Viajaría con el envío del formulario.
🗣«Haz que el formulario envíe también el código del producto sin mostrárselo al usuario.»
type="submit" — enviar
El botón que dispara el envío del formulario y su validación. Pulsar Enter en un campo también lo activa. Hoy se prefiere <button type="submit">, que admite iconos y contenido dentro.
🗣«Pon al final del formulario un botón de enviar bien visible que valide los campos antes de mandar nada.»
type="reset" — restablecer
Devuelve todos los campos del formulario a su valor inicial. Úsalo con cuidado: un clic accidental borra todo lo escrito y frustra al usuario. En formularios largos, mejor evitarlo o pedir confirmación.
<input type="reset" value="Vaciar formulario">
🗣«Añade un botón secundario para vaciar el formulario, menos llamativo que el de enviar y que pida confirmación antes de borrar.»
type="button" — botón neutro
Un botón que no envía nada por sí mismo: espera a que JavaScript le dé un trabajo (abrir un menú, sumar una cantidad…). Dentro de un <form>, todo botón sin type es «submit» por accidente: decláralo siempre.
<input type="button" value="+1">
<!-- o su forma moderna -->
<button type="button">+1</button>
Contador de barras: cada clic suma
🗣«Pon un botón que al pulsarlo sume una unidad al contador, sin enviar el formulario.»
type="image" — botón-imagen
Una imagen que actúa como botón de envío; además manda las coordenadas exactas del clic. Es una reliquia de los mapas de imagen: hoy casi siempre es mejor un <button> con un icono dentro, pero conviene reconocerla al leer código antiguo.
🗣«Explícame la diferencia entre un botón-imagen antiguo y un botón moderno con icono, y usa la opción moderna en mi formulario.»
Los demás controles de formulario
<textarea> — texto de varias líneas
El folio en blanco del formulario: para mensajes, comentarios y cualquier texto largo. rows sugiere su altura inicial y maxlength limita los caracteres. A diferencia del input, se cierra con etiqueta de cierre, y lo que pongas entre ambas es su contenido inicial.
🗣«Añade un cuadro grande para escribir el mensaje, con límite de 500 caracteres y un contador en vivo de los que quedan.»
<select>, <option> y <optgroup> — el desplegable
Una lista cerrada de opciones donde elegir, como la carta de un restaurante: solo puedes pedir lo que está. <optgroup> agrupa opciones bajo subtítulos («Panes», «Dulces»); selected marca la opción inicial y multiple permite elegir varias (con Ctrl). Consejo: para 2-4 opciones, los radios se ven de un vistazo; el desplegable gana cuando la lista es larga.
🗣«Pon un desplegable para elegir el producto, con las opciones agrupadas en panes y dulces y una opción marcada por defecto.»
<button> — el botón moderno
El elemento estrella para acciones. Sus tres personalidades según type: submit (envía el formulario — es el valor por defecto, ¡cuidado!), reset (restablece) y button (neutro, para JavaScript). Admite texto, iconos y HTML dentro, y es accesible por teclado sin hacer nada. Regla: si lleva a otra página es un enlace <a>; si hace algo, es un botón.
🗣«Crea un botón principal bien destacado, otro secundario más discreto y muéstrame cómo queda uno deshabilitado.»
<datalist> — sugerencias mientras escribes
El híbrido entre campo libre y desplegable: el usuario puede escribir lo que quiera, pero recibe sugerencias que se filtran con cada letra. Se conecta al input con list apuntando al id del datalist. Autocompletado nativo sin una línea de JavaScript.
🗣«Haz que el campo de ciudad sugiera opciones mientras se escribe, pero permitiendo también escribir una que no esté en la lista.»
<output> — el resultado de un cálculo
La pantalla de la calculadora: un hueco donde mostrar el resultado que depende de otros campos. Los lectores de pantalla lo tratan como región viva: anuncian el cambio sin que el usuario tenga que ir a buscarlo.
🗣«Haz que al cambiar la cantidad se recalcule el precio total automáticamente y se muestre al lado, sin recargar la página.»
<progress> — la barra de progreso
Cuánto queda de una tarea: subir un archivo, completar un formulario por pasos. value es lo hecho y max el total. Sin value se vuelve «indeterminada»: se anima sola para decir «trabajando, no sé cuánto queda».
🗣«Muestra una barra de progreso mientras se sube el archivo, que avance de verdad y anuncie cuando llegue al cien por cien.»
<meter> — el medidor de nivel
Un valor dentro de una escala conocida, como el indicador de gasolina: no mide avance sino estado. Con low, high y optimum el navegador lo colorea solo (verde/ámbar/rojo). Ideal para fuerza de contraseña, ocupación de aforo o espacio en disco. No confundir con <progress>: progress avanza, meter mide.
<label for="aforo">Aforo del taller</label>
<meter id="aforo" value="18" min="0" max="20"
low="10" high="16" optimum="5">18 de 20</meter>
Demo en vivo
Aforo casi lleno (18/20):18/20
Aforo tranquilo (4/20):4/20
🗣«Añade un medidor de fuerza de la contraseña que cambie de color según sea débil, aceptable o fuerte.»
Atributos de los controles
Los atributos son los ajustes finos de cada campo: qué es obligatorio, qué formato se admite, qué teclado sale en el móvil… Aquí están todos los importantes, agrupados por lo que hacen.
Reglas de validación: required, minlength, maxlength, min, max, step, pattern
Las normas que un campo exige antes de dejarte enviar, como el portero de una sala: required (no puede quedar vacío), minlength/maxlength (largo del texto), min/max/step (límites y salto de los números y fechas) y pattern (un patrón exacto, tipo «5 dígitos» para el código postal). El navegador comprueba todo esto solo, sin JavaScript.
🗣«Haz obligatorio el código postal, que solo admita exactamente cinco dígitos y que el navegador explique el formato cuando esté mal.»
Estado y contenido: value, placeholder, readonly, disabled, checked, selected, multiple, size
Definen cómo llega el campo al usuario: value (contenido inicial), placeholder (pista gris que desaparece al escribir — nunca sustituye al label), readonly (se ve y se envía, pero no se edita), disabled (apagado: ni se edita ni se envía), checked/selected (opción marcada de inicio), multiple (varias selecciones o archivos) y size (ancho visible en caracteres). La diferencia readonly/disabled importa: el número de pedido es readonly (debe enviarse), el cupón caducado está disabled (no debe contar).
🗣«Muestra el número de pedido en un campo que se vea pero no se pueda editar, y deja el campo del cupón desactivado hasta que haya promociones.»
Ayudas de escritura: inputmode, autocomplete, autofocus, list, accept, capture
Hacen que escribir cueste menos: inputmode elige el teclado del móvil sin cambiar el tipo de campo (numeric para el código postal), autocomplete le dice al navegador qué dato es para que lo rellene él (name, email, postal-code…), autofocus coloca el cursor en un campo al cargar (úsalo solo en páginas cuyo único fin es ese formulario), list conecta con un <datalist> de sugerencias, y accept/capture controlan qué archivos se admiten y si se abre la cámara.
🗣«Configura los campos para que en el móvil salga el teclado adecuado en cada uno y el navegador pueda autocompletar el nombre, el correo y la dirección.»
Controles fuera del formulario: form, formaction, formmethod, formnovalidate, formtarget
Casos avanzados que conviene conocer: el atributo form permite que un campo o botón viva fuera del <form> pero pertenezca a él (útil en maquetas complejas), y los atributos formaction/formmethod/formnovalidate/formtarget en un botón concreto sobrescriben la configuración del formulario solo para ese botón. El uso clásico: un botón «Guardar borrador» que envía a otra dirección y sin validar, junto al «Enviar» normal.
<form id="pedido" action="/enviar" method="post">
<input type="text" name="nombre" required>
<button type="submit">Enviar</button>
<button type="submit" formaction="/borrador"
formnovalidate>Guardar borrador</button>
</form>
<!-- Este campo está FUERA pero pertenece al formulario -->
<input type="text" name="nota" form="pedido">
Demo en vivo
🗣«Añade junto al botón de enviar otro de guardar borrador que no valide los campos y mande los datos a otra dirección.»
Validación nativa de HTML
Cómo valida el navegador (y cómo se conecta con CSS y JS)
Cuando pulsas «Enviar», el navegador repasa cada campo contra sus reglas (required, type, pattern…). Si algo falla, bloquea el envío, enfoca el primer campo erróneo y muestra un globo con el mensaje en el idioma del navegador. Ese estado se refleja en CSS (:valid / :invalid pintan el campo) y JavaScript puede leerlo y personalizarlo con la API de validación (checkValidity(), setCustomValidity(), la propiedad validity). La estrategia profesional tiene tres capas: HTML valida lo básico, JS mejora los mensajes en tiempo real y el servidor revalida siempre (la única capa que un tramposo no puede saltarse).
<form>
<label for="correo-v">Correo</label>
<input type="email" id="correo-v" name="correo" required>
<button type="submit">Enviar</button>
</form>
<!-- Sin JS: el navegador bloquea, enfoca y explica solo -->
Demo en vivo
El globo de error que ves lo genera tu navegador, en su idioma, sin una línea de JavaScript.
🗣«Valida el formulario en tres capas: reglas nativas de HTML, mensajes personalizados en tiempo real con JavaScript, y déjame comentado dónde iría la validación del servidor.»
Accesibilidad en formularios (ARIA)
ARIA es un juego de atributos que completa lo que HTML no puede decir por sí solo. La primera regla de ARIA es no usar ARIA cuando HTML ya lo hace: un <button> no necesita role="button". Se usa solo para lo que falta.
aria-label y aria-describedby — nombre y descripción
aria-label da nombre a un control que no tiene texto visible: el botón que solo es una ✕ o una lupa. Sin él, el lector de pantalla dice «botón» a secas. aria-describedby añade información extra apuntando al id de otro texto: la ayuda «mínimo 8 caracteres» bajo la contraseña se lee justo después del nombre del campo. Nombre = qué es; descripción = detalles.
<button type="button" aria-label="Cerrar ventana">✕</button>
<label for="clave2">Contraseña</label>
<input type="password" id="clave2" name="clave"
aria-describedby="clave2-ayuda">
<p id="clave2-ayuda">Mínimo 8 caracteres, con una mayúscula
y un número.</p>
Demo en vivo
← Un lector de pantalla dice «Cerrar ventana, botón», no solo «✕».
Mínimo 8 caracteres, con una mayúscula y un número.
🗣«Pon nombre accesible a los botones que solo tienen icono y asocia el texto de ayuda de cada campo para que el lector de pantalla lo lea al entrar.»
aria-invalid, aria-live y role — estados y anuncios
Tres piezas para que los cambios dinámicos no pasen desapercibidos: aria-invalid="true" marca oficialmente un campo como erróneo (los lectores lo anuncian al entrar); aria-live="polite" convierte una zona en «altavoz»: cualquier texto que aparezca ahí se lee en voz alta sin mover el foco — perfecto para «Formulario enviado ✓» o los errores de validación; y role reetiqueta un elemento solo cuando no existe etiqueta HTML nativa para ese papel (por ejemplo, role="alert" para un aviso urgente que debe leerse ya). Si existe la etiqueta nativa, úsala y olvida el role.
<input type="email" id="c3" name="correo"
aria-invalid="true" aria-describedby="c3-err">
<p id="c3-err" role="alert">El correo no es válido.</p>
<!-- Zona de anuncios: lo que aparezca aquí, se lee -->
<p aria-live="polite" id="estado-envio"></p>
Demo en vivo
Al pulsar, el mensaje aparece en una región aria-live: quien use lector de pantalla lo oye sin tener que buscarlo.
🗣«Haz que los errores y el mensaje de éxito del formulario se anuncien automáticamente a los lectores de pantalla, marcando además cada campo erróneo como inválido.»
4CSS — el aspecto
Si HTML es el esqueleto, CSS es la piel, la ropa y la forma de moverse: colores, tamaños, disposición y animación. Mismo patrón de tarjetas: explicación llana, código, demo visual en vivo y la frase 🗣 para pedirlo.
Anatomía y cascada
La regla CSS: selector, propiedad y valor
Toda regla CSS es una frase con la misma gramática: a quién (el selector), qué ajustar (la propiedad) y cómo dejarlo (el valor). «Todos los botones (selector) tendrán fondo (propiedad) verde (valor)». Las declaraciones van entre llaves y cada una termina en punto y coma.
🗣«Dale a todos los botones fondo verde oscuro, letra blanca y esquinas redondeadas.»
Cascada y especificidad: quién gana cuando dos reglas chocan
Cuando dos reglas quieren pintar el mismo elemento, gana la más específica: el ID (#) puede más que la clase (.), y la clase puede más que la etiqueta. A igual especificidad, gana la que está escrita más abajo (por eso «cascada»). Es como las señales de tráfico: la norma general cede ante la señal de la calle concreta, y esta ante la del guardia. Consejo profesional: trabaja casi siempre con clases y mantén la especificidad baja; los ID y !important son atajos que luego se pagan.
p { color: gray; } /* especificidad baja */
.aviso { color: #b3762a; } /* la clase gana a la etiqueta */
#urgente { color: #b3362c; } /* el ID gana a la clase */
Demo en vivo
Párrafo normal: me pinta la regla de etiqueta (gris).
Tengo la clase .aviso: la clase gana a la etiqueta (ámbar).
Tengo clase Y el id #urgente: el ID gana a todo (rojo).
🗣«Revisa por qué este texto no coge el color que le puse y arréglalo bajando la especificidad, sin usar !important.»
Selectores
Selectores básicos: etiqueta, clase, ID, universal y atributo
Cinco formas de señalar «a quién»: la etiqueta (p) alcanza a todos los párrafos; la clase (.destacado) a los que tú marques — es la herramienta del día a día; el ID (#cabecera) a un único elemento; el universal (*) a todo; y el de atributo ([type="email"]) a los que tengan cierto atributo o valor. Pulsa cada selector en la demo y mira a quién selecciona.
p { } /* todos los <p> */
.destacado { } /* clase: los marcados por ti */
#unico { } /* ID: exactamente uno */
* { } /* todo */
[type="email"] { } /* por atributo */
Demo interactiva — pulsa un selector
Un párrafo normal.
Un párrafo con la clase .destacado.
Párrafo con clase y además el id #unico.
🗣«Aplica el estilo destacado solo a los elementos que yo marque con una clase, sin tocar el resto de párrafos.»
Combinadores: descendiente, hijo >, hermanos + y ~
Seleccionan según el parentesco, como un árbol genealógico: el espacio (article p) alcanza a cualquier descendiente, por profundo que esté; > solo a los hijos directos; + al hermano pegado justo después; y ~ a todos los hermanos posteriores. Pruébalos en la demo.
article p { } /* p dentro de article (a cualquier nivel) */
article > p { } /* p hijo directo de article */
h4 + p { } /* el p inmediatamente después de un h4 */
h4 ~ p { } /* todos los p hermanos posteriores al h4 */
Demo interactiva — pulsa un selector
article
— un h4 (título)
— p hermano justo después del h4
— otro p hermano posterior
div
— p nieto (dentro del div)
🗣«Haz que el primer párrafo que va justo después de cada título se vea como entradilla, más grande y en gris, sin añadir clases a mano.»
Atajos con lógica: :is(h1, h2, h3) agrupa varios selectores en uno; :where() hace lo mismo pero sin sumar especificidad (ideal para estilos base fáciles de sobrescribir); :not(.vendido) selecciona a los que NO cumplen algo; y :has() es el histórico «selector de padre»: article:has(img) selecciona los artículos que contienen una imagen. :has() funciona en todos los navegadores modernos (desde finales de 2023); para páginas que deban soportar navegadores viejos, comprueba el soporte con @supports.
:is(h1, h2, h3) { line-height: 1.2; }
.lista li:not(.vendido) { opacity: 1; }
/* tarjetas que contienen imagen: a dos columnas */
article:has(img) { display: grid; }
Demo interactiva — pulsa un selector
Hogaza clásica (clase .a)
Croissant — .vendido (agotado)
Baguette (clase .b) ¡novedad!
Palmera, sin clase especial
🗣«Atenúa todos los productos menos los agotados, y resalta solo las tarjetas que contengan la etiqueta de novedad, sin añadir clases nuevas al HTML.»
Pseudo-clases: estados y posiciones
Estados de interacción: :hover, :focus, :focus-visible, :active, :visited
Un mismo elemento vive varios momentos y cada uno puede tener su estilo: :hover (el ratón está encima), :focus (está seleccionado, por clic o teclado), :focus-visible (el foco llegó por teclado — es donde debe verse el anillo de foco, sin molestar al clic), :active (justo mientras se pulsa) y :visited (enlace ya visitado). Estos estados son el «lenguaje corporal» de la interfaz: dicen «soy pulsable», «estás aquí», «te he oído».
🗣«Dale al botón sus cuatro estados: que se oscurezca con el ratón encima, que se hunda un poco al pulsarlo y que al navegar con teclado se vea un anillo de foco claro.»
Estados de formulario: :checked, :disabled, :required, :valid, :invalid y compañía
Los campos también cuentan su vida a CSS: :checked (casilla o radio marcados), :disabled (apagado), :required/:optional (obligatorio o no), :valid/:invalid (¿cumple sus reglas ahora mismo?), :placeholder-shown (aún no se ha escrito nada), :read-only (no editable) e :indeterminate (el estado «ni sí ni no» de una casilla madre con hijas a medias). Con ellos, el formulario da feedback visual sin JavaScript.
input:invalid:not(:placeholder-shown) {
border-color: #b3362c; /* error solo tras escribir */
}
input:valid:not(:placeholder-shown) {
border-color: #2c7a3f; /* verde cuando está bien */
}
input[type="checkbox"]:checked + span { font-weight: 600; }
Demo en vivo
🗣«Que cada campo se ponga con borde verde cuando esté bien rellenado y rojo cuando esté mal, pero solo después de que el usuario haya empezado a escribir.»
Posición en la familia: :first-child, :last-child, :nth-child()
Seleccionan por orden de nacimiento: el primero, el último, o siguiendo un patrón — :nth-child(odd) los impares (el clásico «cebra» de las tablas), :nth-child(3n) cada tercero. Sirven para dar estilo según posición sin ensuciar el HTML con clases.
Si las pseudo-clases seleccionan momentos, los pseudo-elementos crean o estilizan partes: ::before y ::after añaden contenido decorativo antes y después (etiquetas, iconos, flechas) sin tocar el HTML; ::placeholder da estilo al texto de pista; ::selection cambia el color al seleccionar texto; y ::marker personaliza las viñetas y números de las listas.
Selecciona este texto con el ratón: la selección va en color de acento.
Viñeta con ::marker en color de acento
Otra viñeta personalizada
🗣«Ponle a cada producto nuevo una estrellita delante y a las ofertas el porcentaje de descuento detrás, sin que yo tenga que escribirlo en el HTML.»
Variables CSS (custom properties)
--variable y var() — el sistema visual en un solo sitio
Las variables CSS son el bote de pintura etiquetado: defines una vez «--color-acento: verde» y lo usas en cien sitios con var(--color-acento). ¿Cambio de imagen corporativa? Tocas una línea y toda la web se repinta. Se declaran normalmente en :root (toda la página) y son la base de los temas claro/oscuro: cada tema redefine las mismas variables con otros valores — exactamente lo que hace esta guía. La demo usa variables de verdad: cambia el acento y observa.
Un solo cambio de variable repinta borde, título y botón a la vez.
🗣«Define los colores, espaciados y radios de toda la web como variables CSS en un solo sitio, de forma que cambiar la paleta entera sea tocar tres líneas.»
Layout: Flexbox y Grid
Flexbox — alinear en una dirección
Flexbox coloca elementos en fila o en columna y reparte el espacio, como ordenar libros en una balda: puedes pegarlos a la izquierda, centrarlos, separarlos por igual… display: flex activa el modo en el contenedor; justify-content reparte en el eje principal, align-items alinea en el perpendicular, flex-direction elige fila o columna y gap pone la separación. Es la herramienta para barras de navegación, filas de botones y cualquier alineación «en una dirección». Pulsa los valores y observa el efecto real:
🗣«Coloca el logo a la izquierda y el menú a la derecha en la misma línea, centrados en vertical, y que en móvil se apilen en columna.»
Grid — la cuadrícula en dos direcciones
Grid dibuja una cuadrícula de filas y columnas y coloca cada pieza en su celda, como una estantería con casillas: la herramienta para galerías de productos, paneles y la estructura general de la página. grid-template-columns define las columnas (1fr = una parte proporcional del espacio); el mágico repeat(auto-fit, minmax(120px, 1fr)) crea columnas que se recolocan solas según el ancho — responsive sin media queries. Una pieza puede ocupar varias celdas con grid-column: span 2. Regla mental: alineación en una dirección → Flexbox; cuadrícula en dos → Grid.
🗣«Monta la galería de productos en una cuadrícula que en el ordenador muestre tres columnas y en el móvil una, con la primera tarjeta ocupando el doble de ancho.»
Responsive: adaptarse a cada pantalla
@media — media queries y breakpoints
Una media query es una regla condicional: «si la pantalla mide al menos tanto, aplica estos estilos». Los anchos donde el diseño cambia se llaman breakpoints; unos razonables: móvil (hasta 599 px), tablet (600–899 px) y escritorio (900 px en adelante). La estrategia profesional es móvil primero: los estilos base son para pantalla pequeña y las media queries van añadiendo mejoras al crecer — así el móvil, que es el entorno más exigente, nunca queda como un remiendo. Esta misma guía funciona así.
Estrecha o ensancha la ventana del navegador (o gira el móvil) y verás cambiar la etiqueta: la pinta una media query real, sin JavaScript.
🗣«Diseña primero para móvil y añade puntos de corte para tablet y ordenador, de forma que la galería pase de una a dos y a tres columnas.»
Unidades relativas: rem, em, %, vw, ch
Los píxeles son centímetros fijos; las unidades relativas son «tallas» que se adaptan: rem se mide respecto al tamaño de letra base de la página (si el usuario la agranda en ajustes, todo escala con ella — por eso es la unidad reina para textos y espaciados), em respecto al elemento actual, % respecto al padre, vw/vh respecto a la ventana (50vw = mitad del ancho de pantalla) y ch respecto al ancho de un carácter (ideal para limitar líneas de texto a ~65ch, el largo cómodo de lectura).
html { font-size: 100%; } /* respeta el ajuste del usuario */
h1 { font-size: 2rem; } /* 2 × la base */
p { max-width: 65ch; } /* líneas cómodas de leer */
.panel { width: 90%; } /* fluido respecto al padre */
Demo en vivo
200px— fijo: no se adapta
50%— la mitad de su contenedor
20rem— crece si el usuario agranda la letra
30vw— un tercio del ancho de la ventana
🗣«Usa unidades relativas en textos y espaciados para que la web escale bien si el usuario agranda la letra, y limita los párrafos a un ancho cómodo de lectura.»
Reglas @ y preferencias del usuario
@supports — preguntar antes de usar
Es el «¿tu navegador sabe hacer esto?» de CSS: el bloque solo se aplica si el navegador soporta la propiedad o el selector indicado. Permite usar novedades con red de seguridad: primero escribes la versión clásica para todos y luego, dentro de @supports, la mejora moderna para quien pueda.
/* base para todos los navegadores */
.tarjeta { border: 1px solid gray; }
/* mejora solo si :has() está soportado */
@supports selector(:has(a)) {
.tarjeta:has(img) { border-color: teal; }
}
Demo en vivo
Si ves este recuadro en verde con un ✓, tu navegador soporta :has() — lo ha comprobado @supports, no JavaScript.
🗣«Usa esa propiedad moderna de CSS, pero con una alternativa clásica para navegadores que no la soporten, comprobándolo con @supports.»
@keyframes y animation — animaciones con guion
Mientras una transición va «de A a B», una animación sigue un guion completo por fotogramas clave: qué pasa al 0 %, al 50 %, al 100 %. Se define con @keyframes y se asigna con animation (duración, ritmo, repeticiones). Para spinners de carga, apariciones suaves y pequeños acentos — siempre con moderación y respetando la preferencia de movimiento reducido.
🗣«Añade un indicador de carga animado y haz que las tarjetas aparezcan con un fundido suave, desactivando las animaciones para quien tenga configurado movimiento reducido.»
prefers-color-scheme y prefers-reduced-motion — escuchar al sistema
El sistema operativo del usuario ya sabe dos cosas importantes: si prefiere tema oscuro y si el movimiento le marea. CSS puede preguntarlo con dos media queries: prefers-color-scheme: dark (aplicar la paleta oscura por defecto) y prefers-reduced-motion: reduce (apagar animaciones y desplazamientos suaves). Respetarlas es señal inequívoca de trabajo profesional; esta guía lo hace en toda la página.
Valores leídos de tu sistema ahora mismo. Cámbialos en los ajustes de tu dispositivo y recarga para verlos cambiar.
🗣«Haz que la web arranque en tema oscuro si el sistema del usuario lo prefiere, y que todas las animaciones se desactiven si tiene activado el movimiento reducido.»
Estilado completo de formularios
Inputs, selects, checkboxes y mensajes con estilo propio
Los controles nativos llegan con el «uniforme» de cada navegador; vestirlos con tu diseño es lo que da aspecto de producto: bordes y radios coherentes, foco visible con el color de acento, checkboxes y radios ampliados con accent-color (una línea y adoptan tu paleta), el botón interno del input de archivo estilizado con ::file-selector-button, y mensajes de error y éxito con color + icono (nunca solo color: hay quien no distingue rojo de verde). Todos los campos de esta guía usan este sistema; la demo lo concentra.
✓Y así uno de éxito, distinguible sin ver el color.
🗣«Dale a todos los campos del formulario un estilo coherente con la web: bordes suaves, foco bien visible, casillas del color de la marca y mensajes de error con icono además de color.»
Transiciones y microinteracciones
transition — cambios con suavidad
Una transición hace que un cambio de estilo ocurra en el tiempo en vez de a saltos: el botón no «teletransporta» su color al pasar el ratón, lo funde en 200 milisegundos. Se declara qué propiedad, cuánto dura y con qué ritmo (ease, linear…). La dosis correcta: 150–300 ms en hover y foco; más de medio segundo en una interacción cotidiana ya estorba. Transiciona transform y opacity (van fluidas); evita animar anchos y altos (obligan al navegador a recalcular la página).
🗣«Añade microinteracciones sutiles: que las tarjetas se eleven un poco al pasar el ratón y los botones cambien de color con una transición suave de unos 200 milisegundos.»
Accesibilidad visual
Contraste, foco visible y tamaño táctil
Tres reglas que deciden si tu web se puede usar en el mundo real: contraste — el texto normal necesita una relación de al menos 4.5:1 con su fondo (piensa en leer la pantalla al sol); foco visible — quien navega con teclado necesita ver dónde está, así que el anillo de foco jamás se elimina (el clásico outline: none sin reemplazo es de los errores más dañinos que existen); y tamaño táctil — todo lo pulsable debe medir al menos 44×44 px, el tamaño de una yema de dedo.
Contraste insuficiente: gris sobre gris (≈2:1) — evítalo
🗣«Revisa el contraste de todos los textos para que cumplan el mínimo de accesibilidad, asegúrate de que el foco de teclado se vea siempre y de que todos los botones tengan tamaño táctil suficiente.»
5JavaScript — el comportamiento
JavaScript es lo que convierte el documento en aplicación: reacciona a clics, valida datos, pide información y cambia la página sin recargarla. Cada tarjeta trae un botón Ejecutar: el código corre de verdad en tu navegador y el resultado aparece debajo.
Fundamentos
let y const — cajas para guardar valores
Una variable es una caja con etiqueta donde guardas un valor para usarlo después. const es una caja precintada: lo que metes no se puede reemplazar (úsala por defecto; el 90 % de las veces no necesitas cambiar el valor). let es la caja abierta para lo que sí cambia (un contador, el texto que escribe el usuario). La vieja var existe, pero tiene comportamientos traicioneros: no la uses en código nuevo.
const nombre = "Lucía"; // no cambiará
let puntos = 0; // cambiará
puntos = puntos + 10; // ✓ permitido con let
// nombre = "Ana"; // ✗ error: const no se reasigna
Demo en vivo
🗣«Guarda el nombre del usuario y sus puntos en variables, usando constantes para lo que no cambie, y ve sumando puntos cada vez que acierte.»
Tipos de datos: texto, número, booleano, null y undefined
Cada valor tiene un tipo, como cada ingrediente tiene su envase: string (texto, entre comillas), number (números, con o sin decimales), boolean (solo true o false — el interruptor), null («vacío a propósito») y undefined («aún sin valor»). Conocer el tipo evita el clásico susto: "2" + 2 es "22" (pega textos), no 4.
const texto = "Hola"; // string
const precio = 3.5; // number
const abierto = true; // boolean
const descuento = null; // vacío a propósito
let proximoPedido; // undefined: sin valor aún
typeof precio; // "number"
"2" + 2; // "22" ¡cuidado: pega textos!
Number("2") + 2; // 4 ✓ convertido antes
Demo en vivo
🗣«Asegúrate de convertir a número lo que venga de los campos del formulario antes de hacer cuentas, para que no se peguen como texto.»
Operadores: aritmética, comparación y lógica
Las herramientas de calcular y decidir: aritméticos (+ - * / %, donde % es «el resto de la división»: perfecto para saber si un número es par), de comparación (usa siempre === y !==, que comparan sin hacer trampas de conversión; evita ==) y lógicos (&& «y», || «o», ! «no») para combinar condiciones: «si es socio Y el pedido supera 20 €».
const total = 3.5 * 4; // 14
const esPar = 14 % 2 === 0; // true (resto 0)
5 === "5"; // false: distinto tipo ✓ fiable
5 == "5"; // true… por eso NO se usa ==
const socio = true, pedido = 25;
const envioGratis = socio && pedido > 20; // true
Demo en vivo
🗣«Calcula el total del pedido y aplica envío gratis solo si el cliente es socio y el importe supera los 20 euros.»
Condicionales: if, else y el ternario
La bifurcación del camino: «si pasa esto, haz A; si no, haz B». if / else if / else encadena todas las alternativas que necesites. El operador ternario (condición ? A : B) es su versión de bolsillo para elegir entre dos valores en una línea. Prueba la demo con tu edad:
if (edad < 18) {
mensaje = "Menú infantil disponible";
} else if (edad >= 65) {
mensaje = "Descuento senior aplicado";
} else {
mensaje = "Menú estándar";
}
// Ternario: dos opciones en una línea
const saludo = esDeDia ? "Buenos días" : "Buenas noches";
Demo en vivo
🗣«Según la edad que escriba el usuario, muéstrale el menú infantil, el estándar o el descuento senior, cada uno con su mensaje.»
Bucles: for, for...of y while
Repetir sin copiar y pegar: el bucle es la cadena de montaje. for clásico cuando controlas el contador; for...of para recorrer una lista elemento a elemento («para cada producto de la lista…»), y while cuando no sabes cuántas vueltas harán falta («mientras quede masa, haz panes»).
// for clásico: contador bajo control
for (let i = 1; i <= 3; i++) {
console.log("Horneada nº " + i);
}
// for...of: recorre una lista
const panes = ["hogaza", "baguette", "centeno"];
for (const pan of panes) {
console.log("Listo: " + pan);
}
Demo en vivo
🗣«Recorre la lista de productos y crea una tarjeta en la página por cada uno, sin repetir código a mano.»
Funciones y arrow functions — recetas con nombre
Una función es una receta guardada: la escribes una vez y la ejecutas cuando quieras, con distintos ingredientes (parámetros) y un resultado (return). Las arrow functions (=>) son la escritura moderna y compacta, omnipresente en el código actual: mismas ideas, menos ceremonia. Si un dato de entrada puede faltar, dale un valor por defecto.
🗣«Organiza el código en funciones pequeñas y reutilizables con nombres claros, cada una con una sola responsabilidad, y usa la sintaxis moderna de flecha.»
Arrays y sus métodos: map, filter, find, forEach
Un array es una lista ordenada de valores, y sus métodos son operaciones de cinta transportadora: map transforma cada elemento y devuelve la lista transformada (precios → precios con IVA); filter deja pasar solo los que cumplen la condición (solo los disponibles); find devuelve el primero que encaje (busca por nombre); y forEach hace algo con cada uno sin devolver nada (pintarlos en pantalla). Encadenados, son media programación moderna: productos.filter(...).map(...).
🗣«De la lista de productos, quédate solo con los disponibles, súbeles el IVA y muéstralos ordenados por precio.»
Objetos — fichas con propiedades
Si el array es una lista, el objeto es una ficha: pares de nombre y valor que describen una cosa (un cliente tiene nombre, correo y si es socio). Se accede con punto (cliente.nombre) y la desestructuración extrae varias propiedades de golpe. La combinación estrella del desarrollo real: un array de objetos — la lista de fichas.
🗣«Guarda los datos de cada cliente como un objeto con nombre, correo y si es socio, y muéstrame cómo leer y actualizar sus propiedades.»
Template literals — texto con huecos
Las comillas invertidas (`) crean textos con huecos que se rellenan solos: ${variable} inserta el valor dentro de la frase. Adiós a pegar trocitos con +. Además respetan los saltos de línea, perfectos para generar HTML desde JavaScript.
const nombre = "Lucía";
const total = 12.5;
// Antes (pegando con +):
const v1 = "Hola " + nombre + ", total: " + total + " €";
// Ahora (template literal):
const v2 = `Hola ${nombre}, total: ${total} €`;
const v3 = `Con cálculo dentro: ${total * 1.21} € con IVA`;
Demo en vivo
🗣«Monta el mensaje de confirmación insertando el nombre del cliente y el total dentro de la frase, con la sintaxis moderna de plantillas.»
try / catch — la red de seguridad
Cuando una operación puede fallar (interpretar datos, llamar a un servidor), try intenta el plan A y catch recoge el error si algo estalla, en vez de dejar que rompa la página entera. Es el airbag del código: no evita el golpe, pero evita el desastre. Dentro del catch se avisa al usuario con un mensaje comprensible, no con jerga técnica.
try {
const datos = JSON.parse(textoRecibido); // puede fallar
mostrarPedido(datos);
} catch (error) {
// Plan B: avisar sin romper la página
mostrarMensaje("No pudimos leer el pedido. Inténtalo de nuevo.");
}
Demo en vivo
🗣«Envuelve las operaciones que puedan fallar en try/catch y, si algo va mal, muestra al usuario un mensaje claro en su idioma en vez de romper la página.»
Promesas y async/await — esperar sin congelar
Hay tareas que tardan (pedir datos a un servidor, leer un archivo). Una promesa es el resguardo de la tintorería: te lo dan al momento y con él recoges el resultado cuando esté — o te enteras de que algo falló. async/await es la forma moderna de usarlas: await significa «espera aquí a que llegue, sin congelar la página». Mientras tanto, el usuario ve un estado de carga. Combinado con try/catch, cubre éxito y error con naturalidad.
async function cargarPedido() {
mostrarCargando(true);
try {
const respuesta = await fetch("/api/pedido"); // espera
const datos = await respuesta.json(); // espera
mostrarPedido(datos); // éxito
} catch (error) {
mostrarError("No se pudo cargar el pedido"); // fallo
} finally {
mostrarCargando(false); // siempre
}
}
Demo en vivo
🗣«Cuando pidas los datos al servidor, muestra un indicador de carga mientras tanto, usa async/await y contempla también el caso de que la petición falle.»
El DOM: JavaScript toca la página
¿Qué es el DOM? El árbol vivo de la página
Cuando el navegador lee tu HTML, construye en memoria un árbol genealógico de la página: el DOM (Document Object Model). Cada etiqueta es una rama; <body> es el tronco y cada párrafo, botón o imagen cuelga de su padre. JavaScript no edita tu archivo HTML: edita este árbol en vivo, y el navegador repinta al instante. Por eso una página puede cambiar sin recargarse.
// El árbol, simplificado:
// document
// └── html
// ├── head → title, meta…
// └── body
// ├── header → h1, nav
// ├── main → section, p, form…
// └── footer
document.title; // lee del árbol
document.body.children; // las ramas hijas del body
Demo en vivo
🗣«Explícame con un dibujo cómo queda el árbol DOM de mi página y qué partes va a tocar el JavaScript que me propones.»
querySelector y querySelectorAll — encontrar elementos
Los prismáticos para localizar ramas del árbol: usan la misma sintaxis que los selectores CSS que ya conoces. querySelector devuelve el primer elemento que encaja; querySelectorAll devuelve todos (una lista que se recorre con forEach). Encontrar el elemento es siempre el paso 1: después se lee, se cambia o se escucha.
🗣«Localiza todas las tarjetas de producto de la página y aplícales el cambio a la vez, usando selectores como los de CSS.»
Crear, modificar y eliminar elementos
JavaScript puede fabricar ramas nuevas y colgarlas del árbol: createElement crea el elemento, textContent le pone el texto (siempre seguro), append lo cuelga y remove() lo poda. Nota de seguridad: para insertar texto del usuario usa textContent, no innerHTML — lo segundo interpretaría HTML y abriría la puerta a inyecciones de código.
// Crear y colgar
const li = document.createElement("li");
li.textContent = "Hogaza de espelta";
lista.append(li);
// Modificar
li.textContent = "Hogaza de espelta (¡última!)";
// Eliminar
li.remove();
Demo en vivo
Pan inicial de la lista
🗣«Haz que al pulsar el botón se añada el producto a la lista de la página, y que cada elemento tenga su botón para quitarlo.»
classList — el interruptor de estilos
La forma correcta de cambiar el aspecto desde JavaScript: en vez de tocar estilos uno a uno, JS pone o quita clases y CSS decide cómo se ve cada una. add, remove y sobre todo toggle (pon/quita del tirón: ideal para abrir menús, marcar favoritos, temas claro/oscuro). Así cada tecnología hace su trabajo: JS decide «cuándo», CSS decide «cómo se ve».
🗣«Haz que el botón encienda y apague el modo destacado de la tarjeta alternando una clase, con el estilo definido en CSS.»
Eventos
addEventListener — escuchar lo que pasa
Un evento es cualquier cosa que ocurre: un clic, una tecla, el envío de un formulario. addEventListener pone un timbre en el elemento: «cuando pase esto, ejecuta esta función». La función receptora recibe el objeto event con todos los detalles (qué elemento fue, qué tecla, dónde estaba el ratón). Es el corazón de toda interactividad.
🗣«Cuando el usuario pulse el botón de añadir al carrito, ejecuta la función que suma el producto y muestra la confirmación.»
Delegación de eventos — un portero para todo el edificio
En vez de poner un timbre en cada piso (un listener por botón), pones un portero en la entrada: un único listener en el contenedor padre que, mirando event.target, sabe qué hijo fue pulsado. Dos ventajas enormes: funciona automáticamente con elementos añadidos después, y consume menos memoria en listas largas. Esta guía la usa para todos sus botones de copiar.
const lista = document.querySelector("#lista-panes");
// UN listener para todos los botones, presentes y futuros
lista.addEventListener("click", (event) => {
const boton = event.target.closest("button");
if (!boton) return; // el clic no fue en un botón
console.log("Elegiste:", boton.textContent);
});
Demo en vivo
🗣«Gestiona los clics de toda la lista con un solo escuchador en el contenedor, de forma que los elementos que se añadan luego funcionen sin código extra.»
Eventos de formulario: submit, input, change, focus, blur
Cinco momentos clave en la vida de un formulario: submit salta al enviar (con event.preventDefault() frenas la recarga y tomas el control); input, con cada letra que se escribe (validación en vivo, contadores); change, al terminar un cambio (elegir en un desplegable, soltar un deslizador); focus y blur, al entrar y salir del campo — blur es el momento educado para validar: cuando el usuario ya terminó de escribir.
formulario.addEventListener("submit", (e) => {
e.preventDefault(); // frena la recarga
// validar y enviar con fetch…
});
campo.addEventListener("input", () => { /* cada letra */ });
campo.addEventListener("blur", () => { /* al salir: validar */ });
Demo en vivo
🗣«Valida cada campo cuando el usuario salga de él, actualiza el contador con cada letra y toma el control del envío para mandarlo sin recargar la página.»
Eventos de teclado: keydown y event.key
keydown avisa de cada tecla pulsada y event.key dice cuál fue («Enter», «Escape», «ArrowDown», «a»…). Con ellos se construyen atajos de teclado y componentes navegables sin ratón: cerrar ventanas con Escape, moverse por sugerencias con las flechas. Regla de accesibilidad: todo lo que se pueda hacer con ratón debe poderse hacer con teclado.
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") cerrarModal();
if (e.key === "ArrowDown") seleccionarSiguiente();
});
Demo en vivo
🗣«Haz que la ventana se cierre con la tecla Escape y que por las sugerencias del buscador se pueda navegar con las flechas y elegir con Enter.»
Formularios con JavaScript
Validación personalizada en tiempo real
La validación nativa funciona, pero sus globos son genéricos y desaparecen. La mejora profesional: escuchar blur e input, comprobar el campo con la API de validación (campo.validity dice exactamente qué falla: valueMissing, typeMismatch, tooShort…) y escribir un mensaje propio, claro y en tu idioma, debajo del campo. El mensaje llega por aria-describedby al lector de pantalla y el envío se frena con checkValidity() si algo sigue mal. La demo valida al salir del campo y reevalúa con cada letra:
campo.addEventListener("blur", () => {
const err = campo.parentElement.querySelector(".error");
if (campo.validity.valueMissing) {
err.textContent = "Este campo es obligatorio.";
} else if (campo.validity.typeMismatch) {
err.textContent = "Ese correo no tiene buena pinta.";
} else {
err.textContent = ""; // todo correcto
}
campo.setAttribute("aria-invalid", !campo.checkValidity());
});
Demo en vivo
🗣«Valida los campos en tiempo real con mensajes propios en español debajo de cada uno: al salir del campo si está mal, y quitando el error en cuanto lo corrija.»
FormData — recoger todo el formulario de una vez
En lugar de leer los campos uno a uno, new FormData(formulario) recoge todos los valores de golpe, como vaciar el buzón entero: cada campo con name aporta su valor, los checkboxes marcados aparecen (los desmarcados no), los select múltiples y archivos también. Con Object.fromEntries() se convierte en un objeto normal listo para enviar como JSON. Por eso el atributo name es sagrado: sin él, el campo no existe para FormData.
formulario.addEventListener("submit", (e) => {
e.preventDefault();
const datos = new FormData(formulario);
const objeto = Object.fromEntries(datos);
// varios valores con el mismo name (checkboxes):
const extras = datos.getAll("extras");
console.log(objeto, extras);
});
Demo en vivo
🗣«Al enviar, recoge todos los campos del formulario con FormData, conviértelos a JSON y muéstrame por consola lo que se enviaría al servidor.»
Normalizar los datos antes de usarlos
Los usuarios escriben « LUCÍA@Gmail.COM » y tu sistema espera «lucia@gmail.com». Normalizar es pasar el dato por el túnel de lavado antes de validarlo o guardarlo: trim() quita espacios de los extremos, toLowerCase() unifica mayúsculas (correos), replaceAll limpia separadores (teléfonos). Ahorra errores absurdos («este correo ya existe» por una mayúscula) y datos sucios en la base de datos.
🗣«Antes de validar o guardar, limpia los datos: quita espacios sobrantes, pasa los correos a minúsculas y deja los teléfonos solo con números.»
Campos condicionales — el formulario que se adapta
Un buen formulario solo pregunta lo necesario: el campo «dirección de envío» no debe aparecer hasta que se elija «envío a domicilio». Se escucha el change del control que decide, y se muestra u oculta el bloque dependiente — ocultándolo con hidden y desactivando sus campos con disabled para que no viajen ni se validen mientras no tocan. Menos campos visibles = más formularios terminados.
🗣«Muestra el campo de dirección solo si eligen envío a domicilio, y que mientras esté oculto no sea obligatorio ni se envíe.»
fetch y envío asíncrono
fetch — hablar con el servidor sin recargar
fetch es el mensajero de la página: lleva datos al servidor y trae la respuesta, todo en segundo plano y sin recargar. Sus cuatro estados son sagrados y el usuario debe ver los cuatro: cargando (botón deshabilitado y aviso «enviando…»), éxito, error (con mensaje comprensible y botón de reintentar) y vacío (cuando no hay resultados). Importante: fetch no considera error un 404 o un 500 — hay que comprobar respuesta.ok a mano. En esta demo el servidor es simulado (funciona sin conexión) y falla a propósito la primera vez para que veas el estado de error y el reintento:
async function enviar(datos) {
try {
const respuesta = await fetch("/api/pedidos", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(datos)
});
if (!respuesta.ok) throw new Error("HTTP " + respuesta.status);
return await respuesta.json(); // éxito
} catch (error) {
// sin conexión o error del servidor → reintentar
throw error;
}
}
// En esta guía el servidor se simula en local con una
// función que devuelve una promesa tras una pausa.
Demo en vivo
🗣«Envía el formulario con fetch sin recargar la página, mostrando el estado de carga, el mensaje de éxito y, si falla, un aviso claro con botón de reintentar.»
localStorage: recordar entre visitas
localStorage — la libreta del navegador
Una pequeña memoria persistente dentro del navegador: lo que guardes sigue ahí al cerrar y volver. Perfecta para preferencias (tema, idioma — esta guía guarda ambos así), borradores y carritos. Solo almacena texto, así que los objetos van con JSON.stringify al guardar y JSON.parse al leer. Dos límites importantes: es por navegador y dispositivo (tu móvil no ve lo guardado en el portátil; para eso hace falta un servidor) y no es segura para datos sensibles: nunca contraseñas.
// Guardar
localStorage.setItem("tema", "oscuro");
localStorage.setItem("carrito", JSON.stringify(items));
// Leer (null si no existe)
const tema = localStorage.getItem("tema");
const carrito = JSON.parse(localStorage.getItem("carrito")) ?? [];
// Borrar
localStorage.removeItem("carrito");
Demo en vivo
Guarda la nota, recarga la página y vuelve aquí: seguirá guardada. Solo en este navegador y este dispositivo.
🗣«Guarda el tema elegido y el borrador del formulario en el navegador, para que al volver a entrar todo siga como lo dejé, y avísame de que solo se guarda en este dispositivo.»
Accesibilidad dinámica
Foco, anuncios y teclado en contenido que cambia
Cuando JavaScript cambia la página, hay que contárselo a quien no la ve: gestión del foco — al abrir un panel, mueve el foco dentro con elemento.focus(); al cerrarlo, devuélvelo al botón de origen (si no, el usuario de teclado queda perdido en el vacío); anuncios — los mensajes que aparecen (errores, «guardado ✓») van en regiones aria-live para que el lector de pantalla los lea solo; y teclado — todo componente creado con JS (modal, menú, pestañas) debe poder abrirse, recorrerse y cerrarse sin ratón (Escape para cerrar es el mínimo). La demo enseña la gestión del foco:
function abrirPanel() {
panel.hidden = false;
panel.querySelector("input").focus(); // foco dentro
}
function cerrarPanel() {
panel.hidden = true;
botonAbrir.focus(); // foco de vuelta
}
document.addEventListener("keydown", (e) => {
if (e.key === "Escape" && !panel.hidden) cerrarPanel();
});
Demo en vivo
🗣«Cuando se abra la ventana, mueve el foco a su interior y devuélvelo al botón al cerrarla; que se pueda cerrar con Escape y que los mensajes nuevos se anuncien al lector de pantalla.»
6Demostraciones completas
Ocho formularios reales, funcionales de principio a fin: labels visibles, validación nativa reforzada con JavaScript, errores accesibles y estados de carga, éxito y error con envío simulado (nada sale de tu navegador). Cada uno trae su código desplegable y su frase 🗣. La casilla «Simular fallo del servidor» te deja provocar el estado de error a voluntad.
Iniciar sesión
El clásico: correo y contraseña, con botón para mostrar la contraseña y opción de recordar sesión.
Ver el código de este formulario
🗣«Créame un formulario de inicio de sesión con correo y contraseña, botón para ver la contraseña, casilla de recordar sesión, validación en tiempo real y estados de carga, éxito y error al enviar.»
Registro
Alta de usuario completa: confirmación de contraseña con comprobación de coincidencia, medidor de fuerza en vivo y casilla legal obligatoria.
Ver el código de este formulario
🗣«Hazme un formulario de registro accesible con nombre, correo, fecha de nacimiento y contraseña repetida dos veces que compruebe que coinciden, con un medidor de fuerza de la contraseña y la casilla obligatoria de aceptar condiciones.»
Contacto
Formulario de contacto con asunto desplegable, prioridad, mensaje con contador de caracteres y doble validación (nativa + JavaScript).
Ver el código de este formulario
🗣«Necesito un formulario de contacto con nombre, correo, asunto desplegable, prioridad y mensaje con contador de caracteres, validado dos veces: por el navegador y por JavaScript con mensajes claros bajo cada campo.»
Búsqueda con autocompletado
Buscador con sugerencias en vivo, navegables con las flechas del teclado y selección con Enter. Busca términos de esta guía: prueba con «flex», «input» o «evento».
Ver el código de este formulario
🗣«Añade un buscador que muestre sugerencias mientras escribo, que se puedan recorrer con las flechas del teclado y elegir con Enter, con mensaje claro cuando no haya resultados.»
Encuesta de satisfacción
Radios, checkboxes y un deslizador con su valor en vivo: los tres controles de opinión en un solo formulario.
Ver el código de este formulario
🗣«Monta una encuesta con una valoración de opción única, varios aspectos marcables a la vez, una nota del 0 al 10 con deslizador que muestre el número elegido y un comentario opcional.»
Checkout — finalizar compra
Resumen del carrito con cantidades editables y total recalculado en vivo, datos de envío y forma de pago. Demo educativa: no escribas datos reales de tarjeta.
Ver el código de este formulario
🗣«Créame una página de finalizar compra con el resumen del carrito y el total recalculándose al cambiar cantidades, datos de envío validados, y el campo de tarjeta apareciendo solo si se elige pagar con tarjeta, todo con envío simulado.»
Subida de archivos con vista previa
Adjunta imágenes (o arrástralas a la zona), míralas en miniatura antes de enviar y quita las que sobren. Con límite de tamaño y validación de tipo.
Ver el código de este formulario
🗣«Quiero subir varias imágenes con vista previa en miniatura antes de enviar, pudiendo arrastrarlas o elegirlas con botón, con límite de tamaño y un botón para quitar cada una.»
Formulario multi-paso
Tres pasos con barra de progreso, navegación adelante y atrás, validación por paso y resumen final antes de enviar. Los formularios largos convierten mejor troceados.
Ver el código de este formulario
🗣«Divide el formulario en tres pasos con barra de progreso, botones de siguiente y atrás, validación antes de avanzar de paso y un resumen final editable antes de enviar.»
7Catálogo de prompts listos para copiar
Peticiones completas en lenguaje natural, tal como se dicen hablando. Cada una indica qué pedir de estructura (HTML), de aspecto (CSS) y de comportamiento (JS). Copia, pega en tu conversación con la IA y ajusta los detalles entre corchetes.
Registro accesible
HTMLCSSJS
Créame un formulario de registro accesible. De estructura: campos de nombre, correo, contraseña repetida dos veces y casilla de aceptar condiciones, cada uno con su etiqueta visible y agrupados con sentido. De aspecto: diseño limpio a una columna, foco bien visible, botones grandes y estados de error y éxito claramente diferenciados con color e icono. De comportamiento: validación en tiempo real con mensajes en español bajo cada campo, comprobación de que las contraseñas coinciden, medidor de fuerza de la contraseña y envío sin recargar con estados de carga, éxito y error.
Contacto con doble validación
HTMLCSSJS
Necesito un formulario de contacto para [mi negocio]. De estructura: nombre, correo, asunto en desplegable y mensaje con límite de 800 caracteres, todos con etiquetas visibles y las reglas de obligatorio puestas en el propio HTML. De aspecto: coherente con mi web, campos cómodos en el móvil y contador de caracteres visible. De comportamiento: que JavaScript refuerce la validación nativa con mensajes propios al salir de cada campo, que el envío sea sin recargar y muestre «enviando…», confirmación de éxito y aviso de error con botón de reintentar.
Checkout con carrito
HTMLCSSJS
Quiero una página de finalizar compra. De estructura: tabla resumen del carrito con producto, precio, cantidad y subtotal; datos de envío completos con autocompletado del navegador; y formas de pago como opciones excluyentes. De aspecto: el total bien destacado, pasos numerados visualmente y todo usable con una mano en el móvil. De comportamiento: recalcular el total al cambiar cantidades, mostrar el campo de tarjeta solo si se elige tarjeta, validar el código postal, y simular el envío con sus estados de carga, éxito y error mientras no haya servidor real.
Formulario multi-paso
HTMLCSSJS
Divide mi formulario largo en tres pasos: [datos personales], [detalles del pedido] y confirmación. De estructura: cada paso en su propio grupo con título, y un resumen final con todos los datos antes de enviar. De aspecto: barra de progreso arriba que muestre en qué paso estoy y cuánto queda, con los pasos completados marcados. De comportamiento: no dejar avanzar si el paso actual tiene errores, permitir volver atrás sin perder lo escrito, llevar el foco al principio de cada paso nuevo y enviar solo desde el último con confirmación.
Login elegante
HTMLCSSJS
Hazme una pantalla de inicio de sesión elegante y sobria. De estructura: correo y contraseña con etiquetas visibles, casilla de recordar sesión y enlace de contraseña olvidada. De aspecto: tarjeta centrada con sombra suave, tema claro y oscuro, transiciones sutiles y foco de teclado siempre visible. De comportamiento: botón para mostrar u ocultar la contraseña, validación al salir de cada campo, botón deshabilitado mientras se envía con su indicador de carga, y mensaje de error genérico si las credenciales fallan (sin revelar cuál de las dos está mal).
Búsqueda con autocompletado
HTMLCSSJS
Añádeme un buscador con autocompletado para [mis productos]. De estructura: campo de búsqueda con su etiqueta, lista de sugerencias correctamente anunciada a los lectores de pantalla y zona de resultados. De aspecto: sugerencias desplegadas justo bajo el campo, con la opción activa resaltada y tamaño cómodo para el dedo. De comportamiento: filtrar sugerencias con cada letra, poder recorrerlas con las flechas y elegir con Enter o con un toque, cerrar con Escape, y mostrar un mensaje amable cuando no haya coincidencias.
Encuesta
HTMLCSSJS
Créame una encuesta de satisfacción. De estructura: una valoración de opción única con radios agrupados bajo su pregunta, varios aspectos marcables con checkboxes, una nota del 0 al 10 con deslizador y un comentario opcional. De aspecto: opciones grandes y fáciles de tocar, el valor del deslizador siempre visible y una barra de progreso si la encuesta es larga. De comportamiento: exigir solo la valoración obligatoria, mostrar el número del deslizador en vivo y, al enviar, agradecer la participación con un resumen de lo respondido.
Landing completa
HTMLCSSJS
Quiero una landing completa para [producto o servicio]. De estructura: cabecera con navegación, sección principal con titular claro y botón de acción, tres beneficios con iconos, testimonios, preguntas frecuentes desplegables sin JavaScript y pie con contacto. De aspecto: paleta sobria de un solo color de acento, tipografía del sistema, mucho espacio en blanco, tema claro y oscuro y diseño móvil primero. De comportamiento: menú hamburguesa accesible en móvil, desplazamiento suave a las secciones y el formulario de la parte final validado y con envío simulado.
Componente: tarjeta de producto
HTMLCSSJS
Diséñame una tarjeta de producto reutilizable. De estructura: imagen con su texto alternativo, nombre, precio, etiqueta opcional de oferta y botón de añadir al carrito, todo como una pieza autónoma. De aspecto: esquinas redondeadas, sombra sutil que crece un poco al pasar el ratón, precio destacado y versión coherente en tema oscuro. De comportamiento: al añadir al carrito, confirmación visual en la propia tarjeta sin recargar, contador de unidades y aviso accesible para lectores de pantalla. Dame también un ejemplo con seis tarjetas en una cuadrícula adaptable.
Componente: modal accesible
HTMLCSSJS
Créame una ventana modal accesible para [confirmar una acción]. De estructura: usa el elemento dialog nativo, con título, texto y botones de confirmar y cancelar. De aspecto: fondo oscurecido tras la ventana, aparición con fundido suave que respete el movimiento reducido y botones con buen tamaño táctil. De comportamiento: al abrirse, el foco entra a la ventana y no puede escaparse de ella con Tab; se cierra con Escape, con el botón de cancelar o pulsando fuera; y al cerrarse, el foco vuelve exactamente al botón que la abrió.
8Tabla de referencia rápida
Todas las capacidades importantes en una sola tabla filtrable: qué es cada recurso, para qué sirve, cómo pedírselo a la IA hablando, un ejemplo mínimo y sus advertencias. Escribe para filtrar o limita por categoría.
Referencia rápida de HTML, CSS y JavaScript
Recurso
Para qué sirve
Cómo pedírselo a la IA
Ejemplo
Advertencias
Ningún recurso coincide con ese filtro.
9Buenas prácticas y qué evitar
El resumen del oficio: lo que separa una página correcta de una profesional. Si pides estas cosas a la IA (o las repasas en su resultado), el salto de calidad es inmediato.
Hazlo así
Estructura con sentido
Etiquetas semánticas para cada zona, un único <h1>, jerarquía de títulos sin saltos y el contenido importante en el HTML (no inyectado todo por JS). El documento debe entenderse leyendo solo las etiquetas.
Accesibilidad desde el diseño
Labels visibles y asociadas, contraste suficiente, foco visible, tamaños táctiles de 44 px, navegación completa por teclado y ARIA solo donde HTML no llega. No es una capa final: se pide desde el primer mensaje.
Validación en tres capas
HTML pone las reglas (required, type, pattern), JavaScript mejora los mensajes y la experiencia, y el servidor revalida siempre. Cada capa cubre el fallo de la anterior.
Móvil primero
Diseña para la pantalla pequeña y mejora hacia arriba con media queries. Unidades relativas (rem, %) para que todo escale, y párrafos limitados a un ancho cómodo de lectura.
Feedback en los cuatro estados
Toda acción muestra su estado: vacío, cargando, éxito y error. Botones deshabilitados mientras se envía, mensajes con color e icono, y anuncios en regiones aria-live para lectores de pantalla.
Separación de responsabilidades
HTML dice qué es, CSS cómo se ve, JS cómo se comporta — en archivos separados. JS cambia clases, no estilos sueltos; CSS centraliza su sistema en variables. Así cada cambio se hace en un solo sitio.
Código que se puede mantener
Nombres descriptivos, funciones pequeñas con una sola tarea, configuración agrupada al principio, comentarios que explican el porqué (no el qué) y cero variables globales sueltas.
Rendimiento y respeto
Imágenes con dimensiones declaradas (sin saltos de página), animaciones solo de transform y opacity, respeto a prefers-reduced-motion y prefers-color-scheme, y nada de dependencias que no aporten.
Antipatrones: si lo ves, corrígelo
Sopa de divs:<div class="boton"> en vez de <button>. Pierde teclado, foco y semántica gratis. Usa el elemento nativo y quedará hecho.
Placeholder como etiqueta: desaparece al escribir y el usuario olvida qué iba ahí. Label visible siempre.
Quitar el foco:outline: none sin reemplazo deja ciegos a los usuarios de teclado. Si no te gusta el anillo por defecto, diséñalo, no lo borres.
Validar solo en JS: si el script falla o lo desactivan, entra todo. Las reglas nativas de HTML y el servidor no son opcionales.
ARIA decorativa: roles y atributos copiados «por si acaso» que contradicen al HTML. Cada ARIA innecesaria es un posible error nuevo.
!important como estilo de vida: señal de especificidad desbocada. Reordena selectores en lugar de gritar más fuerte.
Animarlo todo: desplazamientos largos, rebotes y destellos marean y cansan. Microinteracciones sutiles, y apagadas con prefers-reduced-motion.
Estilos en línea y JS que pinta:style="color:red" repartidos por el HTML y JS tocando estilos sueltos hacen imposible el mantenimiento. Clases + variables CSS.
Elementos obsoletos (solo para reconocerlos)
Los verás en páginas antiguas y en malos tutoriales. No se usan nunca en código nuevo; cada uno tiene su sustituto moderno:
<center> → centrar con CSS (text-align, flexbox o grid).
<font> → tipografía y color con CSS (font-family, color).
<marquee> y <blink> → si de verdad hace falta movimiento, una animación CSS moderada.
<big>, <tt>, <strike> → CSS (font-size, font-family, text-decoration) o <del> si el tachado tiene significado.
Atributos de presentación (bgcolor, align, border, width en HTML…) → todos a CSS. El HTML describe, el CSS decora.
Tablas para maquetar → flexbox y grid. Las tablas solo para datos tabulares.