Programación · n.º 3 entre los lenguajes más usados

Aprende HTML y CSS desde cero

La estructura y el diseño de todas las páginas web.

  • 15 lecciones
  • 19 ejercicios interactivos
  • 7 conceptos clave
  • 58,2 % lo usa*

Qué es HTML y CSS y para qué sirve

HTML describe qué hay en una página (títulos, párrafos, imágenes, enlaces) y CSS decide cómo se ve (colores, tamaños, posición). No son lenguajes de programación en sentido estricto, sino de marcado y de estilos, pero son la base de cualquier web y lo primero que conviene aprender si quieres crear páginas.

  • Crear páginas y sitios web
  • Diseñar correos y documentos
  • La parte visual de aplicaciones web

Para empezar: Crea un archivo index.html con el Bloc de notas o Visual Studio Code y ábrelo con doble clic en el navegador.

Nivel 1 Básico

Lección 1 · Básico

Qué es HTML: tu primera página

HTML marca cada parte del contenido con etiquetas: <h1> para el título principal, <p> para un párrafo… Casi todas se abren y se cierran, y lo que va dentro es el contenido.

Toda página tiene el mismo esqueleto: <!DOCTYPE html>, la etiqueta <html> con el idioma, un <head> con datos que no se ven (como el título de la pestaña) y un <body> con lo que sí se ve.

Ejemplo práctico
<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="utf-8">
    <title>Mi primera página</title>
  </head>
  <body>
    <h1>Hola, soy Ana</h1>
    <p>Esta es mi primera página web.</p>
  </body>
</html>

Ejercicio

¿Dónde va el contenido que se ve en la página?

Ver la solución

Respuesta: En <body>. <body> contiene todo lo visible. <head> guarda datos sobre la página, como el título de la pestaña.

Lección 2 · Básico

Enlaces, imágenes y listas

Los atributos añaden información a una etiqueta. Un enlace <a> necesita href con la dirección; una imagen <img> necesita src con el archivo y alt con una descripción para quien no puede verla.

Las listas se hacen con <ul> (con viñetas) u <ol> (numeradas), y cada elemento va en un <li>.

Ejemplo práctico
<a href="https://www.portaltec.eu/">Visita PortalTec</a>

<img src="gato.jpg" alt="Un gato dormido sobre el teclado" width="400" height="300">

<ul>
  <li>Pan</li>
  <li>Leche</li>
</ul>

Ejercicio

Completa el atributo que describe la imagen para quien no la ve.

<img src="gato.jpg" ="Un gato dormido">
Ver la solución

Respuesta: alt. alt es el texto alternativo: lo leen los lectores de pantalla y aparece si la imagen no carga.

Escribe el código

Escribe una lista numerada con tres pasos (Abre el navegador, Busca la web, Guárdala en favoritos) y, debajo, un enlace con el texto «Ir a la Wikipedia» que lleve a https://es.wikipedia.org.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
<ol>
  <li>Abre el navegador</li>
  <li>Busca la web</li>
  <li>Guárdala en favoritos</li>
</ol>
<a href="https://es.wikipedia.org">Ir a la Wikipedia</a>

<ol> crea una lista numerada (<ul> sería con viñetas) y cada paso va en un <li>. En el enlace, la dirección va en href y el texto que se ve, entre <a> y </a>.

Conceptos clave

Lección 3 · Básico

Qué es CSS y dónde se escribe

CSS son reglas con un selector (a qué elementos afecta) y unas declaraciones propiedad: valor; entre llaves. Por ejemplo, h1 { color: navy; } pone azul marino todos los títulos <h1>.

Lo habitual es escribir el CSS en un archivo aparte, estilos.css, y enlazarlo desde el <head> con <link>. Así una sola hoja da estilo a todas tus páginas.

Ejemplo práctico
<!-- En el <head> de index.html -->
<link rel="stylesheet" href="estilos.css">

/* En estilos.css */
body {
  font-family: system-ui, sans-serif;
  line-height: 1.6;
}
h1 {
  color: navy;
}

Ejercicio

¿Qué etiqueta enlaza una hoja de estilos externa?

Ver la solución

Respuesta: <link rel="stylesheet">. <link rel="stylesheet" href="archivo.css"> carga el CSS de un archivo aparte.

Lección 4 · Básico

Clases y el modelo de caja

Con el atributo class marcas los elementos que quieres tratar igual, y en CSS los seleccionas con un punto: .aviso. Un mismo elemento puede tener varias clases.

Cada elemento es una caja con contenido, relleno interior (padding), borde (border) y margen exterior (margin). Con box-sizing: border-box el ancho que indicas incluye el relleno y el borde, y es más fácil de calcular.

Ejemplo práctico
<p class="aviso">Oferta solo hoy</p>

/* CSS */
* { box-sizing: border-box; }

.aviso {
  padding: 12px 16px;          /* espacio interior */
  border: 2px solid #b45309;   /* borde */
  margin: 24px 0;              /* espacio exterior */
  border-radius: 8px;
}

Ejercicio

¿Qué propiedad da espacio entre el contenido y su borde?

Ver la solución

Respuesta: padding. padding es el relleno interior; margin separa la caja de las demás.

Escribe el código

Completa el CSS para que la .tarjeta tenga 16px de relleno (padding) por todos los lados, un borde sólido de 2px de color #2563eb y las esquinas redondeadas con un radio de 8px.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
<style>
  .tarjeta {
    padding: 16px;
    border: 2px solid #2563eb;
    border-radius: 8px;
  }
</style>
<div class="tarjeta">Hola, soy una tarjeta</div>

padding es el espacio entre el contenido y el borde; border acepta grosor, estilo y color en una sola línea, y border-radius redondea las esquinas.

Conceptos clave

Nivel 2 Intermedio

Lección 5 · Intermedio

Formularios accesibles

Un formulario recoge datos con campos <input>, <select> o <textarea>. El atributo type cambia el teclado y la validación: email, number, date…

Cada campo necesita una etiqueta <label> unida a él con for e id: así el lector de pantalla la anuncia y, al pulsar el texto, se activa el campo.

Ejemplo práctico
<form action="/suscribir" method="post">
  <label for="correo">Tu correo</label>
  <input id="correo" name="correo" type="email" required autocomplete="email">

  <button type="submit">Suscribirme</button>
</form>

Ejercicio

Completa el atributo que une la etiqueta con el campo de id «correo».

<label ="correo">Tu correo</label>
Ver la solución

Respuesta: for. El for de la etiqueta debe coincidir con el id del campo.

Encuentra los errores

Este formulario de suscripción tiene 2 errores de accesibilidad y de uso: uno hace que el lector de pantalla no anuncie para qué es el campo y otro impide que el móvil muestre el teclado de correo y que el navegador compruebe el formato.

Ver la solución
  • Línea 2: La etiqueta no está asociada al campo: le falta for con el mismo valor que el id del campo (correo). Corrección: <label for="correo">Tu correo electrónico</label>
  • Línea 3: Con type="text" el navegador no sabe que es un correo: type="email" muestra el teclado con @ en el móvil y avisa si el formato no es válido. Corrección: <input id="correo" name="correo" type="email" required>

Lección 6 · Intermedio

Colocar elementos con Flexbox

Con display: flex en un contenedor, sus hijos se colocan en fila (o en columna con flex-direction: column). justify-content los reparte en esa dirección y align-items los alinea en la otra.

gap pone separación entre ellos sin tener que usar márgenes. Flexbox es ideal para barras de menú, botones en fila o centrar algo.

Ejemplo práctico
<nav class="menu">
  <a href="/">Inicio</a>
  <a href="/blog/">Blog</a>
  <a href="/contacto/">Contacto</a>
</nav>

/* CSS */
.menu {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
}

Ejercicio

Completa para colocar los hijos en fila con Flexbox.

.menu {
  display: ;
}
Ver la solución

Respuesta: flex. display: flex convierte el elemento en un contenedor flexible.

Encuentra los errores

Este CSS debería poner los tres botones en fila, centrados en horizontal y con 12px de separación. Tiene 2 errores.

Ver la solución
  • Línea 3: flexbox no es un valor válido de display y el navegador ignora la línea: el valor correcto es flex. Corrección: display: flex;
  • Línea 8: align-items se pone en el contenedor (.barra), no en los elementos de dentro; en los botones no hace nada. Si quieres un ajuste propio para un elemento, se usa align-self. Corrección: align-self: center;

Lección 7 · Intermedio

Diseño adaptable al móvil

Una web adaptable se ve bien en cualquier pantalla. Lo primero es la etiqueta <meta name="viewport"> en el <head>; sin ella, el móvil muestra la página como en un ordenador, en miniatura.

Las consultas de medios (@media) aplican reglas solo a partir de cierto ancho. Lo más cómodo es diseñar primero para móvil y añadir cambios para pantallas grandes.

Ejemplo práctico
<meta name="viewport" content="width=device-width, initial-scale=1">

/* CSS: una columna en el móvil, dos a partir de 700 px */
.tarjetas { display: grid; gap: 16px; }

@media (min-width: 700px) {
  .tarjetas { grid-template-columns: 1fr 1fr; }
}

Ejercicio

¿Qué regla aplica estilos solo a partir de cierto ancho de pantalla?

Ver la solución

Respuesta: @media. @media (min-width: …) activa las reglas solo cuando la pantalla mide al menos ese ancho.

Nivel 3 Avanzado

Lección 8 · Avanzado

Rejillas con CSS Grid

Grid coloca elementos en filas y columnas a la vez. Con grid-template-columns defines las columnas; la unidad fr reparte el espacio libre.

La combinación repeat(auto-fit, minmax(200px, 1fr)) crea tantas columnas como quepan, de al menos 200 px, sin escribir ninguna consulta de medios.

Ejemplo práctico
.galeria {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px;
}

Ejercicio

¿Qué hace repeat(auto-fit, minmax(200px, 1fr))?

Ver la solución

Respuesta: Tantas columnas de al menos 200 px como quepan. Ajusta el número de columnas al ancho disponible, cada una de 200 px como mínimo.

Lección 9 · Avanzado

Variables CSS y modo oscuro

Las propiedades personalizadas (variables CSS) empiezan por -- y se leen con var(). Defínelas en :root y cambia todo el diseño tocando un solo valor.

La consulta prefers-color-scheme: dark detecta si la persona usa el tema oscuro del sistema; basta con redefinir ahí las variables de color.

Ejemplo práctico
:root {
  --fondo: #ffffff;
  --texto: #1f2937;
}
@media (prefers-color-scheme: dark) {
  :root {
    --fondo: #111827;
    --texto: #f3f4f6;
  }
}
body {
  background: var(--fondo);
  color: var(--texto);
}

Ejercicio

Completa para usar la variable --texto.

p { color: (--texto); }
Ver la solución

Respuesta: var. var(--nombre) lee el valor de una propiedad personalizada.

Lección 10 · Avanzado

HTML semántico, accesibilidad y publicar

Usa la etiqueta que describe el contenido: <header>, <nav>, <main>, <article>, <footer>, <button> para acciones… Los buscadores y los lectores de pantalla entienden mejor la página y el teclado funciona sin trucos.

Revisa que el texto tenga contraste suficiente con el fondo (las pautas WCAG piden al menos 4,5:1 en texto normal) y, cuando esté lista, publícala gratis con un servicio como GitHub Pages.

Ejemplo práctico
<body>
  <header>
    <nav aria-label="Principal">…</nav>
  </header>
  <main>
    <article>
      <h1>Receta de tortilla</h1>
      <p>…</p>
    </article>
  </main>
  <footer>© 2026 Mi web</footer>
</body>

Ejercicio

Para una acción como «Añadir al carrito», ¿qué elemento es el correcto?

Ver la solución

Respuesta: <button>. <button> se puede usar con el teclado y los lectores de pantalla lo anuncian como botón, sin trabajo extra.

Lección 11 · Avanzado

Transiciones y animaciones

transition hace que un cambio de estilo ocurra poco a poco en vez de golpe, por ejemplo, al pasar el ratón por un botón. Indicas qué propiedad cambia, cuánto dura y con qué curva (ease, linear…).

Para movimientos más elaborados, @keyframes define los pasos de una animación y animation la aplica. Respeta a quien pide menos movimiento en su sistema: dentro de @media (prefers-reduced-motion: reduce) desactiva o suaviza las animaciones.

Ejemplo práctico
<style>
  .boton {
    background: #2563eb;
    color: white;
    transition: background-color 200ms ease, transform 200ms ease;
  }
  .boton:hover {
    background: #1d4ed8;
    transform: translateY(-2px);
  }

  @keyframes latido {
    from { transform: scale(1); }
    50%  { transform: scale(1.15); }
    to   { transform: scale(1); }
  }
  .aviso { animation: latido 1.2s ease-in-out infinite; }

  @media (prefers-reduced-motion: reduce) {
    .boton, .aviso { transition: none; animation: none; }
  }
</style>
<button class="boton">Pásame el ratón</button>
<span class="aviso">●</span>

Ejercicio

¿Para qué sirve @media (prefers-reduced-motion: reduce)?

Ver la solución

Respuesta: Para aplicar estilos solo a quien ha pedido en su sistema reducir el movimiento. Es una preferencia de accesibilidad del sistema operativo: hay personas a las que el movimiento les marea o distrae.

Lección 12 · Avanzado

Posicionamiento: relative, absolute, sticky y fixed

position cambia cómo se coloca un elemento. relative lo desplaza respecto a su sitio normal y, sobre todo, sirve de referencia para sus hijos con absolute, que se colocan con top, right, bottom y left dentro de él (perfecto para una etiqueta de «Nuevo» en la esquina de una tarjeta).

sticky deja un elemento en su sitio hasta que, al hacer scroll, llega a la posición indicada y se queda pegado (como una cabecera); fixed lo fija a la ventana. z-index decide qué queda encima cuando se solapan.

Ejemplo práctico
<style>
  .tarjeta {
    position: relative;   /* referencia para la etiqueta */
    padding: 24px;
    border: 1px solid #ccc;
  }
  .etiqueta {
    position: absolute;
    top: 8px;
    right: 8px;
    background: #dc2626;
    color: white;
    padding: 2px 8px;
  }
  .cabecera {
    position: sticky;
    top: 0;
    z-index: 10;
    background: white;
  }
</style>
<header class="cabecera">Mi tienda</header>
<article class="tarjeta">
  <span class="etiqueta">Nuevo</span>
  <h2>Auriculares</h2>
</article>

Escribe el código

Haz que la .insignia quede en la esquina superior derecha de la .foto: la foto tiene que ser la referencia (position: relative) y la insignia, position: absolute con top: 0 y right: 0.

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
<style>
  .foto {
    position: relative;
    width: 200px;
    height: 120px;
    background: #cbd5e1;
  }
  .insignia {
    position: absolute;
    top: 0;
    right: 0;
    background: #16a34a;
    color: white;
  }
</style>
<div class="foto">
  <span class="insignia">Oferta</span>
</div>

Un elemento con absolute se coloca respecto al antepasado posicionado más cercano. Sin position: relative en la foto, la insignia se iría a la esquina de la página.

Lección 13 · Avanzado

Pseudoclases y pseudoelementos

Las pseudoclases seleccionan elementos según su estado o su posición: :hover (el ratón encima), :focus-visible (enfocado con el teclado), :nth-child(2n) (los pares), :first-child, :disabled… Evitan tener que añadir clases a mano.

Los pseudoelementos, con dos puntos dobles, crean partes que no existen en el HTML: ::before y ::after añaden contenido decorativo con content, y ::placeholder da estilo al texto de ejemplo de un campo.

Ejemplo práctico
<style>
  tr:nth-child(even) { background: #f1f5f9; }   /* filas alternas */

  a:hover { text-decoration: underline; }
  a:focus-visible { outline: 3px solid #f59e0b; outline-offset: 2px; }

  .obligatorio::after {
    content: " *";
    color: #dc2626;
  }

  input::placeholder { color: #64748b; }
</style>
<label class="obligatorio" for="nombre">Nombre</label>
<input id="nombre" placeholder="Por ejemplo, Ana">

Escribe el código

Con una sola regla y :nth-child, pon fondo #e2e8f0 a los elementos impares de la lista (el 1.º, el 3.º y el 5.º).

Tab escribe una sangría; para salir del editor con el teclado, pulsa Esc y luego Tab. Ctrl + Intro corrige.

Ver la solución
<style>
  .lista li:nth-child(odd) {
    background: #e2e8f0;
  }
</style>
<ul class="lista">
  <li>Uno</li>
  <li>Dos</li>
  <li>Tres</li>
  <li>Cuatro</li>
  <li>Cinco</li>
</ul>

:nth-child(odd) (o 2n+1) selecciona los impares; even (o 2n), los pares.

Conceptos clave

Lección 14 · Avanzado

Tablas accesibles

Las tablas son para datos en filas y columnas, no para maquetar. Un <caption> explica qué contiene la tabla, <thead> agrupa la fila de títulos y cada título es un <th>, no un <td>.

Con scope="col" o scope="row", el lector de pantalla sabe si un <th> es el título de una columna o de una fila, y al recorrer cada celda anuncia a qué título pertenece.

Ejemplo práctico
<table>
  <caption>Consumo de la casa por trimestre (kWh)</caption>
  <thead>
    <tr>
      <th scope="col">Trimestre</th>
      <th scope="col">Luz</th>
      <th scope="col">Gas</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">1.º</th>
      <td>820</td>
      <td>1 340</td>
    </tr>
    <tr>
      <th scope="row">2.º</th>
      <td>610</td>
      <td>450</td>
    </tr>
  </tbody>
</table>

Encuentra los errores

Esta tabla funciona a la vista, pero tiene 2 errores de accesibilidad: un lector de pantalla no sabría de qué trata ni cuáles son los títulos de las columnas.

Ver la solución
  • Línea 1: Falta un <caption> justo después de <table> que diga qué contiene la tabla. Corrección: <table><caption>Precios de los productos</caption>
  • Línea 3: Los títulos de las columnas tienen que ser <th scope="col">, no <td> como las celdas de datos. Corrección: <tr><th scope="col">Producto</th><th scope="col">Precio</th></tr>

Lección 15 · Avanzado

Imágenes adaptables y rápidas

Una foto enorme en un móvil gasta datos y hace lenta la página. Con srcset ofreces la misma imagen en varios tamaños y, con sizes, dices qué ancho ocupará; el navegador descarga la más adecuada para cada pantalla.

Pon siempre width y height (el navegador reserva el hueco y la página no salta al cargar), alt con lo que muestra la imagen y loading="lazy" en las que están más abajo, para que no se descarguen hasta que vayan a verse.

Ejemplo práctico
<img
  src="playa-800.jpg"
  srcset="playa-400.jpg 400w, playa-800.jpg 800w, playa-1600.jpg 1600w"
  sizes="(max-width: 600px) 100vw, 600px"
  width="800" height="533"
  alt="Playa de arena blanca con el mar en calma al atardecer"
  loading="lazy">

<style>
  img { max-width: 100%; height: auto; }  /* nunca más ancha que su caja */
</style>

Ejercicio

¿Por qué conviene poner width y height en las imágenes aunque luego el CSS cambie su tamaño?

Ver la solución

Respuesta: Para que el navegador reserve el hueco y el contenido no salte al cargarse. Con esos atributos el navegador conoce la proporción antes de descargar la imagen y deja su espacio, así el texto no se mueve mientras se carga.

¿Has terminado? Sigue practicando

Repasa los conceptos en el diccionario, practica con estas herramientas o prueba otro lenguaje: las ideas que has aprendido (variables, condiciones, funciones, objetos…) te servirán en cualquiera.

Proyectos para practicar HTML y CSS

  • Texto y programación

    Selector de colores y contraste

    Convierte colores entre HEX, RGB y HSL, toma colores de una imagen y comprueba el contraste WCAG.

  • Texto y programación

    Comparar dos textos

    Encuentra las diferencias entre dos versiones de un texto, línea a línea y palabra a palabra.

Fuentes consultadas

* Porcentaje de personas que dijeron haber usado HTML y CSS en la Stack Overflow Developer Survey 2026: Technology (pregunta sobre lenguajes de programación, de scripts y de marcado; todas las respuestas). El progreso de las lecciones se guarda solo en este navegador.