Módulo 08: Texto, color y unidades

Objetivos del módulo

  • Elegir tipografías con pilas de fuentes seguras
  • Cargar fuentes web con Google Fonts y con @font-face
  • Controlar tamaño, peso, interlineado, espaciado, alineación y decoración del texto
  • Expresar colores con nombres, hexadecimal, rgb(), hsl() y oklch()
  • Trabajar con transparencias y currentColor
  • Dominar los fondos: imágenes, tamaño, posición, repetición y fondos múltiples
  • Crear degradados lineales, radiales y cónicos
  • Saber qué unidad usar en cada caso: px, %, em, rem, vw, vh, dvh, ch…
  • Calcular valores con calc(), min() y max()
  • Definir variables CSS (custom properties) y crear un tema claro/oscuro

1. Tipografía: font-family

La propiedad font-family indica qué fuente usar. Como no sabes qué fuentes tiene instaladas cada usuario, se escribe una pila de fuentes (font stack): una lista de opciones separadas por comas. El navegador usa la primera disponible.

body {
    font-family: "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

📘 Concepto: Una pila de fuentes es como pedir en un bar: “Un café con leche de avena; si no hay, de soja; y si no, normal”. La última opción siempre debe ser una familia genérica, que existe en todos los sistemas.

Familia genérica Aspecto Ejemplos
serif Con remates (serifas), clásica Times New Roman, Georgia
sans-serif Sin remates, limpia Arial, Helvetica, Roboto
monospace Todos los caracteres del mismo ancho Consolas, Courier New
cursive Imita la escritura a mano Comic Sans MS
system-ui La fuente de la interfaz del sistema operativo Segoe UI (Windows), San Francisco (macOS)
  • Los nombres con espacios van entre comillas: "Times New Roman".
  • Las familias genéricas van sin comillas: serif, no "serif".

Pilas de fuentes útiles

/* Fuente del sistema: rápida (no se descarga nada) y con aspecto nativo */
.sistema   { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }

/* Serif clásica para textos largos */
.editorial { font-family: Georgia, "Times New Roman", serif; }

/* Código */
.codigo    { font-family: ui-monospace, Consolas, "Courier New", monospace; }

2. Fuentes web

Si quieres una tipografía concreta que no esté en el sistema del usuario, tienes que hacer que el navegador la descargue.

2.1. Google Fonts

Google Fonts ofrece cientos de fuentes libres. Eliges la fuente y los pesos, y copias el código que te genera:

<head>
    <!-- Conexión anticipada a los servidores de fuentes (acelera la carga) -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <!-- Inter en pesos 400 y 700, y Merriweather 700 -->
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&family=Merriweather:wght@700&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="css/estilos.css">
</head>
body {
    font-family: "Inter", system-ui, sans-serif;   /* siempre con alternativas */
}
h1, h2 {
    font-family: "Merriweather", Georgia, serif;
}

⚠️ Importante: Cada peso y estilo es un fichero que se descarga. Pide solo los que uses (normalmente 2 o 3). El parámetro display=swap hace que el texto se muestre de inmediato con la fuente alternativa y se cambie cuando llegue la fuente web, en lugar de dejar el texto invisible.

2.2. @font-face: fuentes alojadas en tu servidor

Puedes descargar los ficheros de la fuente y servirlos tú mismo. Ventajas: no dependes de un servidor externo y mejoras la privacidad (Google no recibe la IP de tus visitantes, algo relevante con el RGPD europeo).

mi-web/
├── css/estilos.css
└── fuentes/
    ├── inter-regular.woff2
    └── inter-bold.woff2
/* Declaramos la fuente: le damos un nombre y decimos dónde está cada fichero */
@font-face {
    font-family: "Inter";
    src: url("../fuentes/inter-regular.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Inter";
    src: url("../fuentes/inter-bold.woff2") format("woff2");
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}

/* Y la usamos como cualquier otra */
body {
    font-family: "Inter", system-ui, sans-serif;
}

📘 Concepto: Hoy basta con el formato WOFF2: está comprimido y lo soportan todos los navegadores actuales. Los formatos antiguos (.ttf, .eot, .svg) ya no son necesarios en la web.

Fuentes variables

Una fuente variable incluye todos los pesos (y a veces otros ejes, como el ancho) en un único fichero. Se declara con un rango de pesos:

@font-face {
    font-family: "Inter";
    src: url("../fuentes/InterVariable.woff2") format("woff2");
    font-weight: 100 900;   /* rango: cualquier peso entre 100 y 900 */
    font-display: swap;
}

.titulo { font-weight: 650; }   /* ¡un peso intermedio! */

3. Propiedades del texto

Tamaño, peso y estilo

p {
    font-size: 1rem;        /* tamaño (las unidades se ven en la sección 7) */
    font-weight: 400;       /* 100-900; normal = 400, bold = 700 */
    font-style: italic;     /* normal | italic */
}
font-weight Nombre habitual
100 Thin
300 Light
400 Regular (normal)
500 Medium
600 Semibold
700 Bold (bold)
900 Black

⚠️ Importante: Si pides un peso que la fuente no tiene (por ejemplo 600 cuando solo has cargado 400 y 700), el navegador usa el más cercano o “falsea” una negrita. Carga los pesos que vayas a usar.

Interlineado: line-height

Controla la altura de cada línea. Un buen interlineado es la diferencia entre un texto agradable y uno agotador.

body {
    line-height: 1.6;   /* ✓ sin unidad: 1,6 veces el tamaño de letra de CADA elemento */
}

h1 {
    line-height: 1.2;   /* los títulos grandes necesitan menos interlineado */
}

💡 Consejo: Usa siempre line-height sin unidad. Con unidades (24px, 1.5em), el valor calculado se hereda tal cual y un título grande heredaría un interlineado pensado para texto pequeño, con líneas solapadas. Para texto de lectura, entre 1.5 y 1.7.

Espaciado, alineación y transformación

.titulo-seccion {
    letter-spacing: 0.1em;        /* espacio entre letras (admite negativos) */
    word-spacing: 0.2em;          /* espacio entre palabras */
    text-align: center;           /* left | right | center | justify | start | end */
    text-transform: uppercase;    /* uppercase | lowercase | capitalize | none */
    text-indent: 2em;             /* sangría de la primera línea */
}

💡 Consejo: Escribe el texto en el HTML con sus mayúsculas normales y usa text-transform: uppercase para mostrarlo en mayúsculas. Así los lectores de pantalla no lo deletrean y, si cambias el diseño, no tienes que reescribir el contenido. Evita text-align: justify en pantallas estrechas: crea “ríos” de espacios en blanco.

Decoración: text-decoration

a {
    text-decoration: underline;          /* none | underline | overline | line-through */
    text-decoration-color: #e63946;
    text-decoration-thickness: 2px;
    text-underline-offset: 4px;          /* separa el subrayado del texto */
}

.precio-antiguo { text-decoration: line-through; color: #888; }

/* Abreviada: línea, estilo, color y grosor */
.error { text-decoration: underline wavy #dc2626 2px; }

Sombra de texto: text-shadow

Sintaxis: desplazamiento-x desplazamiento-y desenfoque color. Admite varias sombras separadas por comas.

.hero-titulo { text-shadow: 2px 2px 4px rgb(0 0 0 / 0.5); }        /* sombra suave */
.neon        { color: #fff; text-shadow: 0 0 5px #0ff, 0 0 15px #0ff, 0 0 30px #0ff; }

Espacios en blanco y desbordamiento

white-space controla cómo se tratan los espacios y saltos de línea del HTML:

Valor Espacios múltiples Saltos de línea del HTML Ajuste automático
normal Se colapsan Se ignoran Sí
nowrap Se colapsan Se ignoran No
pre Se respetan Se respetan No
pre-wrap Se respetan Se respetan Sí
pre-line Se colapsan Se respetan Sí

El patrón clásico para cortar con puntos suspensivos un texto de una línea necesita tres propiedades a la vez (y un ancho limitado):

.nombre-archivo {
    max-width: 200px;
    white-space: nowrap;       /* 1. no partir en varias líneas */
    overflow: hidden;          /* 2. ocultar lo que sobresale */
    text-overflow: ellipsis;   /* 3. mostrar "…" al final */
}

Para limitar un texto a varias líneas (por ejemplo, el resumen de una tarjeta a 3 líneas):

.resumen {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;     /* pese al prefijo, funciona en todos los navegadores actuales */
    overflow: hidden;
}

Y para que las palabras muy largas (URLs, por ejemplo) no rompan el diseño: overflow-wrap: break-word;.

Títulos equilibrados

h1, h2, h3 {
    text-wrap: balance;   /* reparte las palabras para que las líneas tengan longitud similar */
}

La abreviada font

/* font: [estilo] [peso] tamaño/interlineado familia  (tamaño y familia son obligatorios) */
.cita { font: italic 400 1.25rem/1.5 Georgia, serif; }

✏️ Ejercicio: Crea un artículo de blog con un título, un subtítulo y tres párrafos. Carga desde Google Fonts una fuente serif para los títulos y una sans-serif para el texto. Ajusta line-height, limita el ancho del texto a 65ch (lo verás en la sección 7) y pon el subtítulo en mayúsculas con letter-spacing.


4. Colores

CSS ofrece varias formas de escribir un mismo color. Todas estas líneas producen el mismo rojo:

.a { color: red; }
.b { color: #ff0000; }
.c { color: #f00; }
.d { color: rgb(255 0 0); }
.e { color: hsl(0 100% 50%); }

4.1. Nombres de color

CSS reconoce unos 140 nombres en inglés: red, navy, tomato, rebeccapurple, transparent… Son cómodos para pruebas, pero en un diseño real necesitas el tono exacto de la marca.

4.2. Hexadecimal

Formato #RRGGBB: dos dígitos hexadecimales (0-9, a-f) por canal rojo, verde y azul, de 00 (nada) a ff (máximo, 255).

.negro   { color: #000000; }
.blanco  { color: #ffffff; }
.marca   { color: #1d3557; }
.corto   { color: #1a5; }       /* forma corta: equivale a #11aa55 */
.semi    { color: #1d355780; }  /* 8 dígitos: los dos últimos son la opacidad (80 ≈ 50 %) */

4.3. rgb()

Indica la cantidad de rojo, verde y azul de 0 a 255. La sintaxis moderna separa los valores con espacios y la opacidad con una barra:

.rojo        { color: rgb(230 57 70); }
.rojo-50     { color: rgb(230 57 70 / 50%); }   /* 50 % de opacidad */
.antigua     { color: rgba(230, 57, 70, 0.5); } /* sintaxis antigua con comas: sigue funcionando */

4.4. hsl()

Hue (tono), Saturation (saturación) y Lightness (luminosidad). Es mucho más intuitivo para crear variaciones de un mismo color:

flowchart LR
    H["Tono (0-360°)<br/>0 rojo · 120 verde · 240 azul"] --> C["hsl(220 80% 50%)"]
    S["Saturación (0-100 %)<br/>0 % gris · 100 % puro"] --> C
    L["Luminosidad (0-100 %)<br/>0 % negro · 50 % puro · 100 % blanco"] --> C
/* Paleta de azules cambiando solo la luminosidad */
.azul-claro  { background-color: hsl(220 80% 85%); }
.azul        { background-color: hsl(220 80% 50%); }
.azul-oscuro { background-color: hsl(220 80% 25%); }
.azul-50     { background-color: hsl(220 80% 50% / 0.5); }

4.5. oklch(): el color moderno

oklch() (Luminosidad, Croma, Hue/tono) es un espacio de color diseñado según la percepción humana, soportado por todos los navegadores actuales. Su gran ventaja: si dos colores tienen la misma L, se perciben igual de claros, algo que no pasa con hsl() (un amarillo y un azul con L: 50% se ven muy distintos). Además permite colores más vivos en pantallas modernas (gama P3).

/* oklch(luminosidad croma tono) */
.primario   { background-color: oklch(55% 0.2 260); }   /* azul */
.exito      { background-color: oklch(55% 0.2 145); }   /* verde, misma claridad percibida */
.peligro    { background-color: oklch(55% 0.2 25); }    /* rojo, misma claridad percibida */
.transluc   { background-color: oklch(55% 0.2 260 / 40%); }
Componente Rango Significado
L 0 %-100 % Luminosidad percibida (0 % negro, 100 % blanco)
C 0-0,4 aprox. Croma: intensidad del color (0 = gris)
H 0-360 Tono en grados sobre la rueda de color

Comparativa de formatos

Formato Ejemplo Ventajas Inconvenientes
Nombre tomato Legible Pocos colores
Hex #1d3557 Compacto, el más usado en diseño Poco intuitivo
rgb() rgb(29 53 87) Fácil de obtener de herramientas Difícil crear variaciones
hsl() hsl(215 50% 23%) Intuitivo para variaciones Claridad percibida irregular
oklch() oklch(33% 0.07 255) Uniforme, gama amplia, ideal para paletas Menos conocido

4.6. Transparencia: canal alfa frente a opacity

/* Canal alfa: solo el fondo es semitransparente; el texto sigue opaco */
.caja-alfa    { background-color: rgb(0 0 0 / 50%); color: white; }

/* opacity: TODO el elemento (fondo, texto, imágenes, hijos) se vuelve transparente */
.caja-opacity { background-color: black; color: white; opacity: 0.5; }

⚠️ Importante: Si quieres una capa oscura semitransparente con texto legible encima, usa el canal alfa en el color de fondo, no opacity, que haría también transparente el texto.

4.7. currentColor

La palabra clave currentColor equivale al valor actual de la propiedad color del elemento. Es perfecta para componentes que deben adaptarse al color del texto:

.boton-borde {
    color: #2563eb;
    border: 2px solid currentColor;   /* el borde es del mismo color que el texto */
}
.boton-borde:hover {
    color: #dc2626;                   /* cambiando solo color, cambia también el borde */
}

/* Iconos SVG que toman el color del texto que los rodea */
.icono { fill: currentColor; }

Accesibilidad: el contraste

⚠️ Importante: El texto debe contrastar lo suficiente con su fondo. Las pautas WCAG piden una relación mínima de 4,5:1 para texto normal y 3:1 para texto grande. Las DevTools de Chrome y Firefox muestran la relación de contraste al inspeccionar un color: si no llega al mínimo, oscurece el texto o aclara el fondo. El gris claro sobre blanco “tan elegante” suele suspender.


5. Fondos

Propiedades de fondo

.hero {
    background-color: #1d3557;                        /* color de respaldo, por si la imagen no carga */
    background-image: url("../img/montanas.jpg");     /* ruta relativa al fichero CSS */
    background-size: cover;                           /* cubrir toda la caja */
    background-position: center;                      /* centrar la imagen */
    background-repeat: no-repeat;                     /* no repetir en mosaico */
    min-height: 60vh;
}
Propiedad Valores habituales Qué hace
background-color Cualquier color Color de fondo (detrás de la imagen)
background-image url(...), degradados Imagen o degradado de fondo
background-size cover, contain, auto, 200px, 50% auto Tamaño de la imagen
background-position center, top right, 20% 80%, 10px 0 Posición de la imagen
background-repeat repeat, no-repeat, repeat-x, repeat-y, space, round Repetición en mosaico
background-attachment scroll, fixed, local Si el fondo se desplaza con la página
background-origin / background-clip border-box, padding-box, content-box Desde dónde se pinta el fondo

cover frente a contain

Valor Comportamiento Riesgo
cover La imagen cubre toda la caja, manteniendo proporciones Se recortan los bordes de la imagen
contain La imagen cabe entera en la caja, manteniendo proporciones Quedan huecos vacíos

💡 Consejo: Con cover, usa background-position para controlar qué parte se conserva. Si el sujeto de una foto está arriba, background-position: center top evita que le cortes la cabeza.

Efecto parallax simple

.parallax {
    background-image: url("../img/ciudad.jpg");
    background-attachment: fixed;   /* la imagen queda quieta mientras el contenido se desplaza */
    background-size: cover;
}

⚠️ Importante: background-attachment: fixed no funciona en muchos navegadores móviles (se comporta como scroll) y puede afectar al rendimiento. Úsalo como un extra, no como parte esencial del diseño.

La abreviada background

/* color imagen posición / tamaño repetición attachment */
.hero {
    background: #1d3557 url("../img/montanas.jpg") center / cover no-repeat;
}

⚠️ Importante: En la abreviada, el tamaño va después de la posición y separado por una barra (center / cover). Y recuerda que cualquier propiedad abreviada resetea las subpropiedades que no indicas: background: red; elimina una background-image declarada antes.

Fondos múltiples

Puedes apilar varios fondos separándolos por comas. El primero queda encima. El caso más habitual es oscurecer una foto con un degradado para que el texto se lea bien:

.hero {
    background:
        linear-gradient(rgb(0 0 0 / 60%), rgb(0 0 0 / 20%)),   /* capa 1 (encima): velo oscuro */
        url("../img/montanas.jpg") center / cover no-repeat;   /* capa 2 (debajo): la foto */
    color: white;
}

📘 Concepto: ¿Imagen de fondo o <img>? Si la imagen es contenido (la foto de un producto, una ilustración de un artículo), usa <img> con su alt. Si es decoración (una textura, un fondo de cabecera), usa background-image.


6. Degradados

Los degradados son imágenes generadas por el navegador, así que se usan en background-image (o background), no en background-color.

6.1. linear-gradient()

/* De arriba a abajo (dirección por defecto) */
.g1 { background-image: linear-gradient(#2563eb, #7c3aed); }

/* Con dirección por palabras clave */
.g2 { background-image: linear-gradient(to right, #f59e0b, #ef4444); }
.g3 { background-image: linear-gradient(to bottom right, #10b981, #3b82f6); }

/* Con ángulo: 0deg = hacia arriba, 90deg = hacia la derecha */
.g4 { background-image: linear-gradient(135deg, #ec4899, #8b5cf6); }

/* Varias paradas de color con posición */
.g5 { background-image: linear-gradient(to right, red 0%, yellow 50%, green 100%); }

/* Paradas "duras": bandas sin transición (bandera de España) */
.bandera {
    background-image: linear-gradient(#aa151b 25%, #f1bf00 25% 75%, #aa151b 75%);
}

6.2. radial-gradient()

Parte de un punto y se expande en círculo o elipse:

.r1 { background-image: radial-gradient(#fde68a, #f59e0b); }                  /* elipse centrada */
.r2 { background-image: radial-gradient(circle, white, #3b82f6); }           /* círculo */
.r3 { background-image: radial-gradient(circle at top left, #a7f3d0, #065f46); }  /* desde una esquina */

/* Foco de luz sutil sobre fondo oscuro */
.foco {
    background: radial-gradient(circle at 50% 0%, rgb(255 255 255 / 15%), transparent 60%), #0f172a;
}

6.3. conic-gradient()

Los colores giran alrededor de un centro, como las agujas de un reloj. Ideal para gráficos circulares y ruedas de color:

/* Gráfico circular: 40 % rojo, 35 % azul, 25 % verde */
.grafico-tarta {
    width: 200px;
    aspect-ratio: 1;
    border-radius: 50%;
    background-image: conic-gradient(#e63946 0 40%, #457b9d 40% 75%, #2a9d8f 75% 100%);
}

/* Rueda de color */
.rueda {
    width: 200px;
    aspect-ratio: 1;
    border-radius: 50%;
    background-image: conic-gradient(red, yellow, lime, cyan, blue, magenta, red);
}

6.4. Degradados repetidos

/* Rayas diagonales tipo "zona de obras" */
.rayas {
    background-image: repeating-linear-gradient(45deg, #facc15 0 20px, #1f2937 20px 40px);
}
Función Forma Uso típico
linear-gradient() En línea recta Fondos, botones, velos sobre imágenes
radial-gradient() Desde un punto hacia fuera Focos de luz, viñeteados
conic-gradient() Girando alrededor de un centro Gráficos de tarta, indicadores circulares
repeating-*-gradient() Patrón repetido Rayas, tramas

✏️ Ejercicio: Crea un botón con un degradado de 135° entre dos colores de tu elección, texto blanco y una sombra de texto suave. Después crea un gráfico circular con conic-gradient que represente el reparto de tu tiempo en un día (dormir, trabajar, ocio…).


7. Unidades de medida

7.1. Unidades absolutas: px

El píxel CSS es la unidad de referencia. No es un píxel físico de la pantalla (en un móvil de alta densidad, un px CSS puede ocupar 3×3 píxeles reales), sino una medida estable y predecible. Otras unidades absolutas (cm, mm, pt) solo tienen sentido en hojas de impresión.

7.2. Porcentaje: %

Relativo a algo del elemento padre, que depende de la propiedad: en width es el ancho del padre; en font-size, el tamaño de letra del padre.

.columna { width: 50%; }   /* la mitad del ancho de su contenedor */

7.3. em y rem

Unidad Relativa a Ejemplo con raíz de 16px
rem El tamaño de letra del elemento raíz (html) 2rem = 32px, siempre
em El tamaño de letra del propio elemento (o del padre, si se usa en font-size) Depende del contexto

El problema de em es que se acumula al anidar:

<ul class="lista">
    <li>Nivel 1
        <ul class="lista">
            <li>Nivel 2
                <ul class="lista"><li>Nivel 3</li></ul>
            </li>
        </ul>
    </li>
</ul>
.lista { font-size: 1.2em; }    /* 16 → 19,2 → 23,04 → 27,65px... ¡crece en cada nivel! */
.lista { font-size: 1.2rem; }   /* 19,2px en todos los niveles */

¿Entonces em no sirve? Sí: es ideal cuando quieres que algo escale con el texto del propio componente:

.boton {
    font-size: 1rem;
    padding: 0.5em 1em;   /* el relleno crece proporcionalmente si el botón es más grande */
}
.boton--grande {
    font-size: 1.5rem;    /* solo cambiamos el tamaño: el padding se ajusta solo */
}

⚠️ Importante: Usa rem para font-size en lugar de px. Si un usuario aumenta el tamaño de letra por defecto de su navegador (por ejemplo, por baja visión), los rem lo respetan y los px no. Y no fijes html { font-size: 10px; } ni similares: anula esa preferencia.

7.4. Unidades del viewport

El viewport es el área visible de la ventana del navegador.

Unidad Equivale a
vw 1 % del ancho del viewport
vh 1 % del alto del viewport
vmin / vmax 1 % de la dimensión menor / mayor
svh 1 % del alto más pequeño (con las barras del navegador móvil visibles)
lvh 1 % del alto más grande (con las barras ocultas)
dvh 1 % del alto dinámico: se actualiza cuando las barras aparecen y desaparecen
/* Portada a pantalla completa */
.portada {
    min-height: 100vh;    /* respaldo para navegadores antiguos */
    min-height: 100dvh;   /* en móviles, no queda tapada por la barra de direcciones */
}

📘 Concepto: En los móviles, la barra de direcciones aparece y desaparece al hacer scroll, así que “el alto de la pantalla” cambia. 100vh equivale al alto con la barra oculta y el contenido queda parcialmente tapado cuando la barra está visible. dvh resuelve este problema clásico. Fíjate en el truco de declarar la propiedad dos veces: si un navegador no entiende dvh, ignora esa línea y se queda con la anterior.

⚠️ Importante: 100vw incluye el ancho de la barra de desplazamiento vertical en escritorio, por lo que width: 100vw suele provocar un scroll horizontal molesto. Para ocupar todo el ancho usa width: 100%.

7.5. ch: ancho de carácter

1ch es el ancho del carácter “0” en la fuente actual. Es perfecto para limitar la longitud de línea de un texto: lo ideal para leer cómodamente son entre 45 y 75 caracteres por línea.

.articulo {
    max-width: 65ch;     /* unos 65 caracteres por línea */
    margin-inline: auto; /* centrado (lo verás en el módulo 09) */
}

7.6. fr: adelanto

La unidad fr (fracción) solo existe en CSS Grid y reparte el espacio disponible: grid-template-columns: 1fr 2fr; crea dos columnas, la segunda el doble de ancha. La estudiarás en el módulo 11.

¿Qué unidad uso?

Para… Usa Por qué
Tamaño de letra rem Respeta la preferencia del usuario y no se acumula
Relleno de botones y componentes de texto em Escala con el texto del componente
Márgenes y espacios generales rem Coherencia con la tipografía
Bordes finos, sombras px Precisión; no necesitan escalar
Anchos de columnas %, fr Se adaptan al contenedor
Longitud de línea de texto ch Legibilidad
Secciones a pantalla completa dvh, svh Alto real en móviles
Tipografía fluida vw dentro de clamp() Escala con la ventana (módulo 12)
line-height Sin unidad Se hereda correctamente

8. Funciones matemáticas: calc(), min() y max()

calc()

Permite hacer operaciones mezclando unidades, algo imposible de calcular de antemano:

.contenido {
    width: calc(100% - 250px);           /* todo el ancho menos una barra lateral fija */
}

.cabecera-fija + main {
    padding-top: calc(4rem + 20px);
}

.tercio {
    width: calc(100% / 3);
}

⚠️ Importante: Los operadores + y - deben ir rodeados de espacios: calc(100% - 20px) ✓, calc(100%-20px) ✗ (es inválido y se ignora). Con * y / no es obligatorio, pero por legibilidad ponlos también.

min() y max()

min() devuelve el menor de los valores; max(), el mayor. Se evalúan en cada momento según el tamaño real:

.contenedor {
    /* 90 % del ancho, pero como máximo 1200px: en pantallas grandes, 1200px; en pequeñas, 90 % */
    width: min(90%, 1200px);
    margin-inline: auto;
}

.texto-legible {
    /* al menos 1rem aunque 2vw dé menos (en móviles) */
    font-size: max(1rem, 2vw);
}

💡 Consejo: width: min(90%, 1200px) sustituye en una línea al clásico width: 90%; max-width: 1200px;. Recuerda la regla mnemotécnica: min() pone un tope máximo y max() pone un suelo mínimo (parece al revés, pero piénsalo: “el mínimo entre 90 % y 1200px nunca superará 1200px”).

clamp(): un avance

clamp(mínimo, preferido, máximo) combina ambas: usa el valor preferido, pero nunca por debajo del mínimo ni por encima del máximo.

h1 {
    font-size: clamp(1.75rem, 4vw + 1rem, 3.5rem);   /* crece con la ventana, entre 28px y 56px */
}

La usarás a fondo para la tipografía fluida en el módulo 12.


9. Variables CSS (custom properties)

📘 Concepto: Una variable CSS (oficialmente, propiedad personalizada) es un valor con nombre que defines una vez y reutilizas en toda la hoja. Si decides cambiar el color corporativo, lo cambias en un solo sitio.

Declarar y usar

  • Se declaran como una propiedad cuyo nombre empieza por dos guiones: --color-primario.
  • Se usan con la función var().
  • Normalmente se declaran en :root para que estén disponibles en todo el documento.
:root {
    /* Colores */
    --color-primario: #2563eb;
    --color-primario-oscuro: #1e40af;
    --color-texto: #1f2937;
    --color-fondo: #ffffff;

    /* Tipografía */
    --fuente-base: "Inter", system-ui, sans-serif;
    --fuente-titulos: "Merriweather", Georgia, serif;

    /* Espaciado y formas */
    --espacio: 1rem;
    --radio: 8px;
    --sombra: 0 4px 12px rgb(0 0 0 / 0.1);
}

body {
    font-family: var(--fuente-base);
    color: var(--color-texto);
    background-color: var(--color-fondo);
}

.boton {
    background-color: var(--color-primario);
    padding: calc(var(--espacio) / 2) var(--espacio);
    border-radius: var(--radio);
}

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

.tarjeta {
    padding: calc(var(--espacio) * 1.5);
    border-radius: var(--radio);
    box-shadow: var(--sombra);
}

Valor por defecto

var() admite un segundo argumento que se usa si la variable no está definida:

.aviso {
    color: var(--color-aviso, #f59e0b);   /* si --color-aviso no existe, usa naranja */
}

Ámbito y herencia

Las variables se heredan y se pueden redefinir en cualquier elemento. El nuevo valor afecta solo a ese elemento y a sus descendientes. Esto permite crear variantes de componentes de forma muy elegante:

.boton {
    --boton-color: #2563eb;               /* valor por defecto del componente */
    background-color: var(--boton-color);
    border: 2px solid var(--boton-color);
    color: white;
}

/* Las variantes solo cambian la variable */
.boton--exito   { --boton-color: #16a34a; }
.boton--peligro { --boton-color: #dc2626; }

/* Una sección oscura redefine la paleta para todo su contenido */
.seccion-oscura {
    --color-texto: #f9fafb;
    --color-fondo: #111827;
    color: var(--color-texto);
    background-color: var(--color-fondo);
}

⚠️ Importante: Los nombres de las variables distinguen mayúsculas (--Color y --color son distintas). Y solo funcionan como valores de propiedades: no puedes usarlas como nombre de propiedad ni de selector, ni (de momento) dentro de las condiciones de una media query.

Variables CSS frente a variables de Sass

Si has oído hablar de Sass (lo verás con Bootstrap en el módulo 17), sus variables ($color) se sustituyen al compilar y desaparecen del CSS final. Las variables CSS existen en el navegador: se pueden cambiar con media queries, heredar y modificar con JavaScript en tiempo real.

// Cambiar una variable desde JavaScript
document.documentElement.style.setProperty('--color-primario', '#db2777');

10. Tema claro y oscuro

Los sistemas operativos permiten elegir un modo claro u oscuro, y el navegador informa de esa preferencia a CSS mediante la media query prefers-color-scheme. Combinada con variables, crear un tema oscuro es sorprendentemente sencillo.

10.1. Tema automático según el sistema

/* Tema claro (por defecto) */
:root {
    --color-fondo: #ffffff;
    --color-superficie: #f3f4f6;
    --color-texto: #1f2937;
    --color-texto-suave: #6b7280;
    --color-primario: #2563eb;
    color-scheme: light dark;   /* avisa al navegador de que soportamos ambos modos */
}

/* Tema oscuro: solo redefinimos las variables */
@media (prefers-color-scheme: dark) {
    :root {
        --color-fondo: #0f172a;
        --color-superficie: #1e293b;
        --color-texto: #e2e8f0;
        --color-texto-suave: #94a3b8;
        --color-primario: #60a5fa;   /* en oscuro, los colores suelen necesitar más luminosidad */
    }
}

/* El resto del CSS solo usa variables: funciona en ambos temas sin cambios */
body {
    background-color: var(--color-fondo);
    color: var(--color-texto);
}
.tarjeta {
    background-color: var(--color-superficie);
}
.tarjeta small {
    color: var(--color-texto-suave);
}
a {
    color: var(--color-primario);
}

📘 Concepto: La propiedad color-scheme: light dark le dice al navegador que tu página está preparada para ambos modos. Así adapta automáticamente los elementos nativos que tú no estilas: barras de desplazamiento, controles de formulario, el fondo por defecto…

💡 Consejo: Para probar el modo oscuro sin cambiar la configuración de tu sistema, en las DevTools de Chrome abre el menú (⋮) → More tools → Rendering y busca Emulate CSS media feature prefers-color-scheme.

10.2. La función light-dark()

Los navegadores actuales admiten una forma aún más compacta: light-dark(valor-claro, valor-oscuro), que elige automáticamente según el esquema activo. Requiere haber declarado color-scheme.

:root {
    color-scheme: light dark;
    --color-fondo: light-dark(#ffffff, #0f172a);
    --color-texto: light-dark(#1f2937, #e2e8f0);
}

10.3. Selector manual de tema

Muchas webs permiten además que el usuario elija el tema con un botón. Una técnica habitual es usar un atributo en <html>:

<html lang="es" data-tema="claro">
...
<button id="cambiar-tema" type="button">🌓 Cambiar tema</button>
:root {
    --color-fondo: #ffffff;
    --color-texto: #1f2937;
}

:root[data-tema="oscuro"] {
    --color-fondo: #0f172a;
    --color-texto: #e2e8f0;
}

body {
    background-color: var(--color-fondo);
    color: var(--color-texto);
}
// Alterna el atributo data-tema y recuerda la elección
const html = document.documentElement;
const guardado = localStorage.getItem('tema');
if (guardado) {
    html.dataset.tema = guardado;
}

document.getElementById('cambiar-tema').addEventListener('click', () => {
    html.dataset.tema = html.dataset.tema === 'oscuro' ? 'claro' : 'oscuro';
    localStorage.setItem('tema', html.dataset.tema);
});

💡 Consejo: Bootstrap 5.3 usa exactamente esta idea con el atributo data-bs-theme="dark". Lo verás en el módulo 17.


11. Ejercicios

Ejercicio 1: Tipografía de un artículo

Maqueta un artículo de blog con título, autor, fecha y cinco párrafos:

  • Carga desde Google Fonts una fuente para títulos y otra para el cuerpo (máximo dos pesos cada una), con pilas de respaldo.
  • Cuerpo a 1.125rem, line-height: 1.7 y ancho máximo de 65ch, centrado.
  • Autor y fecha en mayúsculas, más pequeños y con letter-spacing: 0.05em.
  • Primer párrafo con letra capital usando ::first-letter.

Ejercicio 2: Paleta con HSL y OKLCH

Crea una fila de 5 cuadrados con el mismo tono en hsl() variando solo la luminosidad (90 %, 70 %, 50 %, 30 %, 10 %). Debajo, otra fila con 5 colores de distinto tono en oklch() con la misma luminosidad y croma. Observa qué fila parece más “uniforme” en claridad.

Ejercicio 3: Cabecera hero

Crea una cabecera que ocupe todo el alto de la pantalla (100dvh con respaldo 100vh), con una imagen de fondo con cover, un velo oscuro hecho con un degradado en fondos múltiples, un título blanco centrado con text-shadow y un botón. Comprueba en DevTools (modo dispositivo) que la imagen siempre cubre la cabecera.

Ejercicio 4: Galería de degradados

Crea 6 tarjetas de 200×200 px, cada una con un degradado distinto: lineal vertical, lineal a 45° con tres colores, con paradas duras (bandera de tres franjas), radial desde una esquina, cónico tipo gráfico de tarta y rayas con repeating-linear-gradient.

Ejercicio 5: Unidades en acción

Crea un componente .boton cuyo relleno esté en em y tres variantes de tamaño que solo cambien font-size en rem. Después crea un .contenedor con width: min(90%, 1100px) y una barra lateral fija de 250px con un contenido principal de calc(100% - 250px). Redimensiona la ventana y explica lo que ocurre.

Ejercicio 6: Sistema de diseño con variables

Define en :root variables para 4 colores, 2 fuentes, 3 tamaños de espaciado, radio de borde y sombra. Construye con ellas una tarjeta de producto y un botón con variantes (--primario, --exito, --peligro) que solo redefinan una variable local del componente.

Ejercicio 7: Tema claro/oscuro

Sobre el ejercicio anterior, añade un tema oscuro automático con prefers-color-scheme, color-scheme: light dark y un botón que permita forzar el tema manualmente con data-tema, guardando la elección en localStorage. Comprueba ambos modos con la emulación de DevTools y verifica el contraste del texto en los dos.


Resumen

Concepto Sintaxis / idea clave
Pila de fuentes font-family: "Inter", system-ui, sans-serif; (genérica al final)
Fuente propia @font-face { font-family; src: url() format("woff2"); font-display: swap; }
Interlineado line-height: 1.6; sin unidad
Puntos suspensivos white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
Colores #1d3557, rgb(29 53 87 / 50%), hsl(215 50% 23%), oklch(55% 0.2 260)
Color del texto actual currentColor
Fondo abreviado background: color url() posición / tamaño repetición;
Fondos múltiples Separados por comas; el primero queda encima
Degradados linear-gradient(), radial-gradient(), conic-gradient()
Unidades de texto rem para tamaños, em para componentes, ch para longitud de línea
Viewport vw, vh, dvh (alto dinámico en móviles)
Cálculos calc(100% - 2rem), min(90%, 1200px), max(1rem, 2vw)
Variables --nombre: valor; y var(--nombre, respaldo)
Tema oscuro @media (prefers-color-scheme: dark) + color-scheme: light dark