Introducción: qué hace CSS y cómo se vincula una hoja externa
CSS (Cascading Style Sheets, hojas de estilo en cascada) es el lenguaje que describe cómo se ve lo que HTML estructura. Si HTML dice «esto es un título», CSS dice de qué color, de qué tamaño y con cuánto espacio alrededor.
Sin CSS no hay páginas «simples»: hay páginas con el estilo del navegador. Fondo blanco, letra con serifa, enlaces azules subrayados, todo pegado al borde. Y todos los sitios del mundo se verían iguales, porque el estilo por defecto es el mismo en todas partes.
Hay tres formas de vincular CSS a una página, y conviene conocer las tres para poder elegir:
| Forma | Cómo se escribe | Cuándo conviene |
|---|---|---|
| Externa archivo aparte |
<link rel="stylesheet" href="/css/estilos.css"> dentro del <head>. |
Siempre, en un sitio con más de una página. |
| Interna | Un bloque <style> dentro del <head>. |
Una página única, o una excepción que no se repite. |
| En línea | El atributo style="color: red" en la etiqueta misma. |
Nunca, salvo pruebas rápidas. |
<head>
<meta charset="utf-8">
<title>Alta de socios</title>
<link rel="stylesheet" href="/css/estilos.css">
</head>
La hoja externa gana por una razón de mantenimiento: un solo archivo para todas las páginas del sitio. Cambiar el color de los títulos es cambiar una línea, y el cambio aparece en las cien páginas a la vez. El estilo en línea, en cambio, hay que buscarlo y corregirlo página por página, y encima pisa todo lo demás: es la causa número uno de los «cambié el CSS y no se aplica». Además, un archivo externo se descarga una sola vez y el navegador lo reutiliza en todas las páginas siguientes, así que también es más rápido.
Reglas y selectores: propiedades, valores, selectores de etiqueta y clase
Una hoja de estilos es una lista de reglas. Cada regla tiene dos partes: a quién afecta y qué le hace.
h1 {
color: #282c32; /* propiedad: valor ; */
font-size: 2rem;
}
↑ ↑
selector declaraciones
El selector dice a qué elementos alcanza la regla; el bloque entre llaves son las declaraciones, cada una con una propiedad y un valor, separadas por punto y coma. Olvidar un punto y coma no da un error visible: hace que la declaración siguiente se ignore en silencio.
| Selector | Ejemplo | Alcanza a |
|---|---|---|
| De etiqueta | p { } | Todos los párrafos de la página. |
| De clase | .aviso { } | Los elementos con class="aviso". Se puede repetir todas las veces que haga falta. |
| De id | #encabezado { } | El elemento con id="encabezado". Es único en la página. |
| Descendiente | .aviso strong { } | Solo los <strong> que están dentro de un elemento con esa clase. |
| Agrupado | h1, h2, h3 { } | Los tres, con las mismas declaraciones. La coma es un «y». |
| De estado | a:hover { } | Los enlaces mientras el puntero está encima. |
Las clases son el caballito de batalla. Tienen dos ventajas sobre los id: se pueden reutilizar en tantos elementos como se quiera —y eso es lo normal— y tienen menos peso, así que después se pueden modificar sin pelearse con la regla anterior. Un id tiene que ser único en toda la página y conviene reservarlo para lo que realmente es único: el ancla de un índice, un campo que JavaScript necesita ubicar.
La regla práctica de un sitio mantenible es simple: clases para los estilos, id para lo único, y selectores lo más cortos posible. Un selector como body div article ul li a funciona, pero cualquier cambio en la estructura de la página lo rompe, y mientras tanto tiene tanto peso que corregirlo es una lucha.
Cascada y herencia: cómo se resuelven y se transmiten los estilos
Estas dos palabras están en el nombre del lenguaje, y explican el 90 % de los «cambié algo y no se aplicó».
La cascada
Casi siempre hay varias reglas que podrían aplicarse al mismo elemento. La cascada es el conjunto de criterios que decide cuál gana. Se aplican en este orden:
- El origen. Los estilos del autor de la página ganan a los del navegador. La excepción es
!important, que invierte la prioridad y por eso conviene evitar: es una declaración de guerra que después hay que desactivar con otra declaración de guerra. - La especificidad. Cuanto más preciso el selector, más peso tiene.
- El orden. Si dos reglas tienen el mismo peso, gana la última que aparece en el archivo.
| Selector | Peso | Le gana a… |
|---|---|---|
p | 1 (etiqueta) | Nada. |
.aviso | 10 (clase) | Los selectores de etiqueta. |
#encabezado | 100 (id) | Etiquetas y clases. |
style="" en la etiqueta | 1000 (en línea) | A todo, salvo !important. |
Por eso un style="" puesto «para probar» y nunca quitado es tan destructivo: le gana a la hoja entera, y quien después edita el CSS no entiende por qué su cambio no aparece.
La herencia
Algunas propiedades se transmiten de un elemento a sus hijos, y otras no. La división es bastante lógica:
- Se heredan las que tienen que ver con el texto:
color,font-family,font-size,line-height,text-align. Si se pone el color en el<body>, todos los párrafos lo toman. - No se heredan las que tienen que ver con la caja:
margin,padding,border,background,width. Y es una suerte: si el margen se heredara, cada elemento anidado tendría el margen del padre y el contenido terminaría en el medio de la página.
Ahí está el error clásico: alguien pone background-color en el <body> esperando que todo el contenido se pinte de ese color, y no pasa nada, porque el fondo no se hereda. Lo que sí ocurre —y es lo que confunde— es que el fondo del <body> se dibuja en toda la ventana cuando el contenido es corto, así que a veces parece que se aplicó.
Colores y fondos
Un color se puede escribir de cuatro maneras, y las cuatro son el mismo color:
| Forma | Ejemplo | Cuándo sirve |
|---|---|---|
| Nombre | red, tomato, steelblue | Hay 147 nombres. Cómodo para pruebas, insuficiente para un diseño. |
| Hexadecimal | #1c814e | La forma más usada. Tres pares: rojo, verde y azul, de 00 a ff. |
rgb | rgb(28, 129, 78) | Igual que el hexadecimal, pero con los valores en decimal. |
rgba | rgba(28, 129, 78, 0.5) | El cuarto valor es la transparencia: 0 invisible, 1 opaco. |
hsl | hsl(150, 64%, 31%) | Matiz, saturación y luminosidad. Es la más fácil de ajustar: para aclarar un color se sube la luz. |
Las propiedades son color para el texto y background-color para el fondo —vale memorizarlo, porque color a secas y background se confunden todo el tiempo—. Para un fondo con imagen está background-image, y con linear-gradient() se arma un degradado sin necesidad de un archivo.
El punto que no es de gusto sino de accesibilidad es el contraste entre el texto y su fondo. El mínimo recomendado para texto normal es 4,5 a 1, y para texto grande, 3 a 1. Un gris claro sobre blanco —tentador porque «se ve elegante»— suele quedar por debajo de 3, y para alguien con poca visión o mirando el celular al sol es directamente ilegible.
Y una advertencia que va más allá del CSS: el color no puede ser el único portador de un significado. Si un dato mal cargado se marca solo con texto rojo, quien no distingue el rojo no se entera. Siempre hay que sumar otro indicador —un ícono, un borde, la palabra «error»—. Lo mismo vale para los formularios: el campo obligatorio vacío no debería distinguirse solo porque el borde se puso rojo.
Tipografía: fuente, tamaño, grosor, alineación e interlineado
| Propiedad | Qué controla | Valores típicos |
|---|---|---|
font-family | La fuente. | Una lista de nombres, del preferido al de reserva. |
font-size | El tamaño. | 1rem, 18px, 1.1em |
font-weight | El grosor. | 400 normal, 600 seminegrita, 700 negrita |
font-style | La inclinación. | normal, italic |
text-align | La alineación. | left, center, right, justify |
line-height | El interlineado. | 1.5 o 1.6 para texto corrido |
letter-spacing | El espacio entre letras. | 0.02em |
text-transform | Mayúsculas y minúsculas. | uppercase, capitalize |
El font-family casi nunca lleva una sola fuente sino una lista, y el motivo es práctico: la computadora del usuario tiene que tener instalada esa fuente. Si no la tiene, el navegador prueba con la siguiente, y la lista termina siempre en una de las cinco familias genéricas —serif, sans-serif, monospace, cursive, fantasy— que existen en todos los dispositivos.
body {
font-family: "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
line-height: 1.6;
}
Los nombres con espacios van entre comillas —"Segoe UI"— porque sin ellas el navegador entiende que son varias fuentes separadas. Y la genérica va al final siempre: es la red de seguridad.
Sobre la legibilidad, tres criterios que mejoran cualquier página. El interlineado: el valor por defecto del navegador —alrededor de 1,2— está pensado para ahorrar espacio, no para leer; con 1,5 o 1,6 el texto respira. El largo del renglón: entre 45 y 75 caracteres es lo cómodo, porque el ojo necesita volver al principio del renglón siguiente sin perderse. Y la cantidad de fuentes: dos, o tres como mucho en todo el sitio. Cada fuente nueva es una decisión más que tomar y una descarga más que hacer.
Modelo de caja: contenido, relleno, borde y margen
Para CSS, todo elemento es una caja, y cada caja tiene cuatro capas, de adentro hacia afuera:
- Contenido: el texto o la imagen. Es el centro.
- Relleno (
padding): el espacio adentro del borde. Empuja el contenido hacia adentro. - Borde (
border): la línea del límite. - Margen (
margin): el espacio afuera. Separa esta caja de las vecinas.
La diferencia entre relleno y margen se ve de un modo infalible: poner un color de fondo. El fondo llega hasta el borde, así que cubre el relleno y no cubre el margen. Si al pintar la caja el color llega hasta donde uno quería, el espacio era relleno; si quedó un aire sin pintar, era margen.
El margen y el relleno se escriben con atajos, y el orden de los valores sigue las agujas del reloj, empezando por arriba:
margin: 10px; /* los cuatro lados */
margin: 10px 20px; /* arriba/abajo izquierda/derecha */
margin: 10px 20px 30px; /* arriba lados abajo */
margin: 10px 20px 30px 40px; /* arriba derecha abajo izquierda */
width mide solo el contenido. Entonces una caja con width: 100% y padding: 20px ocupa el 100 % más 40 píxeles, y se sale de su contenedor. Es el error de maquetación más clásico que existe.
La solución es una sola línea, que se pone una vez al principio de la hoja y vale para todo el sitio: box-sizing: border-box;. Con eso, width pasa a incluir el relleno y el borde, y una caja de 100 % mide 100 % de verdad. Y un detalle que sorprende a todos: los márgenes verticales se superponen. Dos cajas, una debajo de la otra, con 20 y 30 píxeles de margen, no quedan separadas por 50 sino por 30: se toma el mayor. El margen horizontal, en cambio, sí se suma.
Tamaños y unidades: ancho, alto, píxeles, porcentajes y rem
CSS tiene unidades con lógicas muy distintas, y elegir la correcta es la mitad del diseño adaptable:
| Unidad | Es relativa a… | Conviene para |
|---|---|---|
px | Nada: es absoluta. | Bordes, sombras y detalles que no deben cambiar. |
% | El contenedor (o el tamaño de letra del padre, si es texto). | Anchos que acompañan al contenedor. |
em | El tamaño de letra del propio elemento. | Espaciados que deben escalar con la letra. |
rem | El tamaño de letra de la raíz del documento. | Tipografía y espaciados del sitio. |
vw / vh | El ancho / alto de la ventana. | Bloques que ocupan toda la pantalla. |
ch | El ancho del carácter «0». | Limitar el largo del renglón: 65ch. |
La trampa está en em. Como es relativa al tamaño de letra del propio elemento, y ese tamaño puede venir heredado, los em se multiplican: un texto a 1.2em dentro de otro a 1.2em no da 1.2 sino 1.44. Anidando tres niveles, el texto termina enorme y nadie entiende por qué.
rem evita ese problema porque apunta siempre a un solo valor: el tamaño de letra de la raíz. Un 2rem vale lo mismo en cualquier lugar del documento, sin importar cuán anidado esté. Por eso es la unidad recomendada para tipografía.
Y hay una razón de accesibilidad que decide la discusión: quien tiene problemas de visión puede agrandar la letra desde la configuración del navegador. Si los tamaños están en rem, todo el sitio crece con esa preferencia. Si están en px, no pasa nada: el usuario agranda y el texto sigue igual.
Para los anchos, la regla que hace que una página se adapte sola es usar max-width en lugar de width. Un contenedor con max-width: 70rem crece hasta ese límite en un monitor grande y se achica sin problema en un celular. Con width fijo, en el celular aparece una barra de desplazamiento horizontal y el contenido se corta.
Estilos para enlaces, tablas y formularios
Son los tres elementos que traen un estilo propio del navegador, y los tres suelen verse fuera de lugar si no se los toca.
Enlaces
Se les quita el subrayado con text-decoration: none y se les cambia el color con color. Pero un enlace tiene cuatro estados y conviene darles estilo a los cuatro, en este orden:
a:link { color: #1c814e; } /* todavía no visitado */
a:visited { color: #6b4f9e; } /* ya visitado */
a:hover { color: #14603a; } /* el puntero encima */
a:active { color: #0d3f26; } /* mientras se hace clic */
El orden no es una convención caprichosa: los cuatro selectores tienen exactamente el mismo peso, así que cuando dos se podrían aplicar —un enlace visitado sobre el que está el puntero— gana el que aparece último. Si :visited va después de :hover, el efecto del puntero nunca se ve en un enlace ya visitado.
Tablas
Lo primero es border-collapse: collapse;, porque si no cada celda dibuja su propio borde y quedan líneas dobles. Después, el relleno de las celdas: sin padding, los datos quedan pegados a las líneas y cuesta leerlos. Y el problema serio: una tabla con muchas columnas no entra en la pantalla de un celular.
.tabla-scroll {
overflow-x: auto; /* en el celular, se desplaza en lugar de romper */
}
Eso es todo lo que hace falta: envolver la tabla en un contenedor con desplazamiento horizontal. Es preferible a achicar la letra o a esconder columnas, porque el dato sigue estando completo.
Formularios
Los controles de formulario no heredan la tipografía. El navegador les pone su propia fuente y su propio tamaño, así que si no se dice lo contrario, los campos se ven con una letra distinta a la del resto de la página. Se arregla con una línea:
input, select, textarea, button {
font-family: inherit;
font-size: inherit;
}
outline: none y no reemplazarlo por otra marca visible deja el formulario inutilizable para esa persona.
Flexbox básico: distribución, alineación y separación de elementos
Poner dos cosas una al lado de la otra fue durante años un problema en CSS, y se resolvía con trucos frágiles. Flexbox lo resolvió de raíz: se declara display: flex en un contenedor y sus hijos directos se acomodan solos en una fila.
display: flex los hijos se acomodan en fila; gap separa y justify-content reparte.Las propiedades se dividen en dos grupos, y la clave es entender que hay un eje principal y uno transversal. Con la configuración por defecto, el principal es horizontal:
| Propiedad | Eje | Valores útiles |
|---|---|---|
flex-direction | Define cuál es el principal. | row (por defecto), column |
justify-content | Reparte en el principal. | flex-start, center, space-between, space-around |
align-items | Alinea en el transversal. | stretch (por defecto), center, flex-start, flex-end |
gap | La separación entre hijos. | 1rem, 0.5rem 1rem |
flex-wrap | Si los hijos bajan de fila al no entrar. | wrap |
gap merece un comentario aparte, porque reemplaza una práctica vieja y peor: separar los elementos con margin-right. Con márgenes, el último elemento queda con un margen al aire que hay que sacar a mano, y la separación entre el borde del contenedor y el primero no se controla. Con gap, el espacio es solo entre los hijos y nunca en los extremos.
/* Una fila de tarjetas que baja de línea sola */
.tarjetas {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
/* Centrar algo en los dos ejes: el clásico */
.centrado {
display: flex;
justify-content: center;
align-items: center;
}
Ese segundo ejemplo es la razón por la que Flexbox se usa tanto: centrar un elemento vertical y horizontalmente, que antes requería trucos de posicionamiento imposibles de recordar, son tres líneas.
Diseño adaptable: anchos flexibles y consultas de medios
La misma página se va a ver en un monitor de 1920 píxeles y en un celular de 360. El diseño adaptable (o responsive) es hacer que un solo HTML sirva para los dos.
La solución vieja era hacer dos sitios, uno de escritorio y otro «versión móvil», con direcciones distintas. El problema era evidente: todo había que hacerlo dos veces y mantenerlo dos veces.
Hoy son tres herramientas que trabajan juntas:
- Anchos flexibles.
max-widthy porcentajes en lugar dewidthen píxeles. Un contenedor se achica solo, sin necesidad de reglas extra. - Unidades relativas. Tamaños en
rem, que acompañan la configuración de letra del usuario. - Consultas de medios (media queries): reglas que se aplican solo si se cumple una condición sobre la pantalla.
/* Por defecto: pensado para el celular */
@media (min-width: 48rem) {
/* A partir de 48rem de ancho, se agrega esto */
.tarjetas {
display: flex;
}
}
Fijate en la lógica de ese ejemplo: el estilo base es el del celular, y la consulta agrega cosas para pantallas grandes. Eso se llama trabajar «mobile first», y tiene una ventaja que va más allá de la moda: obliga a decidir qué es lo importante, porque en la pantalla chica hay lugar para poco. Lo accesorio se agrega después, cuando hay espacio.
<meta name="viewport" content="width=device-width, initial-scale=1">. Va en el <head> y es la que le dice al celular que use el ancho real de su pantalla. Sin ella, el celular finge tener casi mil píxeles de ancho para mostrar el sitio «como en la computadora», y todo se ve diminuto, con las consultas de medios ignoradas. Es la causa más común de «mi diseño adaptable no funciona».
Queda un caso que no se resuelve con CSS solo: las tablas. Una tabla de seis columnas no entra en un celular, y achicar la letra hasta que entre la vuelve ilegible. Las dos salidas razonables son permitir el desplazamiento horizontal dentro de un contenedor —como se vio antes— o cambiar la estructura: en pantallas chicas, mostrar cada fila como una ficha con la etiqueta de cada dato al lado. La segunda es bastante más trabajo, pero es la que se lee bien de verdad.
Estados de interacción: :hover y :focus
Un elemento no se ve siempre igual: según lo que el usuario esté haciendo con él, cambia. CSS permite dar estilo a cada uno de esos estados con las pseudoclases, que se escriben con dos puntos y se pegan al selector.
| Pseudoclase | Cuándo se aplica | Para qué se usa |
|---|---|---|
:hover | Mientras el puntero está encima. | Avisar que algo es clickeable. |
:focus | Cuando el elemento tiene el foco, con Tab o al hacer clic en un campo. | Mostrar dónde está parado el usuario. |
:focus-visible | Solo cuando el foco llegó navegando con teclado. | Mostrar el contorno solo a quien lo necesita. |
:active | En el instante del clic, mientras el botón está apretado. | Dar respuesta inmediata al toque. |
:disabled | Cuando el control está deshabilitado. | Que se vea que no se puede usar. |
:checked | Cuando una casilla o un botón de opción está marcado. | Resaltar la opción elegida. |
:invalid | Cuando el valor no cumple la validación. | Marcar el campo con error. |
.boton:hover {
background-color: #14603a;
}
.campo:focus {
border-color: #6edfa3;
outline: 2px solid #6edfa3;
outline-offset: 2px;
}
Hay dos advertencias que valen más que cualquier truco de estilo.
La primera: :hover no existe en las pantallas táctiles. En un celular o una tablet no hay puntero que se pare encima, así que ese estado nunca se activa. Cualquier información que aparezca solo al pasar el mouse —un menú, un dato, una aclaración— es información inaccesible en la mitad de los dispositivos. Como comodidad está perfecto; como único camino, no.
La segunda es más grave y ya se adelantó: :focus es obligatorio, no opcional. Es lo único que ve quien navega con la tecla Tab, y hay gente que navega así siempre: porque no puede usar el mouse, o porque le resulta mucho más rápido. Si se saca el contorno del foco y no se pone otra marca visible en su lugar, esa persona queda dando toques a ciegas, sin saber en qué botón va a caer su próxima pulsación. :focus-visible existe justamente para eso: permite que el contorno aparezca cuando se navega con teclado y no cuando se hace clic con el mouse, que es lo que suele molestar de :focus.