Módulo 07: Fundamentos de CSS

Objetivos del módulo

  • Entender qué es CSS y qué papel juega junto a HTML
  • Conocer la sintaxis de una regla CSS: selector, declaración, propiedad y valor
  • Saber incluir CSS en línea, en una hoja interna y en una hoja externa
  • Dominar los selectores básicos, de atributo y los combinadores
  • Usar pseudo-clases y pseudo-elementos
  • Comprender la cascada: origen, importancia, especificidad y orden
  • Calcular la especificidad de cualquier selector
  • Entender la herencia y las palabras clave inherit, initial, unset y revert
  • Conocer la hoja de estilos del navegador y aplicar un reset moderno
  • Depurar CSS con las DevTools
  • Organizar tus clases con la metodología BEM

1. ¿Qué es CSS?

CSS (Cascading Style Sheets, hojas de estilo en cascada) es el lenguaje que describe cómo se ve un documento HTML: colores, tipografías, tamaños, espacios, disposición de los elementos, animaciones…

📘 Concepto: Si una página web fuera una casa, HTML sería la estructura (paredes, puertas, habitaciones), CSS la decoración (pintura, muebles, iluminación) y JavaScript la instalación eléctrica y domótica (lo que hace que las cosas reaccionen). Puedes cambiar toda la decoración sin tirar ni una sola pared.

Separación de contenido y presentación

Antes de CSS, el aspecto se mezclaba con el contenido mediante etiquetas como <font> o atributos como bgcolor. Era un caos: para cambiar el color de los títulos de un sitio de 200 páginas había que editar 200 ficheros. Con CSS, el HTML describe qué es cada cosa (un título, un párrafo, una lista), el CSS describe cómo se ve, y un único fichero .css puede dar estilo a todo un sitio.

¿CSS3? ¿CSS4? Tras CSS 2.1, el W3C dividió CSS en módulos independientes (Selectores, Colores, Flexbox, Grid, Animaciones…), cada uno con su propio nivel y ritmo. Por eso “CSS3” se usa de forma popular para hablar del “CSS moderno”, pero no existe un “CSS4” como versión única. Hoy simplemente se habla de CSS, que evoluciona continuamente.

💡 Consejo: Para saber si una propiedad funciona en los navegadores actuales, consulta caniuse.com o la etiqueta Baseline de MDN, que indica si una característica está disponible en todos los navegadores principales.


2. Sintaxis de CSS

Una hoja de estilos es una lista de reglas. Cada regla tiene un selector (a qué elementos se aplica) y un bloque de declaraciones entre llaves.

/* selector    bloque de declaraciones */
h1 {
    color: navy;          /* declaración: propiedad + valor */
    font-size: 2rem;      /* cada declaración termina en ; */
}
Parte Qué es Ejemplo
Regla Selector + bloque de declaraciones h1 { color: navy; }
Selector Indica a qué elementos se aplica h1, .tarjeta, #menu
Declaración Par propiedad-valor terminado en ; color: navy;
Propiedad Característica que quieres cambiar color, margin, font-size
Valor El valor que le das a la propiedad navy, 10px, bold

Reglas de escritura

  • Las propiedades y la mayoría de valores no distinguen mayúsculas, pero por convención se escriben en minúsculas.
  • Los espacios y saltos de línea no importan; úsalos para que el código sea legible.
  • El ; de la última declaración es opcional, pero ponlo siempre: así no olvidarás añadirlo cuando escribas otra debajo.
  • Puedes agrupar selectores separándolos con comas para aplicarles las mismas declaraciones.
h1, h2, h3 { font-family: Georgia, serif; color: #1d3557; }   /* agrupación */

¿Qué pasa si me equivoco?

CSS es tolerante a errores: si una declaración es inválida, el navegador la ignora y sigue con la siguiente. No hay mensajes de error en pantalla.

p {
    color: rojo;        /* ✗ "rojo" no es un color válido: se ignora */
    font-size: 18 px;   /* ✗ no puede haber espacio entre número y unidad: se ignora */
    margin: 1rem;       /* ✓ esta sí se aplica */
}

⚠️ Importante: Esta tolerancia es una bendición (una errata no rompe la página) y una maldición (el error pasa desapercibido). Las DevTools tachan y marcan con un aviso las declaraciones inválidas; lo verás en la sección 15.

Propiedades abreviadas (shorthand)

Muchas propiedades tienen una versión abreviada que agrupa varias. Por ejemplo, margin: 10px 20px; equivale a escribir por separado margin-top, margin-right, margin-bottom y margin-left. Las verás en detalle en los próximos módulos (margin, padding, border, font, background, flex…).


3. Formas de incluir CSS

Hay tres maneras de aplicar CSS a un documento HTML.

3.1. CSS en línea (atributo style)

Las declaraciones se escriben directamente en el atributo style del elemento:

<p style="color: crimson; font-weight: bold;">Este párrafo es rojo y en negrita.</p>

Solo afecta a ese elemento. Es cómodo para una prueba rápida, pero mezcla contenido y presentación, no se puede reutilizar y tiene una prioridad altísima (cuesta sobrescribirlo).

3.2. Hoja de estilos interna (etiqueta <style>)

Las reglas se escriben dentro de una etiqueta <style> en el <head>:

<head>
    <meta charset="UTF-8">
    <title>CSS interno</title>
    <style>
        body { font-family: system-ui, sans-serif; background-color: #f1faee; }
        h1   { color: #1d3557; }
    </style>
</head>

Útil para páginas sueltas, ejemplos o estilos críticos, pero los estilos solo sirven para ese documento.

Las reglas se escriben en un fichero .css independiente y se enlazan desde el <head> con <link>. Es la forma profesional.

Estructura del proyecto:

mi-web/
├── index.html
├── contacto.html
└── css/
    └── estilos.css

Fichero index.html:

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Mi web</title>
    <link rel="stylesheet" href="css/estilos.css">
</head>
<body>
    <h1>Bienvenido</h1>
</body>

Fichero css/estilos.css (sin etiquetas HTML, solo CSS):

body { font-family: system-ui, sans-serif; line-height: 1.6; margin: 2rem; }
h1   { color: #e63946; }

💻 Ejemplo: Crea los dos ficheros anteriores, abre index.html con Live Server y cambia el color de h1 en estilos.css. Al guardar, la página se actualiza. Si además enlazas la misma hoja desde contacto.html, ambas páginas cambiarán a la vez.

Forma Dónde Alcance Reutilizable Uso recomendado
En línea Atributo style Un elemento No Pruebas puntuales, estilos generados por JavaScript
Interna <style> en el <head> Un documento No Ejemplos, páginas sueltas, CSS crítico
Externa Fichero .css + <link> Todo el sitio Sí Siempre que sea posible

Varias hojas y el atributo media

Puedes enlazar varias hojas; se aplican en el orden en que aparecen. Con el atributo media una hoja solo se aplica en ciertas condiciones:

<link rel="stylesheet" href="css/base.css">
<link rel="stylesheet" href="css/componentes.css">
<link rel="stylesheet" href="css/impresion.css" media="print">

@import (mejor evitarlo)

Desde un fichero CSS se puede importar otro:

/* Debe ir al principio del fichero */
@import url("tipografia.css");

⚠️ Importante: @import obliga al navegador a descargar el primer CSS, leerlo y solo entonces descubrir y pedir el segundo (descargas en cadena). Para webs en producción es preferible usar varias etiquetas <link> o unir los ficheros en uno solo.


4. Comentarios

Los comentarios en CSS se escriben entre /* y */ y pueden ocupar varias líneas. No existe el comentario de una línea con //.

/* ==========================================
   CABECERA
   ========================================== */
.cabecera { background-color: #1d3557; }   /* azul oscuro corporativo */

/* .antiguo { color: red; }   ← código desactivado temporalmente */

💡 Consejo: En VS Code, selecciona líneas y pulsa Ctrl + / para comentarlas o descomentarlas. Usa comentarios para separar secciones del fichero y para explicar el porqué de decisiones no evidentes, no lo que ya dice el código.


5. Selectores básicos

El selector es la “dirección” de la regla: le dice al navegador a quién aplicar los estilos. Usaremos este HTML de referencia:

<header id="cabecera"><h1 class="titulo">Mi blog</h1></header>
<main>
    <p class="intro destacado">Bienvenido a mi blog.</p>
    <p>Otro párrafo normal.</p>
    <a href="https://ejemplo.com" class="boton">Visitar</a>
</main>

Selector de tipo (etiqueta)

Selecciona todos los elementos de ese tipo:

p { color: #333; }   /* todos los párrafos */

Selector de clase

Selecciona los elementos con un valor en su atributo class. Empieza por punto:

.destacado { background-color: #ffe066; }

Un elemento puede tener varias clases separadas por espacios (class="intro destacado"). Y puedes seleccionar elementos que tengan varias clases a la vez escribiéndolas juntas, sin espacio:

.intro.destacado { font-size: 1.25rem; }  /* elementos con AMBAS clases */
p.intro          { font-style: italic; }  /* solo los <p> con la clase intro */

Selector de id

Selecciona el elemento con ese id. Empieza por almohadilla:

#cabecera { padding: 2rem; }

⚠️ Importante: Un id debe ser único en la página. Además, como verás en la especificidad, un selector de id pesa muchísimo y es difícil de sobrescribir. En CSS profesional se usan clases para dar estilo y se reservan los id para anclas (#seccion), formularios (<label for>) y JavaScript.

Selector universal

* { box-sizing: border-box; }   /* el asterisco selecciona TODOS los elementos; box-sizing en el módulo 09 */

Resumen de selectores básicos

Selector Sintaxis Selecciona
Tipo p Todos los <p>
Clase .boton Elementos con class="boton"
Id #cabecera El elemento con id="cabecera"
Universal * Todos los elementos
Agrupación h1, h2 Todos los <h1> y todos los <h2>
Compuesto a.boton Los <a> que tienen la clase boton

💡 Consejo: Para los nombres de clase usa minúsculas y guiones (tarjeta-producto, boton-principal). Elige nombres que describan qué es (.aviso-error) y no cómo se ve (.texto-rojo): si mañana el aviso pasa a ser naranja, el nombre seguirá siendo correcto.


6. Selectores de atributo

Seleccionan elementos según sus atributos y valores. Se escriben entre corchetes.

Selector Significado Ejemplo
[attr] Tiene el atributo [disabled]
[attr="v"] Valor exactamente igual [type="email"]
[attr^="v"] Empieza por [href^="https"]
[attr$="v"] Termina en [href$=".pdf"]
[attr*="v"] Contiene [href*="youtube"]
[attr~="v"] Contiene la palabra (separada por espacios) [class~="activo"]
[attr\|="v"] Es v o empieza por v- [lang\|="es"] (es, es-ES, es-MX)
[attr="v" i] Igual, sin distinguir mayúsculas [type="EMAIL" i]
/* Campos de texto y email de un formulario */
input[type="text"],
input[type="email"] {
    border: 1px solid #999;
    padding: 0.5rem;
}

/* Enlaces externos seguros: añadimos una flecha después */
a[href^="https://"]::after { content: " ↗"; }

/* Enlaces a PDF en rojo */
a[href$=".pdf"] { color: #c1121f; }

/* Imágenes sin texto alternativo: ¡las marcamos para corregirlas! */
img:not([alt]) { outline: 4px solid red; }

💡 Consejo: El último ejemplo es un truco de depuración muy útil: añádelo mientras desarrollas y verás al instante qué imágenes no tienen alt.


7. Combinadores

Los combinadores seleccionan elementos según su relación con otros en el árbol del documento (el DOM). Usaremos este HTML:

<article class="post">
    <h2>Título</h2>
    <p>Primer párrafo.</p>
    <p>Segundo párrafo.</p>
    <div class="nota">
        <p>Párrafo dentro de la nota.</p>
    </div>
</article>
flowchart TD
    A["article.post"] --> H["h2"]
    A --> P1["p (1º)"]
    A --> P2["p (2º)"]
    A --> D["div.nota"]
    D --> P3["p (dentro de la nota)"]
Combinador Sintaxis Selecciona En el ejemplo
Descendiente A B (espacio) B dentro de A, a cualquier profundidad .post p → los 3 párrafos
Hijo directo A > B B hijo inmediato de A .post > p → solo los 2 primeros
Hermano adyacente A + B El B inmediatamente después de A h2 + p → solo el 1º párrafo
Hermanos generales A ~ B Todos los B posteriores a A con el mismo padre h2 ~ p → 1º y 2º párrafo
.post p   { line-height: 1.7; }    /* cualquier párrafo dentro del artículo */
.post > p { margin-bottom: 1rem; } /* solo los hijos directos del artículo */
h2 + p    { font-size: 1.2rem; }   /* el párrafo justo tras el título */
h2 ~ p    { color: #444; }         /* todos los párrafos hermanos posteriores al h2 */

📘 Concepto: Los selectores se leen de derecha a izquierda. .post > p significa “un p… cuyo padre es .post”. Los hermanos solo miran hacia delante: con + y ~ no puedes seleccionar un elemento anterior (para eso existe :has(), que verás en el módulo 14).

⚠️ Importante: No encadenes selectores larguísimos como body main article.post div.nota p span. Son frágiles (si cambias el HTML dejan de funcionar), difíciles de leer y aumentan la especificidad. Prefiere una clase directa: .nota-texto.


8. Pseudo-clases

Una pseudo-clase selecciona elementos en un estado concreto o en una posición concreta, cosas que no se pueden expresar solo con etiquetas y clases. Se escriben con un signo de dos puntos: :hover.

8.1. Estados de interacción

/* Orden recomendado para enlaces: LVHA (Love/HAte) */
a:link    { color: #1d4ed8; }   /* enlace no visitado */
a:visited { color: #7e22ce; }   /* enlace ya visitado */
a:hover   { color: #dc2626; }   /* ratón encima */
a:active  { color: #991b1b; }   /* mientras se pulsa */

/* Botón con efecto al pasar el ratón */
.boton:hover { background-color: #1d3557; color: white; }
Pseudo-clase Cuándo se activa
:hover El puntero está encima del elemento
:active El elemento se está pulsando
:focus El elemento tiene el foco (clic, tabulador o JavaScript)
:focus-visible Tiene el foco y el navegador considera que hay que mostrarlo (normalmente al usar el teclado)
:focus-within El elemento o alguno de sus descendientes tiene el foco
:link / :visited Enlace no visitado / visitado

:focus frente a :focus-visible

Muchos diseñadores quitaban el contorno de foco porque “queda feo al hacer clic”. Eso deja a los usuarios de teclado sin saber dónde están. :focus-visible resuelve el problema: el navegador muestra el indicador cuando hace falta (navegación con teclado) y no al hacer clic con el ratón.

/* ✗ MAL: elimina el foco para todo el mundo */
button:focus { outline: none; }

/* ✓ BIEN: foco personalizado, visible al navegar con teclado */
button:focus-visible { outline: 3px solid #f59e0b; outline-offset: 2px; }

/* Resaltar todo el formulario cuando uno de sus campos tiene el foco */
.buscador:focus-within { box-shadow: 0 0 0 3px #93c5fd; }

⚠️ Importante: Nunca elimines el indicador de foco sin poner otro igual de visible. Es un requisito básico de accesibilidad (WCAG).

8.2. Pseudo-clases estructurales

Seleccionan elementos según su posición entre sus hermanos.

Pseudo-clase Selecciona
:first-child El primer hijo de su padre
:last-child El último hijo
:only-child El único hijo
:nth-child(n) El hijo en la posición n (empieza en 1)
:nth-last-child(n) Igual, contando desde el final
:first-of-type / :last-of-type El primero / último de su tipo de etiqueta
:nth-of-type(n) El n-ésimo de su tipo
:empty Elementos sin hijos ni texto

La fórmula de :nth-child() admite an+b:

Expresión Selecciona
:nth-child(3) El 3º
:nth-child(odd) o (2n+1) 1º, 3º, 5º… (impares)
:nth-child(even) o (2n) 2º, 4º, 6º… (pares)
:nth-child(3n) 3º, 6º, 9º…
:nth-child(n+4) Del 4º en adelante
:nth-child(-n+3) Los 3 primeros
/* Tabla "cebra": filas pares con fondo gris */
tbody tr:nth-child(even) { background-color: #f1f5f9; }

/* Lista de menú: separador entre elementos, salvo en el último */
.menu li            { border-bottom: 1px solid #ddd; }
.menu li:last-child { border-bottom: none; }

/* Los tres primeros artículos, destacados */
.noticias article:nth-child(-n+3) { font-weight: bold; }

⚠️ Importante: p:first-child no significa “el primer párrafo”, sino “un párrafo que además es el primer hijo de su padre”. Si el primer hijo es un <h2>, p:first-child no selecciona nada. Para “el primer párrafo” usa p:first-of-type.

8.3. Pseudo-clases de formulario

input:required    { border-left: 4px solid #f59e0b; }  /* campos obligatorios */
input:disabled    { opacity: 0.5; cursor: not-allowed; }
input:checked + label { font-weight: bold; }            /* checkbox/radio marcado */

/* :user-invalid solo se activa tras interactuar con el campo,
   a diferencia de :invalid, que marca en rojo desde el principio */
input:user-invalid { border-color: #dc2626; }
input:valid        { border-color: #16a34a; }

/* El placeholder está visible (el campo está vacío) */
input:placeholder-shown { background-color: #fafafa; }

8.4. Otras pseudo-clases útiles

/* :not() — todos los botones salvo los de la clase .secundario */
.boton:not(.secundario) { background-color: #2563eb; }

/* :root — el elemento raíz (html). Muy usado para variables CSS (módulo 08) */
:root { --color-principal: #2563eb; }

/* :target — el elemento cuyo id coincide con el #fragmento de la URL */
section:target { background-color: #fef3c7; }

💡 Consejo: Existen pseudo-clases lógicas muy potentes como :is(), :where() y :has() (el “selector padre”). Las veremos a fondo en el módulo 14 de CSS moderno.

✏️ Ejercicio: Crea una lista <ul> de 10 elementos. Haz que los impares tengan fondo gris claro, que el primero esté en negrita, que el último tenga el texto en cursiva y que al pasar el ratón sobre cualquiera cambie a fondo amarillo.


9. Pseudo-elementos

Un pseudo-elemento selecciona una parte de un elemento, o crea un elemento “virtual” que no existe en el HTML. Se escriben con dos signos de dos puntos: ::before.

Pseudo-elemento Selecciona
::before Contenido generado al principio del elemento
::after Contenido generado al final del elemento
::first-letter La primera letra de un bloque de texto
::first-line La primera línea (según el ancho actual)
::placeholder El texto de ejemplo de un input o textarea
::selection El texto que el usuario ha seleccionado
::marker La viñeta o número de un elemento de lista

::before y ::after

Insertan contenido decorativo mediante la propiedad content, obligatoria (aunque sea vacía: content: "").

/* Comillas decorativas en una cita */
.cita::before { content: "“"; font-size: 3rem; color: #e63946; }
.cita::after  { content: "”"; font-size: 3rem; color: #e63946; }

/* Campo obligatorio: asterisco rojo tras la etiqueta */
label.obligatorio::after { content: " *"; color: #dc2626; }

/* Línea decorativa bajo un título (content vacío + tamaño) */
.titulo-seccion::after {
    content: "";
    display: block;
    width: 60px; height: 4px; margin-top: 0.5rem;
    background-color: #2563eb;
}

⚠️ Importante: El contenido de ::before/::after es decorativo: no se puede seleccionar con el ratón y los lectores de pantalla pueden leerlo o no según el caso. Nunca pongas ahí información importante. Además, no funcionan en elementos vacíos de reemplazo como <img> o <input>.

Tipografía y formularios

/* Letra capital al inicio de un artículo */
.articulo p:first-of-type::first-letter {
    float: left; font-size: 3.5rem; line-height: 1; margin-right: 0.5rem; color: #1d3557;
}

/* Primera línea en versalitas */
.articulo p:first-of-type::first-line { font-variant: small-caps; }

/* Texto de ejemplo en gris claro */
input::placeholder { color: #94a3b8; font-style: italic; }

/* Selección de texto con los colores de la marca */
::selection { background-color: #e63946; color: white; }

/* Viñetas de lista en color */
li::marker { color: #2563eb; }

📘 Concepto: La diferencia : / :: es intencionada: una para estados (pseudo-clases), dos para partes (pseudo-elementos). Por compatibilidad histórica los navegadores aceptan :before, pero escribe siempre ::before.


10. La cascada

Muchas veces varias reglas intentan dar valor a la misma propiedad del mismo elemento. ¿Cuál gana? La cascada es el algoritmo que lo decide, y es la “C” de CSS.

p { color: blue; }
.intro { color: green; }
p { color: red; }
<p class="intro">¿De qué color soy?</p>  <!-- verde: la clase pesa más que la etiqueta -->

Para decidir, el navegador compara las declaraciones en conflicto siguiendo estos criterios en orden. Solo si hay empate en un criterio se pasa al siguiente:

flowchart TD
    A["Declaraciones en conflicto"] --> B{"1. Origen e importancia<br/>¿quién la escribió y lleva !important?"}
    B -->|empate| C{"2. Capas @layer<br/>(módulo 14)"}
    C -->|empate| D{"3. Especificidad<br/>¿qué selector es más concreto?"}
    D -->|empate| E{"4. Orden de aparición<br/>gana la última"}
    B -->|hay ganador| W["Valor aplicado"]
    C -->|hay ganador| W
    D -->|hay ganador| W
    E --> W

10.1. Origen

Las declaraciones pueden venir de tres sitios:

Origen Quién la escribe Ejemplo
Agente de usuario El navegador (estilos por defecto) Los <h1> son grandes y en negrita
Usuario La persona que navega (ajustes de accesibilidad, extensiones) Tamaño de letra mínimo
Autor Tú, el desarrollador Tu estilos.css

En condiciones normales, autor > usuario > navegador: tus estilos sobrescriben los del navegador.

10.2. Importancia

Una declaración con !important salta por encima de todas las normales. Y aquí el orden de los orígenes se invierte para proteger al usuario: un !important del usuario gana a un !important del autor (por ejemplo, alguien con baja visión que fuerza un tamaño de letra).

Orden completo de prioridad, de mayor a menor: transiciones en curso → !important del navegador → !important del usuario → !important del autor → animaciones (@keyframes) → normales del autor → normales del usuario → normales del navegador.

10.3. Especificidad y orden de aparición

Si dos declaraciones tienen el mismo origen e importancia, gana la del selector más específico (lo vemos en detalle en la sección siguiente). Y si también empatan en especificidad, gana la que aparece la última. Esto incluye el orden de las hojas: si enlazas base.css y después tema.css, las reglas de tema.css ganan a igual especificidad.

.boton { background-color: blue; }
.boton { background-color: green; }   /* ← gana: misma especificidad, va después */

💡 Consejo: Por eso las hojas de estilo se organizan de lo general a lo concreto: primero el reset, luego estilos base de etiquetas, luego componentes y, al final, utilidades y excepciones.


11. Especificidad

📘 Concepto: La especificidad es el “peso” de un selector. Se expresa con tres números (A, B, C), que se comparan como las posiciones de un marcador: primero A; si empatan, B; si empatan, C.

Columna Qué cuenta Ejemplos
A Selectores de id #menu
B Clases, atributos y pseudo-clases .activo, [type="text"], :hover, :nth-child()
C Tipos (etiquetas) y pseudo-elementos p, li, ::before
— No cuentan *, combinadores (espacio, >, +, ~), :where()

Ejemplos de cálculo

Selector A (ids) B (clases…) C (tipos) Especificidad
* 0 0 0 (0,0,0)
p 0 0 1 (0,0,1)
ul li 0 0 2 (0,0,2)
p::first-line 0 0 2 (0,0,2)
.boton 0 1 0 (0,1,0)
a:hover 0 1 1 (0,1,1)
.menu li.activo 0 2 1 (0,2,1)
input[type="email"]:focus 0 2 1 (0,2,1)
nav > ul > li > a 0 0 4 (0,0,4)
#menu 1 0 0 (1,0,0)
#menu .activo a 1 1 1 (1,1,1)

⚠️ Importante: Las columnas no se suman como un número decimal. (0,1,0) gana a (0,0,15): una sola clase pesa más que cualquier cantidad de etiquetas. Y (1,0,0) gana a (0,30,0): un id pesa más que cualquier cantidad de clases.

Ejemplo comentado

<nav id="menu">
    <a class="enlace activo" href="#">Inicio</a>
</nav>
#menu a        { color: gray; }    /* (1,0,1) ← GANA: tiene un id */
.enlace.activo { color: orange; }  /* (0,2,0) */
nav a          { color: black; }   /* (0,0,2) */
a              { color: blue; }    /* (0,0,1) */

El enlace se ve gris, aunque la regla .enlace.activo parezca “más concreta” a simple vista. Este es el motivo por el que se desaconseja usar ids para dar estilo.

El atributo style y los casos especiales

  • Las declaraciones del atributo style (CSS en línea) ganan a cualquier selector de las hojas de estilo, sea cual sea su especificidad. Solo !important puede con ellas.
  • :not(), :is() y :has() no cuentan por sí mismas: aportan la especificidad de su argumento más específico. :not(#x) vale (1,0,0).
  • :where() siempre aporta (0,0,0), lo que la hace ideal para estilos base fáciles de sobrescribir (módulo 14).

💡 Consejo: En las DevTools de Chrome y Edge, si pasas el ratón por encima de un selector en el panel Styles, te muestra su especificidad. Úsalo para comprobar tus cálculos.

Buenas prácticas

  • Mantén la especificidad baja y uniforme: idealmente, casi todo con una sola clase (0,1,0).
  • Evita ids en CSS y selectores anidados profundos.
  • Si una regla no se aplica, no subas la especificidad a ciegas (añadiendo #id o repitiendo clases): averigua en DevTools qué regla está ganando y por qué.

✏️ Ejercicio: Calcula la especificidad de ul#lista li.item:first-child a y de .pagina .contenido .lista a:hover. ¿Cuál gana? (Solución: (1,2,3) frente a (0,4,1); gana la primera porque tiene un id).


12. !important

Añadir !important al final de un valor hace que esa declaración pase por encima de todas las declaraciones normales, independientemente de la especificidad.

.aviso            { color: red !important; }
#principal .aviso { color: blue; }   /* pierde, aunque su especificidad es mayor */

¿Por qué se considera una mala práctica? Porque rompe el funcionamiento natural de la cascada. La única forma de vencer un !important es con otro !important más específico o posterior, y así empieza una “guerra” imposible de mantener.

Cuándo SÍ es razonable

Situación Ejemplo
Clases de utilidad que deben ganar siempre .oculto { display: none !important; } (Bootstrap lo hace en sus utilidades)
Sobrescribir CSS de terceros que no controlas Un widget externo con estilos en línea
Preferencias de accesibilidad Desactivar animaciones con prefers-reduced-motion (módulo 13)

⚠️ Importante: Si te encuentras escribiendo !important para “arreglar” algo en tu propio CSS, para y busca la causa real en DevTools. Casi siempre es un problema de especificidad u orden que se arregla mejor reorganizando.


13. Herencia

📘 Concepto: Algunas propiedades se heredan: si se las das a un elemento, sus descendientes las reciben automáticamente, salvo que se les asigne otro valor. Como un apellido que pasa de padres a hijos.

<article class="post">
    <h2>Título</h2>
    <p>Un párrafo con un <strong>texto destacado</strong>.</p>
</article>
.post {
    color: #334155;                 /* heredado por h2, p y strong */
    font-family: Georgia, serif;    /* heredado */
    border: 2px solid #334155;      /* NO heredado: solo el article tiene borde */
}

¡Imagina el desastre si border se heredara! Cada párrafo, título y strong tendría su propio borde. Por eso, en general:

Se heredan (texto) No se heredan (caja y disposición)
color margin, padding, border
font-family, font-size, font-weight, font-style width, height
line-height, letter-spacing, text-align background
text-transform, white-space display, position
visibility, cursor box-shadow, overflow
list-style opacity, transform

💡 Consejo: Aprovecha la herencia: define font-family, color y line-height una vez en body y todo el documento los heredará. Excepción: los controles de formulario (button, input, select, textarea) no heredan la fuente por defecto; por eso los resets añaden font: inherit a esos elementos.

Palabras clave globales

Cualquier propiedad acepta estas palabras clave para controlar la herencia:

Valor Efecto
inherit Toma el valor del elemento padre (fuerza la herencia, aunque la propiedad no se herede)
initial Usa el valor inicial definido en la especificación de CSS (no el del navegador)
unset Si la propiedad se hereda, actúa como inherit; si no, como initial
revert Vuelve al valor que tendría según la hoja del navegador (deshace los estilos del autor)
revert-layer Vuelve al valor de la capa de cascada anterior (módulo 14)
/* Los enlaces del pie heredan el color del texto en lugar del azul por defecto */
.pie a { color: inherit; }

/* Botones con la misma tipografía que el resto de la página */
button, input, select, textarea { font: inherit; }

/* initial vs revert en un h1 */
h1.a { font-weight: initial; }  /* normal: el valor inicial de font-weight es normal */
h1.b { font-weight: revert; }   /* bold: el valor que le da el navegador a un h1 */

La propiedad all permite aplicar una de estas palabras a todas las propiedades a la vez:

/* Botón "desnudo": quita todos los estilos que trae el navegador */
.boton-limpio { all: unset; cursor: pointer; }

⚠️ Importante: all: unset también elimina el indicador de foco y el display del botón. Si lo usas, vuelve a definir un :focus-visible adecuado.


14. La hoja de estilos del navegador, reset y normalize

Aunque no escribas ni una línea de CSS, los títulos se ven grandes, los enlaces azules y subrayados, y el body tiene un margen de 8px. Son los estilos del agente de usuario (user agent stylesheet), la hoja por defecto del navegador.

El problema: cada navegador tiene pequeñas diferencias, y algunos valores por defecto no son prácticos para maquetar. Hay dos enfoques para solucionarlo:

Enfoque Idea Ejemplos
Reset Borrar casi todos los estilos por defecto y empezar desde cero El clásico Reset CSS de Eric Meyer, resets modernos personalizados
Normalize Conservar los estilos útiles y solo corregir las diferencias entre navegadores normalize.css, modern-normalize

Bootstrap incluye su propia hoja llamada Reboot, basada en normalize (la verás en el módulo 15).

Un reset moderno y sensato

Este es un buen punto de partida para tus proyectos. Ponlo al principio de tu hoja de estilos:

/* ==========================================
   RESET MODERNO
   ========================================== */

/* 1. El ancho incluye padding y borde (módulo 09) */
*, *::before, *::after {
    box-sizing: border-box;
}

/* 2. Quitar márgenes por defecto */
* {
    margin: 0;
}

/* 3. Altura mínima de pantalla y altura de línea cómoda */
body {
    min-height: 100vh;
    line-height: 1.5;
}

/* 4. Multimedia: bloque y nunca más ancha que su contenedor */
img, picture, video, canvas, svg {
    display: block;
    max-width: 100%;
}

/* 5. Los controles de formulario heredan la tipografía */
input, button, textarea, select {
    font: inherit;
}

/* 6. Evitar que palabras muy largas desborden */
p, h1, h2, h3, h4, h5, h6 {
    overflow-wrap: break-word;
}

⚠️ Importante: Si quitas las viñetas de las listas con list-style: none, Safari deja de anunciarlas como lista en VoiceOver. En menús de navegación puedes añadir role="list" al <ul> para mantener la semántica.


15. Depurar CSS con las DevTools

Las herramientas de desarrollo del navegador (F12 o Ctrl + Mayús + I) son tu mejor aliado con CSS. Repasa el módulo 01 si no las recuerdas.

Panel Elements / Styles

Haz clic derecho sobre un elemento → Inspeccionar. En el panel Elements verás el HTML y, en la pestaña Styles, todas las reglas que afectan al elemento ordenadas de más a menos prioritaria. Las declaraciones tachadas han perdido en la cascada; las tachadas con un icono de aviso ⚠ son inválidas. Las reglas del navegador aparecen como user agent stylesheet y, al final, las heredadas (Inherited from…).

Trucos imprescindibles

Acción Cómo
Editar un valor en vivo Clic sobre el valor y escribe; usa ↑/↓ para números
Desactivar una declaración Desmarca la casilla a su izquierda
Añadir una declaración Clic en el espacio en blanco dentro de la regla
Forzar un estado (:hover, :focus…) Botón :hov en la barra de Styles
Ver el valor final y qué regla gana Pestaña Computed: despliega la propiedad
Ver el modelo de cajas Diagrama al final de Styles o en Computed
Ver la especificidad Pasa el ratón sobre el selector (Chrome/Edge)
Ir al fichero y la línea Clic en el enlace estilos.css:42 junto a la regla

💡 Consejo: Los cambios hechos en DevTools se pierden al recargar. Úsalas para experimentar y, cuando encuentres el valor correcto, cópialo a tu fichero .css.

Checklist cuando “mi CSS no funciona”

  1. ¿El fichero está enlazado? Mira la pestaña Network o si la regla aparece en Styles. Revisa la ruta del href.
  2. ¿El selector coincide con el elemento? Comprueba mayúsculas, puntos y almohadillas (.menu frente a menu).
  3. ¿La declaración aparece tachada? Otra regla gana: mira cuál y por qué (especificidad u orden).
  4. ¿Aparece con aviso? Hay una errata en la propiedad o el valor.
  5. ¿Se aplica pero no hace nada? Algunas propiedades necesitan otras (por ejemplo, z-index necesita position, width no afecta a elementos inline).
  6. ¿Caché? Recarga forzada con Ctrl + F5.

16. Metodología BEM

A medida que un proyecto crece, los nombres de clase se vuelven un problema: ¿.titulo es el de la tarjeta, el del modal o el de la cabecera? BEM (Block, Element, Modifier) es una convención de nombres que resuelve esto con una estructura predecible.

Parte Qué es Sintaxis Ejemplo
Bloque Componente independiente y reutilizable bloque .tarjeta
Elemento Parte de un bloque que no tiene sentido fuera de él bloque__elemento .tarjeta__titulo
Modificador Variante o estado de un bloque o elemento bloque--modificador .tarjeta--destacada

Ejemplo completo

<article class="tarjeta tarjeta--destacada">
    <img class="tarjeta__imagen" src="portatil.jpg" alt="Portátil gris">
    <div class="tarjeta__cuerpo">
        <h3 class="tarjeta__titulo">Portátil Pro 14</h3>
        <p class="tarjeta__precio">999 €</p>
        <a class="tarjeta__boton tarjeta__boton--grande" href="#">Comprar</a>
    </div>
</article>
/* Bloque y su modificador */
.tarjeta            { border: 1px solid #e2e8f0; border-radius: 8px; overflow: hidden; }
.tarjeta--destacada { border-color: #f59e0b; box-shadow: 0 4px 12px rgb(0 0 0 / 0.15); }

/* Elementos */
.tarjeta__imagen { width: 100%; }
.tarjeta__cuerpo { padding: 1rem; }
.tarjeta__titulo { font-size: 1.25rem; }
.tarjeta__precio { color: #16a34a; font-weight: bold; }
.tarjeta__boton  { display: inline-block; padding: 0.5rem 1rem; background-color: #2563eb; color: white; }

/* Modificador de un elemento */
.tarjeta__boton--grande { padding: 0.75rem 1.5rem; font-size: 1.1rem; }

Ventajas y reglas de oro

  • Especificidad plana: todo es una sola clase (0,1,0), así que el orden manda y no hay sorpresas.
  • Autodocumentado: leyendo .tarjeta__precio sabes a qué componente pertenece.
  • Sin dependencia del HTML: no usas .tarjeta div p, así que reorganizar el marcado no rompe el estilo.
  • No anides elementos en el nombre: .tarjeta__cuerpo__titulo ✗ → .tarjeta__titulo ✓. El nombre refleja el bloque, no la profundidad.
  • El modificador nunca va solo: class="tarjeta--destacada" ✗ → class="tarjeta tarjeta--destacada" ✓.

💡 Consejo: BEM no es la única metodología (existen OOCSS, SMACSS, ITCSS o el enfoque de utilidades de Tailwind y Bootstrap), pero es la más extendida para CSS escrito a mano y combina muy bien con Bootstrap: usas las utilidades de Bootstrap para lo genérico y BEM para tus componentes propios.


17. Ejercicios

Ejercicio 1: Tres formas de incluir CSS

Crea una página index.html con un <h1>, dos <p> y un enlace. Aplica:

  • Un color al <h1> mediante el atributo style.
  • Un color de fondo al body en una etiqueta <style> interna.
  • Una tipografía y un color de párrafo en un fichero externo css/estilos.css.

Después, mueve todo a la hoja externa y elimina el CSS en línea y el interno.

Ejercicio 2: Galería de selectores

Con este HTML, escribe una regla por cada punto sin añadir clases nuevas:

<main>
    <h2>Productos</h2>
    <p>Introducción a la sección.</p>
    <ul class="productos">
        <li>Teclado</li>
        <li class="oferta">Ratón</li>
        <li>Monitor</li>
        <li class="oferta agotado">Webcam</li>
    </ul>
    <p>Descarga el <a href="catalogo.pdf">catálogo en PDF</a> o visita <a href="https://tienda.com">la tienda</a>.</p>
</main>
  1. El párrafo que va justo después del h2, en cursiva.
  2. Los li con clase oferta, en verde.
  3. El li que es a la vez oferta y agotado, tachado (text-decoration: line-through).
  4. Los li pares, con fondo gris claro.
  5. Los enlaces a ficheros .pdf, en rojo, y los que empiezan por https, con una flecha ↗ añadida con ::after.

Ejercicio 3: Formulario con estados

Crea un formulario con nombre (obligatorio), email (obligatorio) y un botón. Aplica estilos con:

  • ::placeholder en gris e itálica.
  • Un asterisco rojo tras las etiquetas de campos obligatorios usando ::after.
  • :focus-visible con un contorno naranja de 3px.
  • :user-invalid con borde rojo y :valid con borde verde.
  • :hover y :active en el botón.

Ejercicio 4: Calcula la especificidad

Indica la especificidad de cada selector y ordénalos de mayor a menor:

.nav a:hover
#cabecera .logo
header nav ul li a
a
.nav .enlace.activo
li:nth-child(2)::before

Comprueba tus respuestas creando el HTML necesario y pasando el ratón por encima de cada selector en DevTools.

Ejercicio 5: El misterio del color

Sin ejecutarlo, deduce de qué color se ve el texto del botón. Después compruébalo en el navegador y explica qué criterio de la cascada decide en cada paso.

<div id="zona">
    <button class="btn btn-primario" style="color: purple;">Enviar</button>
</div>
#zona .btn       { color: orange; }
.btn-primario    { color: white !important; }
.btn.btn-primario { color: yellow; }
button           { color: black; }

Después elimina la declaración con !important. ¿Qué color se ve ahora? ¿Y si además quitas el atributo style?

Ejercicio 6: Componente con BEM

Diseña con nomenclatura BEM un componente alerta que tenga un icono, un título, un texto y un botón de cerrar, con tres modificadores: alerta--exito, alerta--aviso y alerta--error. Usa una sola clase por selector y añade un ::before decorativo en el bloque.


Resumen

Concepto Sintaxis / idea clave
Regla CSS selector { propiedad: valor; }
CSS externo <link rel="stylesheet" href="css/estilos.css">
Comentario /* comentario */
Selectores básicos p, .clase, #id, *, h1, h2
Atributo [type="email"], [href^="https"], [href$=".pdf"]
Combinadores A B descendiente, A > B hijo, A + B adyacente, A ~ B hermanos
Pseudo-clases :hover, :focus-visible, :nth-child(2n), :not(.x)
Pseudo-elementos ::before, ::after (requieren content), ::placeholder, ::selection
Cascada Origen e importancia → capas → especificidad → orden
Especificidad (ids, clases/atributos/pseudo-clases, tipos/pseudo-elementos)
!important Gana a todo lo normal; úsalo solo en casos justificados
Herencia Se heredan las propiedades de texto, no las de caja
Palabras clave inherit, initial, unset, revert
Reset Neutraliza los estilos del navegador; box-sizing: border-box
BEM .bloque, .bloque__elemento, .bloque--modificador