Saltar al contenido
proyecto-maquetacion.css · devschool

Proyecto: una landing page con CSS

Lección 22 de 25 · 10 min de lectura · Actualizado el

En esta lección
  1. Lo que vas a construir
  2. Paso 1: el HTML completo
  3. Paso 2: variables, reset y base
  4. Paso 3: la cabecera y el menú
  5. Paso 4: el héroe
  6. Paso 5: tres tarjetas con Grid
  7. Paso 6: imagen y texto con Flexbox
  8. Paso 7: testimonios
  9. Paso 8: el formulario
  10. Paso 9: el pie
  11. Errores frecuentes
  12. Checklist final
  13. Resumen

Ha llegado el momento de juntar todo lo que has aprendido. En este proyecto vas a maquetar, desde cero y paso a paso, la página de presentación (landing page) de un gimnasio de barrio: cabecera con menú, sección principal, tarjetas, testimonios, formulario y pie. Usarás variables, Flexbox, Grid, media queries y el enfoque mobile first. Al final tendrás una página completa y una lista para revisar tus propios proyectos.

Lo que vas a construir

La página de FitBarrio tiene siete bloques, de arriba abajo:

  1. Cabecera con el logo y un menú de navegación.
  2. Héroe: el gran bloque de bienvenida con un título y un botón.
  3. Clases: tres tarjetas colocadas con Grid.
  4. Por qué nosotros: una imagen y un texto uno al lado del otro con Flexbox.
  5. Testimonios de socios.
  6. Formulario de contacto.
  7. Pie de página.

En el móvil todo irá en una columna. A partir de 700px de ancho, el menú se pone en fila, las tarjetas en tres columnas y la imagen al lado del texto.

Crea una carpeta con dos archivos: index.html y estilos.css. El ejemplo de esta lección es una versión recortada que puedes tocar en el editor.

Paso 1: el HTML completo

Antes de escribir una sola línea de CSS, escribe el HTML semántico y completo. Si el HTML está bien estructurado, el CSS es mucho más fácil. Fíjate en que las clases siguen la convención BEM que viste en Organizar el CSS.

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>FitBarrio | Tu gimnasio de barrio</title>
  <link rel="stylesheet" href="estilos.css">
</head>
<body>
  <header class="cabecera">
    <div class="contenedor cabecera__interior">
      <a class="logo" href="/">FitBarrio</a>
      <nav>
        <ul class="menu">
          <li><a class="menu__enlace" href="#clases">Clases</a></li>
          <li><a class="menu__enlace" href="#testimonios">Opiniones</a></li>
          <li><a class="menu__enlace" href="#contacto">Contacto</a></li>
        </ul>
      </nav>
    </div>
  </header>

  <main>
    <section class="heroe">
      <div class="contenedor">
        <h1 class="heroe__titulo">Entrena cerca de casa</h1>
        <p class="heroe__texto">Clases en grupo y sala abierta de 7:00 a 23:00.</p>
        <a class="boton" href="#contacto">Primera clase gratis</a>
      </div>
    </section>

    <section class="seccion" id="clases">
      <div class="contenedor">
        <h2 class="seccion__titulo">Nuestras clases</h2>
        <div class="tarjetas">
          <article class="tarjeta">
            <h3 class="tarjeta__titulo">Spinning</h3>
            <p>45 minutos de cardio al ritmo de la música.</p>
          </article>
          <article class="tarjeta">
            <h3 class="tarjeta__titulo">Yoga</h3>
            <p>Gana flexibilidad y aprende a respirar mejor.</p>
          </article>
          <article class="tarjeta">
            <h3 class="tarjeta__titulo">Fuerza</h3>
            <p>Técnica con monitor y rutinas a tu medida.</p>
          </article>
        </div>
      </div>
    </section>

    <section class="seccion seccion--clara">
      <div class="contenedor destacado">
        <img class="destacado__imagen" src="sala.jpg" alt="Sala de musculación de FitBarrio"
             width="600" height="400">
        <div class="destacado__texto">
          <h2 class="seccion__titulo">Por qué FitBarrio</h2>
          <p>Sin permanencia, con monitores titulados y a cinco minutos andando.</p>
        </div>
      </div>
    </section>

    <section class="seccion" id="testimonios">
      <div class="contenedor">
        <h2 class="seccion__titulo">Lo que dicen nuestros socios</h2>
        <div class="testimonios">
          <blockquote class="testimonio">
            <p>«Vengo antes de trabajar y el ambiente es genial.»</p>
            <footer class="testimonio__autor">Lucía, socia desde 2024</footer>
          </blockquote>
          <blockquote class="testimonio">
            <p>«Los monitores te corrigen la técnica sin agobiar.»</p>
            <footer class="testimonio__autor">Andrés, socio desde 2025</footer>
          </blockquote>
        </div>
      </div>
    </section>

    <section class="seccion seccion--clara" id="contacto">
      <div class="contenedor">
        <h2 class="seccion__titulo">Pide tu clase gratis</h2>
        <form class="formulario">
          <label class="formulario__etiqueta" for="nombre">Nombre</label>
          <input class="formulario__campo" id="nombre" name="nombre" required>

          <label class="formulario__etiqueta" for="correo">Correo</label>
          <input class="formulario__campo" id="correo" name="correo" type="email" required>

          <button class="boton" type="submit">Enviar</button>
        </form>
      </div>
    </section>
  </main>

  <footer class="pie">
    <div class="contenedor">
      <p>© 2026 FitBarrio · Calle Mayor, 12</p>
    </div>
  </footer>
</body>
</html>

Un detalle: el .contenedor va dentro de cada sección. Así el fondo de la sección ocupa todo el ancho de la pantalla, pero el contenido queda centrado y con un ancho máximo.

Paso 2: variables, reset y base

Empieza el estilos.css por las variables y la base. Si mañana cambian los colores de la marca, solo tocas estas líneas.

/* ---------- Variables ---------- */
:root {
  --primario: #e4572e;
  --primario-oscuro: #b8401d;
  --oscuro: #1d2433;
  --claro: #f6f7fb;
  --radio: 10px;
  --espacio: 1rem;
}

/* ---------- Reset y base ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

body {
  font-family: system-ui, sans-serif;
  line-height: 1.5;
  color: var(--oscuro);
}

img { display: block; max-width: 100%; height: auto; }
input, button { font: inherit; }

/* ---------- Layout ---------- */
.contenedor {
  width: min(100% - 32px, 1100px);
  margin-inline: auto;
}

.seccion { padding-block: clamp(2rem, 6vw, 4rem); }
.seccion--clara { background: var(--claro); }
.seccion__titulo { margin-bottom: calc(var(--espacio) * 1.5); }

width: min(100% - 32px, 1100px) hace que el contenido ocupe todo el ancho menos 16px por lado, pero nunca más de 1100px. El padding-block con clamp() da más aire vertical en pantallas grandes sin media queries.

Paso 3: la cabecera y el menú

En móvil, logo arriba y menú debajo. En pantalla ancha, el logo a la izquierda y el menú a la derecha, con Flexbox.

/* ---------- Cabecera ---------- */
.cabecera {
  background: var(--oscuro);
  padding-block: 12px;
}

.logo {
  color: white;
  font-size: 1.4rem;
  font-weight: bold;
  text-decoration: none;
}

.menu {
  list-style: none;
  padding: 0;
  margin-top: 8px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 20px;
}

.menu__enlace {
  color: #ccd3e0;
  text-decoration: none;
}

.menu__enlace:hover,
.menu__enlace:focus-visible {
  color: white;
}

@media (width >= 700px) {
  .cabecera__interior {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }
  .menu { margin-top: 0; }
}

Fíjate en :focus-visible: quien navega con el teclado también debe ver qué enlace está activo.

Paso 4: el héroe

Es lo primero que ve el visitante: un título grande, una frase y un botón llamativo.

/* ---------- Héroe ---------- */
.heroe {
  background: linear-gradient(135deg, var(--claro), #ffe3d8);
  padding-block: clamp(3rem, 10vw, 6rem);
  text-align: center;
}

.heroe__titulo {
  font-size: clamp(2rem, 1rem + 5vw, 3.5rem);
  line-height: 1.1;
}

.heroe__texto {
  margin-block: 12px 24px;
  font-size: 1.1rem;
}

/* ---------- Botón (componente reutilizable) ---------- */
.boton {
  display: inline-block;
  background: var(--primario);
  color: white;
  padding: 12px 24px;
  border: none;
  border-radius: var(--radio);
  font-weight: bold;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.2s;
}

.boton:hover { background: var(--primario-oscuro); }

El título usa tipografía fluida: crece con la pantalla entre 2rem y 3.5rem. Lo explicamos en Diseño responsive.

Paso 5: tres tarjetas con Grid

Mobile first: el grid empieza con una columna (lo que hace por defecto) y pasa a tres en pantallas anchas.

/* ---------- Tarjetas ---------- */
.tarjetas {
  display: grid;
  gap: 20px;
}

.tarjeta {
  background: white;
  border: 1px solid #e3e6ee;
  border-top: 4px solid var(--primario);
  border-radius: var(--radio);
  padding: 20px;
}

.tarjeta__titulo { margin-bottom: 8px; }

@media (width >= 700px) {
  .tarjetas { grid-template-columns: repeat(3, 1fr); }
}

Como alternativa sin media query, grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)) decidiría el número de columnas solo. Lo tienes en Grid. Aquí usamos la media query para que siempre haya exactamente una o tres, nunca dos con una huérfana.

Paso 6: imagen y texto con Flexbox

En móvil, la imagen encima del texto; en pantalla ancha, uno al lado del otro, repartiendo el espacio a partes iguales.

/* ---------- Destacado ---------- */
.destacado {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.destacado__imagen {
  border-radius: var(--radio);
  aspect-ratio: 3 / 2;
  object-fit: cover;
  width: 100%;
}

@media (width >= 700px) {
  .destacado {
    flex-direction: row;
    align-items: center;
  }
  .destacado__imagen,
  .destacado__texto {
    flex: 1;
  }
}

aspect-ratio y object-fit evitan que la foto se deforme aunque tenga otras proporciones (más en Imágenes: object-fit, aspect-ratio y filtros).

Paso 7: testimonios

Cada testimonio es una cita con su autor. En pantallas anchas, dos columnas.

/* ---------- Testimonios ---------- */
.testimonios {
  display: grid;
  gap: 20px;
}

.testimonio {
  margin: 0;
  padding: 20px;
  border-left: 4px solid var(--primario);
  background: var(--claro);
  border-radius: 0 var(--radio) var(--radio) 0;
  font-style: italic;
}

.testimonio__autor {
  margin-top: 8px;
  font-style: normal;
  font-weight: bold;
  font-size: 0.9rem;
}

@media (width >= 700px) {
  .testimonios { grid-template-columns: 1fr 1fr; }
}

Paso 8: el formulario

Etiquetas encima de cada campo, campos a todo el ancho y un ancho máximo para que no queden larguísimos en el ordenador.

/* ---------- Formulario ---------- */
.formulario {
  display: grid;
  gap: 8px;
  max-width: 480px;
}

.formulario__etiqueta { font-weight: bold; }

.formulario__campo {
  padding: 10px 12px;
  border: 1px solid #b9c0cf;
  border-radius: var(--radio);
  margin-bottom: 8px;
}

.formulario__campo:focus {
  outline: 3px solid #ffc7b3;
  border-color: var(--primario);
}

.formulario .boton { justify-self: start; }

justify-self: start evita que el botón se estire a todo el ancho, que es lo que hacen los hijos de un grid por defecto. Tienes más ideas en Estilos para formularios.

Paso 9: el pie

/* ---------- Pie ---------- */
.pie {
  background: var(--oscuro);
  color: #ccd3e0;
  text-align: center;
  padding-block: 24px;
  font-size: 0.9rem;
}

Pegando en orden los bloques de los pasos 2 a 9 tienes el estilos.css completo. Ábrelo en el navegador, pulsa F12, activa el modo dispositivo y estrecha y ensancha la ventana: verás cómo cada bloque cambia a partir de 700px.

Errores frecuentes

  • Empezar por el CSS sin tener el HTML. Acabas añadiendo <div> a trompicones y reescribiendo estilos.
  • Poner el ancho máximo en la sección en lugar de en un contenedor interior. El fondo de color deja de llegar a los bordes de la pantalla.
  • Olvidar la etiqueta viewport. En el móvil real las media queries no se aplicarán.
  • Repetir colores a mano en lugar de usar las variables. Al cambiar la marca tendrás que buscar cada #e4572e.
  • Olvidar el alt, el width y el height de la imagen. Sin las dimensiones, la página “salta” mientras carga la foto.
  • Probar solo en el ancho de tu pantalla. Revisa también 320px, que es el ancho de los móviles más pequeños.

Checklist final

Antes de dar por terminada cualquier maquetación, repasa esta lista:

  • El HTML es semántico (header, nav, main, section, footer) y valida sin errores.
  • Tiene <meta name="viewport"> y lang="es".
  • Los colores, radios y espacios salen de variables en :root.
  • El CSS está escrito mobile first: base para móvil y min-width / width >= para crecer.
  • No hay scroll horizontal en ningún ancho, de 320px en adelante.
  • Las imágenes tienen alt, no se deforman y no se salen de su caja.
  • Los títulos usan tipografía fluida o se ven bien en todos los tamaños.
  • Todos los enlaces y botones tienen estado :hover y :focus-visible visibles.
  • Los campos de formulario tienen su <label> asociado.
  • El contraste entre texto y fondo es suficiente.
  • Las clases siguen un criterio (BEM) y no hay ids ni !important para estilar.
  • Lo has probado en DevTools y en un móvil de verdad.

Resumen

ParteTécnica principal
BaseVariables, reset, .contenedor con min()
CabeceraFlexbox con justify-content: space-between
HéroeDegradado y tipografía fluida con clamp()
TarjetasGrid de una a tres columnas
DestacadoFlexbox de columna a fila, flex: 1
TestimoniosGrid de dos columnas
FormularioGrid con gap y justify-self
TodoMobile first y una media query a 700px

Como siguiente paso, prueba a añadir una sección de tarifas por tu cuenta, o descubre cómo hacer lo mismo más rápido con Bootstrap.

Pruébalo tú

Cambia el código y pulsa Ejecutar (o Ctrl + Enter).

proyecto-maquetacion.html
Resultado

Pon a prueba lo que has aprendido

[CSS] Con mobile first, ¿cómo quedan las tarjetas en un móvil de 390px?
.tarjetas { display: grid; gap: 20px; }

@media (width >= 700px) {
  .tarjetas { grid-template-columns: repeat(3, 1fr); }
}

[CSS] ¿Qué ancho tiene .contenedor en una pantalla de 1400px?
.contenedor {
  width: min(100% - 32px, 1100px);
  margin-inline: auto;
}

[CSS] Quieres que el fondo de color de cada sección llegue a los bordes de la pantalla, pero el texto quede centrado con un ancho máximo. ¿Qué estructura usas?

[CSS] En un formulario con display: grid, el botón de enviar se estira a todo el ancho. ¿Qué lo evita?
.formulario { display: grid; gap: 8px; }

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.