CSS desde cero: estilos, colores, unidades y selectores

Por: Artiko
htmlcssjavascriptdomfrontendselectores-cssespecificidadunidades-csscolores-csscascada

CSS desde cero: estilos, colores, unidades y selectores

En el capítulo 1 construiste tu primer documento HTML: el <!DOCTYPE html>, la raíz <html>, la cabecera <head> con su <title> y su <meta charset="utf-8">, y el cuerpo <body> con el contenido visible. Si abriste ese archivo en el navegador habrás notado algo: funciona, pero se ve exactamente igual que cualquier otra página sin estilos. Fondo blanco, texto negro con serifas, enlaces azules subrayados, títulos en negrita.

Eso no es un accidente ni un error tuyo. El navegador aplica una hoja de estilos por defecto (los llamados user agent stylesheets) a todo documento HTML. Esa hoja dice que un <h1> es grande y negrita, que un <p> tiene margen arriba y abajo, que un <a> es azul. HTML solo aporta la estructura y el significado de cada trozo de contenido; nunca su apariencia.

Este capítulo trata sobre la capa que decide la apariencia: CSS, Cascading Style Sheets u hojas de estilo en cascada. Vas a aprender cómo conectar CSS a tu HTML de las tres formas que existen, cómo expresar un color de cuatro maneras distintas, qué significa realmente cada unidad de medida, y cómo apuntar con precisión a los elementos que quieres modificar. Al final vas a construir una tarjeta de perfil completa aplicando todo junto.

Qué es CSS y qué problema resuelve

Imagina que tienes un sitio de veinte páginas y quieres que todos los títulos sean de color granate. Sin CSS tendrías que abrir las veinte páginas y modificar cada <h1> una por una. Si mañana el color cambia a azul, repites el trabajo veinte veces. CSS existe para romper esa relación: el contenido se escribe una vez en HTML, y las decisiones visuales se escriben una vez en CSS, aunque afecten a miles de elementos.

Esa separación tiene tres consecuencias prácticas: hay un solo lugar de cambio, porque modificas una regla y el cambio se propaga a todo el sitio; el HTML queda legible, sin contaminarse de instrucciones de color y tamaño; y hay reutilización real, porque el navegador descarga el archivo CSS una vez y lo guarda en caché para todas las páginas siguientes.

Anatomía de una regla CSS

Toda hoja de estilos está formada por reglas. Una regla tiene dos partes: el selector, que responde a la pregunta ¿a qué elementos afecta esto?, y el bloque de declaraciones, que responde ¿qué les hago?.

h1 {
  color: darkred;
  font-size: 32px;
}

Descompuesto pieza por pieza:

  • h1 es el selector. Apunta a todos los elementos <h1> del documento.
  • Las llaves { } delimitan el bloque de declaraciones.
  • color: darkred; es una declaración. Se compone de una propiedad (color), dos puntos, un valor (darkred) y un punto y coma que cierra.
  • font-size: 32px; es otra declaración dentro del mismo bloque.

El punto y coma separa declaraciones. La última de un bloque puede omitirlo, pero acostúmbrate a ponerlo siempre: el día que agregues una línea más abajo, no vas a tener que acordarte de volver a subir a corregir la anterior.

CSS también tiene comentarios, que van entre /* y */. Todo lo que quede dentro es ignorado por el navegador, incluso declaraciones completas:

/* Este comentario explica por qué el título es granate */
h1 {
  color: darkred;
  /* font-size: 48px;  <- probé este tamaño y quedó muy grande */
  font-size: 32px;
}

A diferencia de otros lenguajes, CSS no tiene comentarios de una sola línea: no existe //. Si lo escribes, el navegador intentará interpretar esa línea como una regla, fallará, y en algunos casos arrastrará la declaración siguiente con ella.

Las tres formas de aplicar CSS

Existen exactamente tres maneras de que un estilo llegue a un documento HTML. Las tres funcionan, pero cada una tiene su lugar.

1. Estilo inline: el atributo style

Es la forma más directa: pones las declaraciones dentro del atributo style del propio elemento HTML. No hay selector, porque el elemento ya está identificado por el hecho de llevar el atributo.

<body style="color: #999999; background-color: #000000;">
  <h1>Título con el estilo del body</h1>
  <p style="color: #ffcc00;">Este párrafo tiene su propio color.</p>
</body>

Fíjate en la sintaxis: dentro de style="..." van declaraciones sueltas, sin llaves y sin selector. El <body> recibe texto gris claro sobre fondo negro, y el <p> sobreescribe el color con un amarillo.

El estilo inline sirve para una prueba rápida o para un valor que se calcula dinámicamente con JavaScript. Como estrategia general es la peor opción: repite la misma declaración en cada elemento, no se puede cachear, no se puede reutilizar y su especificidad es tan alta que después cuesta sobreescribirlo desde una hoja de estilos.

2. CSS interno: la etiqueta <style>

Un paso mejor. Colocas un bloque <style> dentro del <head> y ahí escribes reglas completas, con selectores. Todo el documento comparte esas reglas.

<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="utf-8">
    <title>CSS interno</title>
    <style>
      /* Una sola regla afecta a todo el documento */
      body {
        color: #999999;
        background-color: #000000;
        font-family: system-ui, sans-serif;
      }
      h1 { color: #ffcc00; }
      p  { line-height: 1.6; }
    </style>
  </head>
  <body>
    <h1>Super Club Net</h1>
    <p>Ahora los estilos viven en un solo lugar.</p>
    <p>Y este párrafo hereda exactamente los mismos.</p>
  </body>
</html>

Notarás que aquí ya no hay atributos style sueltos: el color de los títulos se declara una vez y sirve para todos los <h1> presentes y futuros del documento.

En material antiguo vas a ver la etiqueta escrita como <style type="text/css">. Ese atributo era obligatorio en HTML 4 y es completamente opcional en HTML5: el navegador asume text/css de todas formas. Puedes escribir simplemente <style>.

La forma recomendada para cualquier sitio real. El CSS vive en su propio archivo con extensión .css, y el HTML lo referencia desde el <head> con una etiqueta <link>.

Archivo estilos.css:

body {
  color: #999999;
  background-color: #000000;
  font-family: system-ui, sans-serif;
  margin: 0;
  padding: 24px;
}

h1 { color: #ffcc00; font-size: 32px; }
p  { line-height: 1.6; max-width: 60ch; }

Archivo index.html, en la misma carpeta:

<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="utf-8">
    <title>CSS externo</title>
    <link rel="stylesheet" href="estilos.css">
  </head>
  <body>
    <h1>Super Club Net</h1>
    <p>Los estilos ahora viven en un archivo aparte, reutilizable.</p>
  </body>
</html>

Tres detalles de la etiqueta <link>: rel="stylesheet" es obligatorio, porque le dice al navegador qué relación tiene el archivo con el documento, y sin él la descarga no sirve de nada; href acepta rutas relativas (css/estilos.css, ../estilos.css) o absolutas (https://ejemplo.cl/estilos.css); y type="text/css", que aparece en tutoriales antiguos, es opcional en HTML5. Además, <link> es una etiqueta vacía, sin contenido ni cierre, exactamente como <meta> o <img> del capítulo anterior.

Cómo carga el navegador cada una

Vale la pena entender el mecanismo, porque explica por qué el CSS externo se recomienda tanto:

sequenceDiagram
    participant U as Usuario
    participant N as Navegador
    participant S as Servidor
    participant C as Caché local

    U->>N: Solicita index.html
    N->>S: GET /index.html
    S-->>N: HTML (con style inline e interno incrustados)
    Note over N: El CSS inline e interno<br/>ya viajaron dentro del HTML
    N->>C: Encuentra link rel=stylesheet, ¿está en caché?
    alt No está en caché
        N->>S: GET /estilos.css
        S-->>N: estilos.css + cabeceras de caché
        N->>C: Guarda estilos.css
    else Ya está en caché
        C-->>N: estilos.css sin salir a la red
    end
    N->>U: Construye el CSSOM y pinta la página

La segunda página del sitio que enlace ese mismo estilos.css no vuelve a descargarlo: lo toma de la caché. En cambio, un <style> interno viaja completo dentro de cada HTML, y un style inline viaja repetido en cada elemento que lo lleve.

Tabla comparativa de los tres métodos

CriterioInline (style="...")Interno (<style>)Externo (<link>)
Dónde se escribeEn el atributo del elementoEn el <head> del documentoEn un archivo .css aparte
Usa selectoresNo
AlcanceUn solo elementoEse documentoTodos los documentos que lo enlacen
Se puede cachear y reutilizarNoNo (viaja con el HTML)
Peso de especificidadAltísimoNormalNormal
Requiere petición HTTP extraNoNoSí, la primera vez
Uso recomendadoValores dinámicos desde JS, pruebas puntualesPágina única, demos, correo electrónicoCualquier sitio real

La regla práctica: usa CSS externo por defecto. El CSS interno es aceptable en un archivo de demostración de una sola página. El inline se reserva para lo que no se puede resolver de otra forma.

Varios archivos CSS y el orden importa

Nada impide enlazar dos o más hojas. El navegador las aplica en el orden en que aparecen en el HTML:

<link rel="stylesheet" href="base.css">
<link rel="stylesheet" href="componentes.css">
<link rel="stylesheet" href="tema-oscuro.css">

Si base.css dice h1 { color: black; } y tema-oscuro.css dice h1 { color: white; }, gana el blanco porque se cargó después. Esa es la primera pista de lo que significa la palabra cascada, y volveremos a ella al final del capítulo.

Colores en CSS

Casi cualquier propiedad visible acepta un color: color para el texto, background-color para el fondo, border-color, outline-color, box-shadow y varias más. Por eso conviene entender bien las formas de expresarlo antes de seguir.

El modelo RGB: por qué rojo más verde da amarillo

Las pantallas producen color con luz, no con pigmento. Cada píxel tiene tres emisores diminutos: uno rojo, uno verde y uno azul. Sumando distintas intensidades de esos tres se obtiene cualquier color del monitor. Eso se llama síntesis aditiva o modelo RGB (Red, Green, Blue).

Cuando eras chico te enseñaron que amarillo más azul da verde. Eso también es cierto, pero corresponde a CMYK (Cyan, Magenta, Yellow, Key/negro), el modelo de la tinta y la pintura, que es sustractivo: cada pigmento resta longitudes de onda a la luz blanca que lo ilumina. Los dos modelos son correctos, describen fenómenos físicos distintos.

flowchart TD
    subgraph ADITIVO["RGB — luz de pantalla (síntesis aditiva)"]
        R["Rojo 255"] --> AM["Amarillo"]
        G["Verde 255"] --> AM
        G2["Verde 255"] --> CY["Cian"]
        B["Azul 255"] --> CY
        R2["Rojo 255"] --> MG["Magenta"]
        B2["Azul 255"] --> MG
        AM --> BL["Los tres al máximo = Blanco"]
        CY --> BL
        MG --> BL
        NEG["Los tres en 0 = Negro"]
    end

    subgraph SUSTRACTIVO["CMYK — tinta sobre papel (síntesis sustractiva)"]
        C["Cian"] --> AZ["Azul"]
        M["Magenta"] --> AZ
        M2["Magenta"] --> RO["Rojo"]
        Y["Amarillo"] --> RO
        Y2["Amarillo"] --> VE["Verde"]
        C2["Cian"] --> VE
        AZ --> NE["Las tres mezcladas = Negro sucio"]
        RO --> NE
        VE --> NE
        PAP["Sin tinta = Blanco del papel"]
    end

En pantalla: blanco es 100% de los tres canales; negro es 0% de los tres; amarillo es 100% rojo + 100% verde + 0% azul; magenta es rojo y azul al máximo con verde en cero; cian es verde y azul al máximo con rojo en cero.

Nombres de color

La forma más simple es escribir el nombre en inglés. CSS reconoce 147 nombres definidos por la especificación, más la palabra clave transparent:

h1      { color: darkred; }
.error  { color: crimson; }
.exito  { color: seagreen; }
.aviso  { background-color: gold; }
.oculto { background-color: transparent; }

Algunos nombres útiles y su valor hexadecimal equivalente:

NombreHexadecimalComentario
white / black#ffffff / #000000Blanco y negro puros
red / blue#ff0000 / #0000ffCanales saturados
lime / green#00ff00 / #008000Cuidado: green es bastante más oscuro
yellow#ffff00Rojo + verde al máximo
magenta / fuchsia#ff00ffDos nombres, mismo color
cyan / aqua#00ffffDos nombres, mismo color
gray / grey#808080Ambas grafías son válidas
orange#ffa500El único nombre agregado en CSS 2.1
rebeccapurple#663399Agregado en homenaje a Rebecca Meyer

Los nombres son cómodos para prototipar, pero rara vez son el color exacto que un diseño necesita. Para eso están las notaciones numéricas.

Hexadecimal

Es la notación más frecuente en la web. Se escribe un # seguido de dígitos hexadecimales.

El sistema hexadecimal es base 16: en lugar de contar del 0 al 9 y llevarse una, cuenta del 0 al 15 usando las letras a a f para los valores 10 a 15, de modo que a vale 10, b vale 11 y así hasta f, que vale 15.

Con dos dígitos se representan 16 × 16 = 256 valores, de 00 (que es 0) a ff (que es 255), y un color hexadecimal usa tres pares, uno por canal:

flowchart LR
    HEX["#3a7fd5"] --> P1["3a"]
    HEX --> P2["7f"]
    HEX --> P3["d5"]
    P1 --> R["Rojo<br/>3×16 + 10 = 58"]
    P2 --> G["Verde<br/>7×16 + 15 = 127"]
    P3 --> B["Azul<br/>13×16 + 5 = 213"]
    R --> OUT["rgb 58 127 213<br/>un azul medio"]
    G --> OUT
    B --> OUT

El total es 256 × 256 × 256 = 16.777.216 colores, los famosos 16,7 millones o color verdadero.

.marca     { color: #3a7fd5; }              /* azul medio */
.fondo     { background-color: #f5f5f5; }   /* gris muy claro */
.destacado { color: #ff6b35; }              /* naranjo */

Notación abreviada de 3 dígitos. Cuando los dos dígitos de cada par son iguales, se puede escribir un solo dígito por canal y el navegador lo duplica:

AbreviadoEquivale aColor
#000#000000Negro
#fff#ffffffBlanco
#f00#ff0000Rojo
#369#336699Azul apagado
#ccc#ccccccGris claro

Ojo con el mecanismo: #369 no se completa con ceros ni se rellena a la derecha, cada dígito se duplica sobre sí mismo. Por eso #123 es #112233, no #123000 ni #000123.

Notación con transparencia de 8 y 4 dígitos. CSS moderno permite un cuarto par que representa el canal alfa: #00000033 es negro al 20%, #0008 es negro al 53% en forma abreviada y #000000ff es negro opaco. Igual que en los canales de color, 00 es 0% de opacidad y ff es 100%.

Las letras hexadecimales no distinguen mayúsculas de minúsculas: #FF6B35 y #ff6b35 son idénticos. Elige una convención y mantenla en todo el proyecto.

rgb() y rgba()

La función rgb() expresa lo mismo que el hexadecimal, pero con números decimales de 0 a 255:

.marca { color: rgb(58, 127, 213); }   /* idéntico a #3a7fd5 */
.rojo  { color: rgb(255, 0, 0); }      /* idéntico a #ff0000 */
.gris  { color: rgb(128, 128, 128); }  /* idéntico a #808080 */

rgba() agrega un cuarto argumento, el alfa, que va de 0 (transparente total) a 1 (opaco total). La sintaxis moderna admite además espacios en lugar de comas y una barra antes del alfa; las cuatro líneas siguientes producen el mismo negro al 60% y todas son válidas:

.a { background-color: rgba(0, 0, 0, 0.6); }
.b { background-color: rgb(0, 0, 0, 0.6); }
.c { background-color: rgb(0 0 0 / 0.6); }
.d { background-color: rgb(0 0 0 / 60%); }

También se aceptan porcentajes en los canales: rgb(100% 0% 0%) es rojo puro.

Una distinción importante: opacity: 0.6 y rgba(0, 0, 0, 0.6) no son lo mismo. opacity afecta al elemento y a todo su contenido, incluidos texto e hijos. El alfa de rgba() afecta solo a esa propiedad de color en particular.

/* El fondo se ve translúcido, el texto encima sigue nítido */
.con-alfa    { background-color: rgba(255, 107, 53, 0.4); color: white; }

/* Todo el elemento se atenúa, texto incluido */
.con-opacity { background-color: #ff6b35; color: white; opacity: 0.4; }

hsl() y hsla()

Esta es la notación que más ayuda cuando estás diseñando y no solo copiando un color. hsl() describe un color con tres parámetros que corresponden a cómo lo percibimos: H (hue, matiz) es un ángulo de 0 a 360 grados sobre la rueda de color, donde 0 es rojo, 120 es verde y 240 es azul; S (saturation, saturación) va de 0% (gris total) a 100% (color puro); y L (lightness, luminosidad) va de 0% (negro absoluto) a 100% (blanco absoluto), con el 50% como punto donde el color se ve más pleno.

.rojo   { color: hsl(0, 100%, 50%); }
.verde  { color: hsl(120, 100%, 50%); }
.azul   { color: hsl(240, 100%, 50%); }
.gris   { color: hsl(0, 0%, 50%); }
.blanco { color: hsl(0, 0%, 100%); }

La ventaja aparece cuando necesitas variaciones de un mismo color. Con hexadecimal, aclarar un azul requiere calcular tres números nuevos. Con HSL basta mover la luminosidad:

.marca-oscura   { background-color: hsl(214, 65%, 30%); }
.marca-base     { background-color: hsl(214, 65%, 50%); }
.marca-clara    { background-color: hsl(214, 65%, 70%); }
.marca-palida   { background-color: hsl(214, 65%, 92%); }
.marca-apagada  { background-color: hsl(214, 20%, 50%); }

Toda esa paleta es visiblemente coherente porque comparte matiz y saturación. Lograr lo mismo a mano en hexadecimal es tedioso y propenso a error.

hsla() agrega el canal alfa y también acepta la sintaxis moderna con espacios y barra, así que hsla(214, 65%, 50%, 0.25) y hsl(214 65% 50% / 25%) son el mismo velo azul. En el matiz la unidad deg es opcional, y los ángulos se envuelven: hsl(380, 100%, 50%) es lo mismo que hsl(20, 100%, 50%).

Tabla comparativa de notaciones de color

NotaciónEjemploAlfaLegible por humanosCuándo usarla
NombrecrimsonNo (salvo transparent)Muy altaPrototipos, ejemplos, depuración
Hex 6 o 3 dígitos#3a7fd5, #39dNoBajaEl estándar de facto, lo que entrega el diseñador
Hex 8 dígitos#3a7fd580Muy bajaCuando ya tienes el hex y necesitas transparencia
rgb() / rgba()rgb(58 127 213 / 50%)MediaValores calculados, superposiciones, sombras y velos
hsl() / hsla()hsl(214 65% 50% / 50%)AltaDiseñar paletas y generar variantes claras y oscuras

Las cuatro notaciones se pueden mezclar libremente en la misma hoja de estilos. El navegador las convierte todas al mismo valor interno.

Un ejemplo con las cuatro notaciones a la vez

<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="utf-8">
    <title>Cuatro formas de decir el mismo color</title>
    <style>
      body { font-family: system-ui, sans-serif; padding: 32px; }
      .muestra {
        display: inline-block; box-sizing: border-box;
        width: 160px; height: 90px; padding: 8px; margin: 8px;
        color: white; font-size: 14px;
      }
      /* Los tres primeros producen exactamente el mismo azul */
      .por-hex     { background-color: #3a7fd5; }
      .por-rgb     { background-color: rgb(58, 127, 213); }
      .por-hsl     { background-color: hsl(214, 65%, 53%); }
      .por-hex-abr { background-color: #38d; }
      .translucido { background-color: rgba(58, 127, 213, 0.35); color: #1a1a1a; }
    </style>
  </head>
  <body>
    <div class="muestra por-hex">#3a7fd5</div>
    <div class="muestra por-rgb">rgb(58, 127, 213)</div>
    <div class="muestra por-hsl">hsl(214, 65%, 53%)</div>
    <div class="muestra por-hex-abr">#38d (aproximado)</div>
    <div class="muestra translucido">rgba con 35% de alfa</div>
  </body>
</html>

Ábrelo y compara: los tres primeros bloques son indistinguibles, y el cuarto es una aproximación, porque #38d equivale a #3388dd.

Unidades de medida

Cada vez que una propiedad expresa un tamaño (font-size, width, margin, padding, border-width, top, gap) necesitas acompañar el número de una unidad; la excepción son las propiedades sin dimensión como line-height: 1.5, opacity: 0.6 o z-index: 10. Y hay una regla de sintaxis que da problemas a todo el mundo la primera vez: el número y la unidad van pegados, sin espacio.

p { margin-left: 20px; }  /* correcto */
p { margin-left: 20 px; } /* incorrecto: el espacio descarta la declaración */
p { margin-left: 20; }    /* incorrecto: sin unidad tampoco vale */
p { margin: 0; }          /* correcto: el cero es la única excepción */

Las unidades se dividen en dos grandes familias: absolutas (un valor fijo) y relativas (un valor que depende de otra cosa).

Unidades absolutas

UnidadNombreEquivalenciaUso
pxPíxel CSS1/96 de pulgadaPantalla, es la unidad absoluta que sí se usa
ptPunto1/72 de pulgada = 1,333pxEstilos para impresión
pcPica12pt = 16pxTipografía tradicional, casi no se usa
cm / mm / inCentímetro, milímetro, pulgada37,8px / 3,78px / 96pxHojas de impresión
QCuarto de milímetro0,945pxMuy raro

En pantalla solo se usa px; el resto tiene sentido cuando escribes CSS destinado a papel. Y ojo: el píxel CSS no es el píxel físico de la pantalla. En un teléfono de alta densidad, un px de CSS puede corresponder a 2 o 3 píxeles físicos, y el navegador hace esa traducción para que 16px se vea del mismo tamaño aparente en cualquier dispositivo.

.borde  { border: 1px solid #ddd; }
.icono  { width: 24px; height: 24px; }

px es predecible: 24px es 24px siempre, sin importar el contexto. Esa es su virtud y también su límite, porque no responde a las preferencias del usuario.

Porcentaje: %

Un porcentaje siempre se calcula respecto de algo, y ese algo depende de la propiedad.

PropiedadEl 100% se refiere a
widthEl ancho del bloque contenedor
heightEl alto del bloque contenedor (si tiene alto definido)
margin y padding (los cuatro lados)El ancho del contenedor, incluso arriba y abajo
font-sizeEl font-size del elemento padre
line-heightEl font-size del propio elemento
top / leftAlto / ancho del contenedor posicionado

Ese detalle de padding-top: 10% calculándose sobre el ancho sorprende siempre; es intencional, y se usa para crear cajas de proporción fija. Los porcentajes además se encadenan: cada nivel se calcula sobre el resultado del anterior.

.contenedor        { width: 600px; }  /* 600px fijos */
.mitad             { width: 50%; }    /* 50% de 600px = 300px */
.mitad-de-la-mitad { width: 50%; }    /* 50% de 300px = 150px */

em: relativo al tamaño de fuente del elemento

1em equivale al font-size calculado del propio elemento. Y cuando em se usa dentro de la propia declaración de font-size, se refiere al font-size del padre. Esa doble regla es la fuente de casi toda la confusión con esta unidad.

.tarjeta {
  font-size: 16px;
  padding: 1em;        /* 1 × 16px = 16px, usa su propio font-size */
  margin: 0.5em;       /* 0.5 × 16px = 8px */
}
.tarjeta h2 {
  font-size: 1.5em;    /* 1.5 × 16px del padre = 24px */
  margin-bottom: 1em;  /* 1 × 24px de sí mismo = 24px */
}

El problema serio de em es que se compone al anidar. Si un elemento con font-size: 1.2em contiene otro con font-size: 1.2em, el segundo termina en 1,44 veces el original; y un tercer nivel en 1,728. Ese efecto bola de nieve es la razón principal por la que existe rem.

flowchart TD
    ROOT["html — font-size: 16px<br/>raíz del documento"]
    subgraph EM["Cadena con em — se multiplica en cada nivel"]
        E1["section 1.25em<br/>16 × 1.25 = 20px"] --> E2["div 1.25em<br/>20 × 1.25 = 25px"] --> E3["p 1.25em<br/>25 × 1.25 = 31.25px"]
    end
    subgraph REM["Cadena con rem — siempre mira la raíz"]
        R1["section 1.25rem<br/>= 20px"] --> R2["div 1.25rem<br/>= 20px"] --> R3["p 1.25rem<br/>= 20px"]
    end
    ROOT --> E1
    ROOT -.->|"referencia fija"| R1
    ROOT -.->|"referencia fija"| R2
    ROOT -.->|"referencia fija"| R3

Donde em sí brilla es en componentes que deben escalar como una unidad. Un botón cuyo padding, border-radius y gap estén en em cambia de tamaño completo con solo tocar su font-size:

.boton {
  font-size: 16px;
  padding: 0.625em 1.25em;              /* 10px 20px */
  border-radius: 0.375em;               /* 6px */
  border: 0.0625em solid currentColor;  /* 1px */
}
.boton--grande { font-size: 22px; }     /* todo lo demás escala solo */

rem: relativo al tamaño de fuente de la raíz

1rem equivale siempre al font-size del elemento raíz <html>, sin importar cuán anidado esté el elemento. En todos los navegadores ese valor por defecto es 16px, y esa es una cifra que conviene memorizar:

rem0.50.750.87511.251.523
px812141620243248
:root { font-size: 100%; }       /* no lo toques: respeta al usuario */

body     { font-size: 1rem; }      /* 16px */
h1       { font-size: 2.5rem; }    /* 40px */
h2       { font-size: 1.75rem; }   /* 28px */
.nota    { font-size: 0.875rem; }  /* 14px */
.seccion { padding: 2rem 1.5rem; } /* 32px vertical, 24px horizontal */

rem se prefiere sobre px para tipografía por accesibilidad: si una persona con baja visión sube el tamaño de letra por defecto de su navegador a 20px, todos los valores en rem crecen proporcionalmente, mientras que los px se quedan clavados. Por eso el truco frecuente de poner html { font-size: 62.5% } para que 1rem valga 10px, aunque funciona, anula justamente la preferencia que rem venía a respetar.

Unidades de viewport: vw, vh, vmin, vmax

El viewport es el área visible de la ventana del navegador. Estas unidades se calculan sobre ella:

UnidadSignificaEn una ventana de 1200 × 800
1vw1% del ancho del viewport12px
1vh1% del alto del viewport8px
1vmin1% de la dimensión menor8px (el alto es menor)
1vmax1% de la dimensión mayor12px (el ancho es mayor)
.hero          { height: 100vh; display: flex; align-items: center; }
.titulo-fluido { font-size: 5vw; }  /* crece con el ancho de la ventana */

Cuidado con dos trampas conocidas:

  1. 100vw incluye la barra de desplazamiento vertical. Si la página tiene scroll, un elemento con width: 100vw es más ancho que el contenido disponible y genera scroll horizontal. Usa width: 100% para ancho completo dentro del flujo normal.
  2. 100vh en móviles. La barra de direcciones de los navegadores móviles aparece y desaparece al hacer scroll, así que la altura real cambia. Para eso existen tres variantes:
UnidadNombre completoComportamiento
svhsmall viewport heightAltura con las barras del navegador visibles. La más pequeña.
lvhlarge viewport heightAltura con las barras ocultas. La más grande.
dvhdynamic viewport heightSe actualiza mientras las barras entran y salen.
.hero-movil    { min-height: 100svh; }  /* siempre cabe */
.hero-dinamico { min-height: 100dvh; }  /* se ajusta en vivo */

Existen svw, lvw y dvw de forma análoga para el ancho, además de svmin, lvmax y el resto de combinaciones.

Unidades relativas al tipo de letra

Menos frecuentes, pero muy útiles cuando las necesitas:

UnidadSe mide comoUso típico
chAncho del carácter 0 en la fuente actualLimitar el ancho de un texto: max-width: 65ch
exAltura de la letra x minúsculaAjustes tipográficos finos
lh / rlhEl line-height calculado del elemento o de la raízAlinear al ritmo vertical

ch resuelve un problema real de legibilidad: una línea de más de 75 caracteres cansa la vista, y max-width: 65ch la limita con precisión sin importar el tamaño de fuente.

article p { max-width: 65ch; line-height: 1.6; }

Tabla de referencia rápida de unidades

UnidadTipoReferenciaUso recomendado
pxAbsoluta1/96 pulgadaBordes, iconos, sombras, valores que no deben escalar
%RelativaEl contenedor, según la propiedadAnchos fluidos, layouts
emRelativafont-size propio (del padre en font-size)Espaciado de componentes que escalan juntos
remRelativafont-size de <html>, 16px por defectoTipografía y espaciado global
vw / vh / vmin / vmaxRelativaDimensiones del viewportSecciones a pantalla completa
svh / lvh / dvhRelativaViewport con o sin barras del navegadorAlturas completas en móvil
chRelativaAncho del carácter 0Ancho máximo de columnas de texto
pt / cm / mm / inAbsolutaMedidas físicasSolo hojas de impresión

Selectores en CSS

Un selector es la parte de la regla que decide a qué elementos se aplican las declaraciones. Hay muchos tipos, y dominarlos es lo que separa escribir CSS de pelear con él.

Selector de tipo (o de etiqueta)

Apunta a todos los elementos de un tipo determinado. Se escribe el nombre de la etiqueta, sin < ni >:

p  { line-height: 1.6; }
h1 { font-size: 2.5rem; }
a  { color: #3a7fd5; }
ul { padding-left: 1.5rem; }

Todos los <p> del documento reciben el mismo line-height sin que haya que tocarlos individualmente, y el que agregues mañana también.

Selector de clase

Una clase es una etiqueta que tú inventas y le pones a los elementos que quieras mediante el atributo class. En CSS se escribe con un punto delante.

.rojo   { color: #e74c3c; }
.verde  { color: #2ecc71; }
.grande { font-size: 2.5rem; }
.caja   { border: 1px solid #444444; padding: 1rem; }
<h1 class="verde grande">Mi título verde y grande</h1>
<p class="rojo">Mi contenido en rojo.</p>
<p class="verde">Este párrafo es verde.</p>
<div class="caja rojo">Una caja con borde y texto rojo.</div>

Dos cosas clave: un elemento puede llevar varias clases separadas por espacio (class="verde grande") y recibe los estilos de todas; y una misma clase puede repetirse en cuantos elementos quieras, que es precisamente su razón de ser.

Las clases son el mecanismo principal de trabajo en CSS. Los nombres deben describir el propósito más que la apariencia: .destacado envejece mejor que .rojo, porque el día que el destacado pase a ser azul no tendrás una clase llamada .rojo que pinta azul.

Selector de ID

Un ID identifica a un único elemento del documento. En HTML se declara con el atributo id, y en CSS se selecciona con # delante.

.verde  { color: #2ecc71; }
.rojo   { color: #e74c3c; }
.grande { font-size: 2.5rem; }
#unico  { color: #ffcc00; }
<h1 class="verde grande">Mi título</h1>
<p class="rojo">Mi contenido</p>
<p class="verde" id="unico">Este párrafo tiene clase verde e ID único</p>

El último párrafo tiene class="verde", que lo pintaría de verde, pero también id="unico", que lo pinta de amarillo. Gana el amarillo. No porque la regla #unico esté escrita después, sino porque un selector de ID tiene mayor especificidad que uno de clase. Lo veremos en detalle más adelante.

Reglas del atributo id: debe ser único en todo el documento (repetirlo es HTML inválido), no puede contener espacios, es sensible a mayúsculas (#Menu y #menu son distintos) y sirve además como destino de un enlace interno (<a href="#unico">) y como forma de encontrar el elemento desde JavaScript.

Recomendación práctica: usa clases para estilar y reserva los ID para anclas de navegación y para JavaScript. Los ID tienen tanta especificidad que después cuesta sobreescribirlos.

Selector universal y agrupación

El asterisco * selecciona absolutamente todos los elementos, y la coma agrupa selectores que comparten bloque:

/* Un "reset" mínimo muy común */
* { margin: 0; padding: 0; box-sizing: border-box; }

h1, h2, h3, h4 { font-family: Georgia, serif; line-height: 1.2; }

Escribir h1, h2, h3 es idéntico a escribir tres reglas separadas con el mismo contenido, pero más corto y más fácil de mantener.

Combinadores: relaciones entre elementos

Los combinadores expresan la posición relativa de un elemento respecto de otro en el árbol del documento.

CombinadorSintaxisSelecciona
DescendienteA B (espacio)Todos los B dentro de un A, a cualquier profundidad
Hijo directoA > BLos B que son hijos inmediatos de un A
Hermano adyacenteA + BEl primer B que viene justo después de un A, mismo padre
Hermano generalA ~ BTodos los B que vienen después de un A, mismo padre
/* Descendiente: cualquier p dentro de .articulo, sin importar cuán hondo */
.articulo p { color: #333333; }

/* Hijo directo: solo los p que cuelgan directamente de .articulo */
.articulo > p { font-weight: 600; }

/* Hermano adyacente: el primer p inmediatamente después de un h2 */
h2 + p { font-size: 1.125rem; color: #666666; }

/* Hermano general: todos los p que siguen a un h2 en el mismo padre */
h2 ~ p { letter-spacing: 0.01em; }
<div class="articulo">
  <h2>Un subtítulo</h2>
  <p>Primer párrafo: es hijo directo y hermano adyacente del h2.</p>
  <p>Segundo párrafo: hijo directo y hermano general.</p>
  <blockquote>
    <p>Párrafo anidado: es descendiente, pero no hijo directo.</p>
  </blockquote>
</div>

Sigue el árbol para ver por qué cada regla toca lo que toca:

flowchart TD
    DIV["div.articulo"]
    H2["h2"]
    P1["p — primer párrafo"]
    P2["p — segundo párrafo"]
    BQ["blockquote"]
    P3["p — párrafo anidado"]

    DIV --> H2
    DIV --> P1
    DIV --> P2
    DIV --> BQ
    BQ --> P3

    H2 -.->|"h2 + p<br/>solo el adyacente"| P1
    H2 -.->|"h2 ~ p<br/>todos los siguientes"| P1
    H2 -.->|"h2 ~ p"| P2
    DIV -.->|".articulo &gt; p<br/>solo hijos directos"| P1
    DIV -.->|".articulo &gt; p"| P2
    DIV -.->|".articulo p<br/>cualquier descendiente"| P3

P3 recibe .articulo p (es descendiente) pero no .articulo > p (su padre es el blockquote, no el div) ni h2 ~ p (no es hermano del h2).

Selectores de atributo

Permiten seleccionar por la presencia o el valor de cualquier atributo HTML:

SintaxisSelecciona elementos cuyo atributo…
[disabled]Existe, sin importar su valor
[type="email"]Vale exactamente email
[href^="https"]Empieza con https
[href$=".pdf"]Termina en .pdf
[href*="ninjas"]Contiene ninjas en cualquier parte
`[lang=“es”]`
[class~="caja"]Contiene la palabra caja en una lista separada por espacios
/* Marca visualmente los enlaces externos y las descargas */
a[href^="http"]::after { content: " ↗"; }
a[href$=".pdf"]::after { content: " (PDF)"; }

/* Estiliza campos de formulario según su tipo y estado */
input[type="email"] { border-color: #3a7fd5; }
input[disabled]     { opacity: 0.5; cursor: not-allowed; }

Pseudoclases

Una pseudoclase describe un estado o una posición del elemento, no algo que esté escrito en el HTML. Se escriben con dos puntos.

PseudoclaseAplica cuando
:hoverEl puntero está sobre el elemento
:focus / :focus-visibleTiene el foco del teclado / lo tiene y conviene mostrarlo
:activeSe está presionando
:visitedEs un enlace ya visitado
:first-child / :last-childEs el primer o el último hijo de su padre
:nth-child(n)Ocupa la posición n (acepta odd, even, 2n+1)
:not(sel)No cumple el selector indicado
:checked / :disabledEs un control marcado / deshabilitado
:emptyNo tiene hijos ni texto
:rootEs el elemento raíz, es decir <html>
table  { border-collapse: collapse; width: 100%; max-width: 480px; }
td, th { padding: 0.5rem 0.75rem; text-align: left; }

/* Filas alternadas: mucho más legible que pintarlas a mano */
tbody tr:nth-child(odd)  { background-color: #f5f5f5; }
tbody tr:nth-child(even) { background-color: #ffffff; }

/* Quitar el borde inferior a la última fila */
tbody tr:last-child td { border-bottom: none; }

/* Estados de un enlace: el orden importa */
a         { color: #3a7fd5; text-decoration: none; }
a:visited { color: #7b52ab; }
a:hover   { text-decoration: underline; }
a:active  { color: #e74c3c; }

El orden de las reglas de enlace no es arbitrario. :link, :visited, :hover, :active tienen la misma especificidad, así que la última escrita gana. Si pones :hover antes que :visited, un enlace visitado nunca cambiará al pasar el mouse.

Pseudoelementos

Un pseudoelemento no selecciona un elemento existente: crea o apunta a una parte de uno. Se escriben con dos pares de dos puntos (::) para distinguirlos de las pseudoclases.

PseudoelementoApunta a
::before / ::afterUn elemento generado antes o después del contenido
::first-line / ::first-letterLa primera línea o la primera letra de un bloque
::selectionEl texto que el usuario tiene seleccionado
::placeholderEl texto de marcador de un campo de formulario
::markerLa viñeta o número de un elemento de lista
/* ::before y ::after exigen la propiedad content, aunque sea vacía */
.aviso::before { content: "⚠ "; color: #e67e22; }

/* Capitular: la primera letra grande, flotando a la izquierda */
article p::first-letter {
  font-size: 3em;
  float: left;
  line-height: 0.8;
  padding-right: 0.1em;
}

::selection      { background-color: #ffcc00; color: #111111; }
input::placeholder { color: #999999; font-style: italic; }

Si un ::before o ::after no aparece, la causa casi siempre es la misma: falta content. Sin esa propiedad el pseudoelemento no se genera, aunque le des ancho, alto y color.

Cascada, especificidad y herencia

Llegamos al mecanismo que le da nombre a CSS. Cuando varias reglas quieren dar valores distintos a la misma propiedad del mismo elemento, el navegador tiene que elegir una. El proceso ocurre en este orden:

flowchart TD
    START["Varias reglas declaran la misma<br/>propiedad para el mismo elemento"]
    IMP{"¿Alguna lleva<br/>!important?"}
    START --> IMP
    IMP -->|"Sí, solo una"| WIN1["Gana esa"]
    IMP -->|"Sí, varias"| ESP
    IMP -->|"Ninguna"| ORIG["Se compara el origen:<br/>navegador &lt; usuario &lt; autor"]
    ORIG --> ESP["Se calcula la especificidad<br/>A-B-C de cada selector"]
    ESP --> COMP{"¿Hay una especificidad<br/>más alta?"}
    COMP -->|"Sí"| WIN2["Gana la más específica"]
    COMP -->|"Empate"| ORDEN["Gana la que aparece<br/>última en el código"]
    WIN1 --> FIN["Valor final aplicado"]
    WIN2 --> FIN
    ORDEN --> FIN

Cómo se calcula la especificidad

La especificidad se expresa como una terna de tres números que se comparan de izquierda a derecha, como si fuera un podio: la primera columna manda sobre las otras dos.

ColumnaSuma 1 por cada…Peso
ASelector de ID (#unico)Máximo
BClase (.caja), atributo ([type=text]), pseudoclase (:hover)Medio
CTipo (p, div) y pseudoelemento (::before)Mínimo

El selector universal * y los combinadores (>, +, ~, espacio) no suman nada.

Ejemplos calculados:

SelectorABCLectura
p0010-0-1
p::before0020-0-2
.caja0100-1-0
.caja p / a:hover0110-1-1
.caja.destacada0200-2-0
#unico1001-0-0
#unico p .nota1111-1-1
style="..." (inline)Por encima de todos los selectores
Cualquier cosa con !importantPor encima del inline

La comparación es columna por columna, no una suma total. Un selector 1-0-0 gana a un 0-10-10. Diez clases no alcanzan a un solo ID.

/* 0-0-1  → azul */
p { color: blue; }

/* 0-1-1  → verde, gana al anterior */
.contenido p { color: green; }

/* 1-0-0  → naranjo, gana a todos los de arriba */
#principal { color: orange; }

/* 0-2-0  → NO gana al ID, aunque esté escrito después */
.contenido.destacado { color: purple; }

Un caso concreto que aparece en la fuente original de este curso y que conviene analizar:

div.outer p       { color: orange; }
div:not(.outer) p { color: lime; }

Ambos selectores valen 0-1-2: una clase o pseudoclase y dos tipos. :not() en sí mismo no suma especificidad, pero lo que va dentro de sus paréntesis sí, y ahí hay una clase. Como empatan, decide el orden: en el código de arriba ganaría lime para cualquier p que cumpla ambas condiciones. La clave es que en la práctica esas condiciones son mutuamente excluyentes: un div no puede tener y no tener .outer al mismo tiempo.

!important y por qué evitarlo

Agregando !important al final de una declaración, esta sube por encima de toda la comparación de especificidad:

p { color: blue !important; }
#principal p { color: orange; }  /* pierde: el azul es !important */

Es tentador y es un problema: una vez que lo usas para ganar una pelea, la única forma de ganarle a ese estilo después es otro !important con más especificidad, y en pocos meses la hoja se vuelve una escalada de la que no se sale. Resérvalo para sobreescribir CSS de un tercero que no puedes modificar.

Herencia

Algunas propiedades se heredan automáticamente del padre a los hijos y otras no. La especificación lo decide propiedad por propiedad siguiendo una lógica razonable: se hereda lo tipográfico, no se hereda lo geométrico.

Se heredanNo se heredan
color, visibility, cursor, list-stylemargin, padding, border, background
font-family, font-size, font-weight, font-stylewidth, height, overflow, float
line-height, letter-spacing, word-spacingdisplay, position, box-shadow
text-align, text-indent, text-transformz-index, transform, opacity

Por eso declarar la tipografía una sola vez en body funciona para todo el documento:

/* Todos los p, li, td y h2 del documento reciben estos tres valores */
body { font-family: system-ui, sans-serif; color: #1a1a1a; line-height: 1.6; }

Existen además cuatro palabras clave universales, aceptadas por todas las propiedades, que dan control explícito sobre la herencia:

Palabra claveEfecto
inheritToma el valor computado del padre, incluso en propiedades que no se heredan
initialVuelve al valor inicial de la especificación
unsetSe comporta como inherit si la propiedad se hereda, y como initial si no
revertVuelve al valor que la hoja del navegador le habría dado
/* Forzar que un botón herede la tipografía en vez de usar la del sistema */
button { font: inherit; color: inherit; }

Variables CSS: propiedades personalizadas

Con lo visto ya puedes escribir hojas de estilo completas, pero hay una pieza más que multiplica lo que aprendiste sobre colores y unidades: las propiedades personalizadas, conocidas coloquialmente como variables CSS.

Se declaran con dos guiones al inicio y se leen con la función var():

:root {
  --color-marca: hsl(214, 65%, 50%);
  --color-marca-oscuro: hsl(214, 65%, 32%);
  --color-texto: #1a1a1a;
  --color-fondo: #ffffff;

  --espacio-sm: 0.5rem;
  --espacio-md: 1rem;
  --espacio-lg: 2rem;

  --radio: 0.5rem;
  --fuente: system-ui, -apple-system, "Segoe UI", sans-serif;
}

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

.boton {
  background-color: var(--color-marca);
  color: white;
  padding: var(--espacio-sm) var(--espacio-md);
  border: none;
  border-radius: var(--radio);
}

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

Se declaran sobre :root (que es <html>) para que estén disponibles en todo el documento, porque las variables CSS se heredan como cualquier otra propiedad. var() acepta además un valor de reserva por si la variable no existe: var(--color-marca, #3a7fd5).

Proyecto: una tarjeta de perfil completa

Este ejemplo junta las tres formas de aplicar CSS, las cuatro notaciones de color, cinco unidades distintas y siete tipos de selector. Guárdalo como tarjeta.html y ábrelo en el navegador.

<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Tarjeta de perfil</title>
    <style>
      /* ---- Variables: toda la paleta se deriva de --matiz ---- */
      :root {
        --matiz: 214;
        --marca:        hsl(var(--matiz), 65%, 50%);
        --marca-oscuro: hsl(var(--matiz), 65%, 34%);
        --marca-palido: hsl(var(--matiz), 65%, 95%);
        --texto: #1a1a1a;
        --tenue: #6b7280;
        --borde: #e5e7eb;
        --radio: 0.75rem;
        --sombra: 0 1px 3px rgba(0, 0, 0, 0.1), 0 8px 24px rgba(0, 0, 0, 0.08);
      }

      /* ---- Reset mínimo y base: la herencia reparte tipografía y color ---- */
      *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

      body {
        font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
        color: var(--texto);
        background-color: #f9fafb;
        line-height: 1.6;
        min-height: 100svh;
        display: flex; align-items: center; justify-content: center;
        padding: 1.5rem;
      }

      /* ---- Selector de clase ---- */
      .tarjeta {
        width: 100%;
        max-width: 24rem;                 /* 384px */
        background-color: white;
        border: 1px solid var(--borde);
        border-radius: var(--radio);
        box-shadow: var(--sombra);
        overflow: hidden;
      }

      /* ---- Degradado calculado con hsl y calc ---- */
      .tarjeta__cabecera {
        height: 6rem;
        background: linear-gradient(135deg,
          hsl(var(--matiz), 65%, 50%),
          hsl(calc(var(--matiz) + 40), 70%, 45%));
      }

      /* ---- Avatar: em, todo escala con su propio font-size ---- */
      .avatar {
        font-size: 1.75rem;               /* 28px: la base de sus em */
        width: 2.85em; height: 2.85em;    /* ~80px */
        margin: -2.5rem auto 0;
        border: 0.14em solid white;       /* ~4px */
        border-radius: 50%;
        background-color: var(--marca-oscuro);
        color: white;
        font-weight: 700;
        display: flex; align-items: center; justify-content: center;
      }

      .tarjeta__cuerpo    { padding: 1rem 1.5rem 1.5rem; text-align: center; }
      .tarjeta__cuerpo h2 { font-size: 1.375rem; margin-bottom: 0.125rem; }

      /* Hermano adyacente: solo el p que sigue al h2 */
      .tarjeta__cuerpo h2 + p {
        color: var(--tenue);
        font-size: 0.875rem;              /* 14px */
        margin-bottom: 1rem;
      }

      /* ---- Etiquetas: pseudoelemento ::before ---- */
      .etiquetas {
        list-style: none;
        display: flex; flex-wrap: wrap; justify-content: center;
        gap: 0.5rem;
        margin-bottom: 1.25rem;
      }
      .etiquetas li {
        background-color: var(--marca-palido); color: var(--marca-oscuro);
        font-size: 0.75rem;               /* 12px */
        padding: 0.25em 0.75em;           /* em: escala con esos 12px */
        border-radius: 999px;
      }
      .etiquetas li::before { content: "# "; opacity: 0.6; }

      /* ---- Botón: pseudoclases de estado ---- */
      .boton {
        display: block; width: 100%;
        font: inherit;                    /* hereda en vez de usar la del sistema */
        font-weight: 600;
        color: white;
        background-color: var(--marca);
        border: none;
        padding: 0.75em 1em;
        border-radius: var(--radio);
        cursor: pointer;
        transition: background-color 150ms ease;
      }
      .boton:hover  { background-color: var(--marca-oscuro); }
      .boton:active { background-color: hsl(var(--matiz), 65%, 26%); }
      .boton:focus-visible {
        outline: 3px solid hsl(var(--matiz) 65% 50% / 45%);
        outline-offset: 2px;
      }

      /* ---- Selector de atributo: marca los enlaces externos ---- */
      a[href^="http"]::after { content: " ↗"; font-size: 0.8em; }
      .enlace {
        display: inline-block; margin-top: 0.75rem;
        color: var(--marca); font-size: 0.8125rem;   /* 13px */
      }

      /* ---- Estadísticas: hijo directo y :not() ---- */
      .stats { display: flex; border-top: 1px solid var(--borde); }
      .stats > div { flex: 1; padding: 0.75rem 0.5rem; text-align: center; }
      .stats > div:not(:last-child) { border-right: 1px solid var(--borde); }
      .stats strong { display: block; font-size: 1.125rem; }
      .stats span {
        font-size: 0.75rem; color: var(--tenue);
        text-transform: uppercase; letter-spacing: 0.05em;
      }
    </style>
  </head>
  <body>
    <article class="tarjeta">
      <div class="tarjeta__cabecera"></div>
      <div class="avatar">SC</div>

      <div class="tarjeta__cuerpo">
        <h2>Super Club Net</h2>
        <p>Aprendiendo HTML, CSS y JavaScript</p>

        <ul class="etiquetas">
          <li>html</li>
          <li>css</li>
          <li>javascript</li>
        </ul>

        <button class="boton" type="button">Seguir</button>
        <a class="enlace" href="https://ninjas.cl">Sitio del autor original</a>
      </div>

      <div class="stats">
        <div><strong>16</strong><span>Capítulos</span></div>
        <div><strong>147</strong><span>Colores</span></div>
        <div><strong>9</strong><span>Unidades</span></div>
      </div>
    </article>
  </body>
</html>

Cuatro cosas que vale la pena revisar en ese archivo: la paleta completa sale de un solo número, --matiz: 214, así que cambiarlo a 340 vuelve la tarjeta entera rosada de forma coherente; min-height: 100svh centra la tarjeta sin que la barra de direcciones del móvil provoque un salto; max-width: 24rem combinado con width: 100% respeta el tamaño de letra del usuario y a la vez encoge la tarjeta en pantallas angostas; y .stats > div:not(:last-child) pone el separador entre columnas sin dejar uno colgando al final.

Errores comunes

ErrorQué pasaCausaSolución
margin: 20 pxLa declaración se ignora por completoEspacio entre número y unidadEscribir margin: 20px, siempre pegado
width: 300El ancho no cambiaFalta la unidadwidth: 300px. Solo el 0 puede ir sin unidad
// esto es una nota en CSSSe pierde esa línea y a veces la siguienteCSS no tiene comentarios de una líneaUsar /* esto es una nota */
El <link> no aplica estilosLa página se ve sin CSSFalta rel="stylesheet" o la ruta del href está malVerificar el atributo rel y la ruta relativa al HTML
.mi clase { }El selector no aplicaEl espacio lo convierte en combinador descendienteLos nombres de clase no llevan espacio: .mi-clase
class=".destacado"La clase nunca coincideEl punto va solo en el CSS, no en el HTMLEn HTML: class="destacado". En CSS: .destacado
Dos elementos con el mismo idEl HTML es inválido y JS solo encuentra el primeroLos ID deben ser únicosUsar una clase si necesitas repetirlo
No logras sobreescribir un estiloLa regla nueva no tiene efectoLa otra regla tiene mayor especificidad o es inlineComparar especificidad; subirla con una clase extra, no con !important
#123 no da el color esperadoSale #112233La abreviatura duplica cada dígitoEscribir los 6 dígitos si el color no es simétrico
::before no apareceEl pseudoelemento no se generaFalta la propiedad contentAgregar content: ""; aunque sea vacío
a:hover no cambia el color en enlaces visitadosEl hover se ignora:visited está escrito después de :hoverOrden correcto: :link, :visited, :hover, :active
Tamaños que se disparan al anidarTextos cada vez más grandesem se multiplica en cada nivelUsar rem para tipografía y em solo dentro de un componente
width: 100vw genera scroll horizontalAparece una barra abajo100vw incluye el ancho de la barra de scrollUsar width: 100%
height: 100vh salta en el celularEl contenido se corta o se mueveLa barra del navegador cambia la altura visibleUsar 100svh o 100dvh
opacity: 0.5 atenúa también el textoTodo el bloque queda pálidoopacity afecta al elemento completoUsar rgba() o hsl(... / 50%) solo en el color de fondo
padding-top: 10% da un valor inesperadoEl espacio no coincide con el altoLos porcentajes de padding se calculan sobre el anchoUsar rem o px si quieres un valor vertical predecible
El CSS externo no se actualizaVes la versión antigua tras editarlaEl navegador tiene el archivo en cachéRecargar forzando con Ctrl+F5, o agregar ?v=2 al href

Ejercicios propuestos

  1. De inline a externo. Toma la página que hiciste en el capítulo 1, agrégale al menos seis declaraciones usando el atributo style en distintos elementos, y luego migra todo a un archivo estilos.css enlazado con <link>. El resultado visual debe ser idéntico y el HTML debe quedar sin ningún atributo style.

  2. Traductor de colores. Crea una página con seis cuadrados de 120 × 120 píxeles. Pinta cada par con el mismo color, expresado una vez en hexadecimal y otra en rgb(). Debajo de cada cuadrado escribe la notación usada. Comprueba en el navegador que cada par es indistinguible.

  3. Paleta con HSL. Elige un matiz entre 0 y 360 y genera cinco tonos del mismo color variando solo la luminosidad: 15%, 30%, 50%, 70% y 90%. Preséntalos como una franja horizontal. Luego repite el ejercicio bajando la saturación a 25% y compara ambas franjas.

  4. em contra rem. Construye dos columnas. En la izquierda anida cuatro <div> con font-size: 1.2em cada uno. En la derecha anida cuatro con font-size: 1.2rem. Escribe dentro de cada uno el tamaño resultante en píxeles que calculaste a mano, y verifica con las herramientas de desarrollo del navegador si acertaste.

  5. Tabla con filas alternadas. Crea una tabla de al menos ocho filas con datos reales. Usa :nth-child(odd) para pintar las filas impares, :first-child para destacar la cabecera y :last-child para quitar el borde inferior de la última fila. No agregues ninguna clase a las filas del HTML: todo debe resolverse con pseudoclases.

  6. Duelo de especificidad. Escribe un párrafo con class="destacado" e id="principal" dentro de un <div class="contenido">. Después crea cinco reglas que le den un color distinto: una por tipo, una por clase, una por ID, una combinada .contenido .destacado, y una inline. Antes de abrir el navegador, escribe en un comentario qué color crees que ganará y por qué. Luego comprueba.

  7. Cambiar el tema con una variable. Parte del proyecto de la tarjeta de perfil. Extrae todos los colores a propiedades personalizadas en :root que dependan de --matiz. Después agrega una segunda tarjeta con style="--matiz: 340" y verifica que cambia de paleta completa sin tocar una sola regla.

  8. Sección a pantalla completa. Crea un hero que ocupe 100svh de alto, centrado vertical y horizontalmente, con un título cuyo font-size esté en vw y un párrafo con max-width: 60ch. Redimensiona la ventana y observa cuál elemento cambia de tamaño y cuál no.

Lo que viene

Ya sabes conectar CSS a un documento de tres formas distintas, expresar cualquier color en cuatro notaciones, elegir la unidad de medida correcta para cada situación y apuntar con precisión a los elementos mediante selectores, combinadores, pseudoclases y pseudoelementos. También entiendes por qué a veces una regla “no funciona”: casi siempre es la especificidad haciendo su trabajo.

En el capítulo 3 volvemos al HTML para trabajar el contenido en serio: la jerarquía de encabezados de <h1> a <h6>, párrafos, saltos de línea, énfasis semántico, listas ordenadas y no ordenadas, y los caracteres especiales con sus entidades HTML (&amp;, &lt;, &nbsp;), que es donde se originan buena parte de los símbolos rotos que ves en la web. Todo lo que aprendiste aquí sobre selectores te va a servir de inmediato para darle forma a ese contenido. El recorrido completo del curso está en el índice.


Este capítulo cubre el temario de la sección CSS de la guía Super Club Net de Camilo Castro (Ninjas.cl), publicada bajo licencia CC BY-NC-SA 4.0, con material propio, ejemplos ampliados y explicaciones adicionales.