CSS desde cero: estilos, colores, unidades y selectores
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:
h1es 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>.
3. CSS externo: la etiqueta <link>
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
| Criterio | Inline (style="...") | Interno (<style>) | Externo (<link>) |
|---|---|---|---|
| Dónde se escribe | En el atributo del elemento | En el <head> del documento | En un archivo .css aparte |
| Usa selectores | No | Sí | Sí |
| Alcance | Un solo elemento | Ese documento | Todos los documentos que lo enlacen |
| Se puede cachear y reutilizar | No | No (viaja con el HTML) | Sí |
| Peso de especificidad | Altísimo | Normal | Normal |
| Requiere petición HTTP extra | No | No | Sí, la primera vez |
| Uso recomendado | Valores dinámicos desde JS, pruebas puntuales | Página única, demos, correo electrónico | Cualquier 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:
| Nombre | Hexadecimal | Comentario |
|---|---|---|
white / black | #ffffff / #000000 | Blanco y negro puros |
red / blue | #ff0000 / #0000ff | Canales saturados |
lime / green | #00ff00 / #008000 | Cuidado: green es bastante más oscuro |
yellow | #ffff00 | Rojo + verde al máximo |
magenta / fuchsia | #ff00ff | Dos nombres, mismo color |
cyan / aqua | #00ffff | Dos nombres, mismo color |
gray / grey | #808080 | Ambas grafías son válidas |
orange | #ffa500 | El único nombre agregado en CSS 2.1 |
rebeccapurple | #663399 | Agregado 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:
| Abreviado | Equivale a | Color |
|---|---|---|
#000 | #000000 | Negro |
#fff | #ffffff | Blanco |
#f00 | #ff0000 | Rojo |
#369 | #336699 | Azul apagado |
#ccc | #cccccc | Gris 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ón | Ejemplo | Alfa | Legible por humanos | Cuándo usarla |
|---|---|---|---|---|
| Nombre | crimson | No (salvo transparent) | Muy alta | Prototipos, ejemplos, depuración |
| Hex 6 o 3 dígitos | #3a7fd5, #39d | No | Baja | El estándar de facto, lo que entrega el diseñador |
| Hex 8 dígitos | #3a7fd580 | Sí | Muy baja | Cuando ya tienes el hex y necesitas transparencia |
rgb() / rgba() | rgb(58 127 213 / 50%) | Sí | Media | Valores calculados, superposiciones, sombras y velos |
hsl() / hsla() | hsl(214 65% 50% / 50%) | Sí | Alta | Diseñ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
| Unidad | Nombre | Equivalencia | Uso |
|---|---|---|---|
px | Píxel CSS | 1/96 de pulgada | Pantalla, es la unidad absoluta que sí se usa |
pt | Punto | 1/72 de pulgada = 1,333px | Estilos para impresión |
pc | Pica | 12pt = 16px | Tipografía tradicional, casi no se usa |
cm / mm / in | Centímetro, milímetro, pulgada | 37,8px / 3,78px / 96px | Hojas de impresión |
Q | Cuarto de milímetro | 0,945px | Muy 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.
| Propiedad | El 100% se refiere a |
|---|---|
width | El ancho del bloque contenedor |
height | El alto del bloque contenedor (si tiene alto definido) |
margin y padding (los cuatro lados) | El ancho del contenedor, incluso arriba y abajo |
font-size | El font-size del elemento padre |
line-height | El font-size del propio elemento |
top / left | Alto / 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:
| rem | 0.5 | 0.75 | 0.875 | 1 | 1.25 | 1.5 | 2 | 3 |
|---|---|---|---|---|---|---|---|---|
| px | 8 | 12 | 14 | 16 | 20 | 24 | 32 | 48 |
: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:
| Unidad | Significa | En una ventana de 1200 × 800 |
|---|---|---|
1vw | 1% del ancho del viewport | 12px |
1vh | 1% del alto del viewport | 8px |
1vmin | 1% de la dimensión menor | 8px (el alto es menor) |
1vmax | 1% de la dimensión mayor | 12px (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:
100vwincluye la barra de desplazamiento vertical. Si la página tiene scroll, un elemento conwidth: 100vwes más ancho que el contenido disponible y genera scroll horizontal. Usawidth: 100%para ancho completo dentro del flujo normal.100vhen 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:
| Unidad | Nombre completo | Comportamiento |
|---|---|---|
svh | small viewport height | Altura con las barras del navegador visibles. La más pequeña. |
lvh | large viewport height | Altura con las barras ocultas. La más grande. |
dvh | dynamic viewport height | Se 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:
| Unidad | Se mide como | Uso típico |
|---|---|---|
ch | Ancho del carácter 0 en la fuente actual | Limitar el ancho de un texto: max-width: 65ch |
ex | Altura de la letra x minúscula | Ajustes tipográficos finos |
lh / rlh | El line-height calculado del elemento o de la raíz | Alinear 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
| Unidad | Tipo | Referencia | Uso recomendado |
|---|---|---|---|
px | Absoluta | 1/96 pulgada | Bordes, iconos, sombras, valores que no deben escalar |
% | Relativa | El contenedor, según la propiedad | Anchos fluidos, layouts |
em | Relativa | font-size propio (del padre en font-size) | Espaciado de componentes que escalan juntos |
rem | Relativa | font-size de <html>, 16px por defecto | Tipografía y espaciado global |
vw / vh / vmin / vmax | Relativa | Dimensiones del viewport | Secciones a pantalla completa |
svh / lvh / dvh | Relativa | Viewport con o sin barras del navegador | Alturas completas en móvil |
ch | Relativa | Ancho del carácter 0 | Ancho máximo de columnas de texto |
pt / cm / mm / in | Absoluta | Medidas físicas | Solo 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.
| Combinador | Sintaxis | Selecciona |
|---|---|---|
| Descendiente | A B (espacio) | Todos los B dentro de un A, a cualquier profundidad |
| Hijo directo | A > B | Los B que son hijos inmediatos de un A |
| Hermano adyacente | A + B | El primer B que viene justo después de un A, mismo padre |
| Hermano general | A ~ B | Todos 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 > p<br/>solo hijos directos"| P1
DIV -.->|".articulo > 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:
| Sintaxis | Selecciona 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.
| Pseudoclase | Aplica cuando |
|---|---|
:hover | El puntero está sobre el elemento |
:focus / :focus-visible | Tiene el foco del teclado / lo tiene y conviene mostrarlo |
:active | Se está presionando |
:visited | Es un enlace ya visitado |
:first-child / :last-child | Es 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 / :disabled | Es un control marcado / deshabilitado |
:empty | No tiene hijos ni texto |
:root | Es 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.
| Pseudoelemento | Apunta a |
|---|---|
::before / ::after | Un elemento generado antes o después del contenido |
::first-line / ::first-letter | La primera línea o la primera letra de un bloque |
::selection | El texto que el usuario tiene seleccionado |
::placeholder | El texto de marcador de un campo de formulario |
::marker | La 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 < usuario < 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.
| Columna | Suma 1 por cada… | Peso |
|---|---|---|
| A | Selector de ID (#unico) | Máximo |
| B | Clase (.caja), atributo ([type=text]), pseudoclase (:hover) | Medio |
| C | Tipo (p, div) y pseudoelemento (::before) | Mínimo |
El selector universal * y los combinadores (>, +, ~, espacio) no suman nada.
Ejemplos calculados:
| Selector | A | B | C | Lectura |
|---|---|---|---|---|
p | 0 | 0 | 1 | 0-0-1 |
p::before | 0 | 0 | 2 | 0-0-2 |
.caja | 0 | 1 | 0 | 0-1-0 |
.caja p / a:hover | 0 | 1 | 1 | 0-1-1 |
.caja.destacada | 0 | 2 | 0 | 0-2-0 |
#unico | 1 | 0 | 0 | 1-0-0 |
#unico p .nota | 1 | 1 | 1 | 1-1-1 |
style="..." (inline) | — | — | — | Por encima de todos los selectores |
Cualquier cosa con !important | — | — | — | Por 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 heredan | No se heredan |
|---|---|
color, visibility, cursor, list-style | margin, padding, border, background |
font-family, font-size, font-weight, font-style | width, height, overflow, float |
line-height, letter-spacing, word-spacing | display, position, box-shadow |
text-align, text-indent, text-transform | z-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 clave | Efecto |
|---|---|
inherit | Toma el valor computado del padre, incluso en propiedades que no se heredan |
initial | Vuelve al valor inicial de la especificación |
unset | Se comporta como inherit si la propiedad se hereda, y como initial si no |
revert | Vuelve 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
| Error | Qué pasa | Causa | Solución |
|---|---|---|---|
margin: 20 px | La declaración se ignora por completo | Espacio entre número y unidad | Escribir margin: 20px, siempre pegado |
width: 300 | El ancho no cambia | Falta la unidad | width: 300px. Solo el 0 puede ir sin unidad |
// esto es una nota en CSS | Se pierde esa línea y a veces la siguiente | CSS no tiene comentarios de una línea | Usar /* esto es una nota */ |
El <link> no aplica estilos | La página se ve sin CSS | Falta rel="stylesheet" o la ruta del href está mal | Verificar el atributo rel y la ruta relativa al HTML |
.mi clase { } | El selector no aplica | El espacio lo convierte en combinador descendiente | Los nombres de clase no llevan espacio: .mi-clase |
class=".destacado" | La clase nunca coincide | El punto va solo en el CSS, no en el HTML | En HTML: class="destacado". En CSS: .destacado |
Dos elementos con el mismo id | El HTML es inválido y JS solo encuentra el primero | Los ID deben ser únicos | Usar una clase si necesitas repetirlo |
| No logras sobreescribir un estilo | La regla nueva no tiene efecto | La otra regla tiene mayor especificidad o es inline | Comparar especificidad; subirla con una clase extra, no con !important |
#123 no da el color esperado | Sale #112233 | La abreviatura duplica cada dígito | Escribir los 6 dígitos si el color no es simétrico |
::before no aparece | El pseudoelemento no se genera | Falta la propiedad content | Agregar content: ""; aunque sea vacío |
a:hover no cambia el color en enlaces visitados | El hover se ignora | :visited está escrito después de :hover | Orden correcto: :link, :visited, :hover, :active |
| Tamaños que se disparan al anidar | Textos cada vez más grandes | em se multiplica en cada nivel | Usar rem para tipografía y em solo dentro de un componente |
width: 100vw genera scroll horizontal | Aparece una barra abajo | 100vw incluye el ancho de la barra de scroll | Usar width: 100% |
height: 100vh salta en el celular | El contenido se corta o se mueve | La barra del navegador cambia la altura visible | Usar 100svh o 100dvh |
opacity: 0.5 atenúa también el texto | Todo el bloque queda pálido | opacity afecta al elemento completo | Usar rgba() o hsl(... / 50%) solo en el color de fondo |
padding-top: 10% da un valor inesperado | El espacio no coincide con el alto | Los porcentajes de padding se calculan sobre el ancho | Usar rem o px si quieres un valor vertical predecible |
| El CSS externo no se actualiza | Ves la versión antigua tras editarla | El navegador tiene el archivo en caché | Recargar forzando con Ctrl+F5, o agregar ?v=2 al href |
Ejercicios propuestos
-
De inline a externo. Toma la página que hiciste en el capítulo 1, agrégale al menos seis declaraciones usando el atributo
styleen distintos elementos, y luego migra todo a un archivoestilos.cssenlazado con<link>. El resultado visual debe ser idéntico y el HTML debe quedar sin ningún atributostyle. -
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. -
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.
-
emcontrarem. Construye dos columnas. En la izquierda anida cuatro<div>confont-size: 1.2emcada uno. En la derecha anida cuatro confont-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. -
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-childpara destacar la cabecera y:last-childpara quitar el borde inferior de la última fila. No agregues ninguna clase a las filas del HTML: todo debe resolverse con pseudoclases. -
Duelo de especificidad. Escribe un párrafo con
class="destacado"eid="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. -
Cambiar el tema con una variable. Parte del proyecto de la tarjeta de perfil. Extrae todos los colores a propiedades personalizadas en
:rootque dependan de--matiz. Después agrega una segunda tarjeta constyle="--matiz: 340"y verifica que cambia de paleta completa sin tocar una sola regla. -
Sección a pantalla completa. Crea un
heroque ocupe100svhde alto, centrado vertical y horizontalmente, con un título cuyofont-sizeesté envwy un párrafo conmax-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 (&, <, ), 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.