Tres horas. Media hora para entender cómo encaja una imagen en un hueco que no es el suyo y por qué el estado de foco no es opcional, y dos horas y media dejando el catálogo coherente y el sitio recorrible sin ratón.
Hoy · Hoja de ruta
- 1. Aprende: Por qué una imagen rompe un layout, cómo se encajan imágenes de tamaños distintos sin deformarlas, qué estados tiene una interfaz y cuándo un movimiento comunica algo.
- 2. Haz: Consigue un catálogo coherente con imágenes irregulares, recorre tu sitio con el teclado y añade solo las transiciones que informen.
- 3. Comprueba: Ninguna imagen está deformada, sabes en todo momento dónde está el foco, y cada movimiento de tu página comunica algo.
Antes de empezar · 5 minutos, sin apuntes
- ¿Qué significa trabajar mobile first?
- ¿Qué diferencia hay entre un layout fluido y una media query?
- Ordena el diagnóstico de un desbordamiento horizontal antes de escribir CSS nuevo.
Se explica
25 minutos · conceptos y demostración
Tres asuntos que parecen menores y no lo son: una imagen que no encaja rompe el layout entero, un foco invisible deja la web inservible para quien no usa ratón, y un movimiento sin motivo estorba. Los tres se resuelven con decisiones, no con recetas.
La regla que va en toda hoja de estilos
Una imagen tiene un tamaño propio, y si es más ancha que su contenedor, se sale. Es la causa número uno de scroll horizontal.
img {
max-width: 100%;
height: auto;
}
max-width: 100% impide que sobrepase a su contenedor. height: auto es imprescindible junto a la anterior: sin ella, si el HTML declaraba width y height —como pedíamos en la UD1—, al reducirse el ancho la altura se quedaría fija y la imagen se deformaría.
Encajar una imagen en un hueco que no es el suyo
El problema real de un catálogo: las fotos de producto vienen con tamaños y proporciones distintas, y las tarjetas quedan desiguales. La tentación es forzarlas, y eso las estira:
/* Mal: deforma */
.card img { width: 100%; height: 15rem; }
La solución es decirle cómo debe encajar en el hueco que le das:
.card img {
width: 100%;
height: 15rem;
object-fit: cover;
}
object-fit
Cómo se acomoda el contenido de una imagen dentro de la caja que se le ha dado. cover la escala hasta llenarla y recorta lo que sobra, conservando la proporción. contain la encaja entera y deja huecos.
| Valor | Resultado |
|---|---|
fill |
Por defecto: la estira hasta llenar. Deforma |
cover |
Llena el hueco y recorta. La opción de un catálogo |
contain |
Cabe entera, con huecos a los lados |
Si el recorte de cover elimina la parte relevante, object-position determina qué zona se conserva:
.card img { object-fit: cover; object-position: top; }
Mejor todavía que fijar una altura en rem es declarar una proporción, porque entonces la altura se calcula sola a partir del ancho y la tarjeta funciona igual en una columna estrecha que en una ancha:
.card img {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
Una interfaz tiene más de un estado
a { color: var(--color-primary); }
a:visited { color: #6d28d9; }
a:hover { text-decoration: underline; }
a:focus-visible{ outline: 3px solid currentColor; outline-offset: 3px; }
a:active { color: #b91c1c; }
En formularios, los campos preparados en la UD1 proporcionan ya sus propios selectores:
input:focus { }
input:required { }
input:invalid { }
input:disabled { }
input:checked { }
:invalid merece una advertencia: se aplica desde que carga la página, así que un campo obligatorio y vacío aparece en rojo antes de que nadie haya escrito nada. Combínalo con :user-invalid, que solo actúa después de que la persona haya interactuado, o marca el error de otra forma.
Nunca outline: none a secas
El contorno del foco resulta visualmente poco atractivo y constituye la única indicación de posición para quien navega con teclado. Su supresión deja la web inutilizable para esas personas: al pulsar Tab no se produce ningún cambio perceptible.
Si no te gusta el contorno por defecto, sustitúyelo por otro igual de claro: un contorno propio, un cambio de fondo, un borde. Lo que no vale es dejarlo sin ninguna indicación.
:focus-visible
Se aplica cuando el navegador entiende que la indicación de foco hace falta: al llegar con el teclado, sí; al hacer clic con el ratón en un botón, normalmente no.
Así se puede tener un foco de teclado bien visible sin que aparezca un recuadro cada vez que alguien pulsa con el ratón, que era la razón por la que la gente lo quitaba.
:focus-visible {
outline: 3px solid var(--color-primary);
outline-offset: 3px;
}
outline tiene una ventaja sobre border para esto: no ocupa espacio en el layout, así que nada se mueve al recibir el foco. Y outline-offset lo separa del elemento para que se lea mejor.
Transiciones y transformaciones
Una transición suaviza el paso de un estado a otro. Se declara en el estado normal, no en el :hover, para que funcione en los dos sentidos:
.card {
transition: transform 150ms ease, box-shadow 150ms ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgb(0 0 0 / 0.12);
}
transform qué propiedad se anima
150ms cuánto dura
ease cómo acelera
No animes all
transition: all 300ms parece cómodo y anima también cosas que no querías, incluidas propiedades que obligan al navegador a recalcular el layout entero. El resultado es una interfaz que va a tirones sin que se sepa por qué.
Nombra las propiedades. Si puedes elegir, anima transform y opacity: son las dos que el navegador resuelve sin rehacer el layout, y por eso van suaves.
transform: translateY(-2px);
transform: scale(1.03);
transform: rotate(2deg);
transform: translateY(-2px) scale(1.02); /* se combinan en una sola declaración */
Una transformación no afecta al espacio que ocupa el elemento: se dibuja movido, pero su hueco sigue donde estaba y nada se descoloca alrededor. Por eso mover una tarjeta con transform es seguro y hacerlo con margin-top no.
El movimiento tiene que decir algo
Una interfaz no mejora por tener zooms, rebotes, sombras y degradados. Cada efecto debería responder a una pregunta: ¿qué le está comunicando esto a quien lo ve?
| Movimiento | Comunica | ¿Vale la pena? |
|---|---|---|
| Un botón que se aclara al pasar por encima | «Esto se puede pulsar» | Sí |
| Una tarjeta que se eleva ligeramente | «Esto es interactivo» | Sí |
| Un campo que se marca al recibir el foco | «Estás escribiendo aquí» | Sí |
| Un titular que entra rebotando al cargar | Nada | No |
Hay además quien configura su sistema para reducir las animaciones, y no por gusto: el movimiento puede provocar mareo o desorientación. El sistema operativo lo comunica, y CSS puede leerlo.
prefers-reduced-motion
Una media query que no pregunta por el tamaño de la pantalla sino por una preferencia declarada por quien usa el dispositivo. Existen más de esta familia, como prefers-color-scheme.
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
Es uno de los poquísimos sitios donde !important está justificado: es una preferencia de la persona, y debe ganar a cualquier estilo del sitio.
Se trabaja
150 minutos · práctica sobre tu propio proyecto
Los dos primeros pasos arreglan lo que está roto: las imágenes y el foco. Los cuatro últimos añaden movimiento, y después quitan el que sobra.
Paso 1 · El catálogo irregular · 35 min
Reúne al menos seis imágenes deliberadamente dispares: alguna vertical, alguna horizontal, alguna muy grande, alguna pequeña. Puedes usar fotos reales de tu tema.
Consigue que el catálogo se vea coherente sin editar los archivos:
- Todas las tarjetas con la misma altura de imagen.
- Ninguna imagen deformada.
- Nada se sale a 320 px.
- Comprueba las verticales: si
coverles corta la cabeza a las fotos, ajústalo conobject-position. - Explica en un comentario por qué has elegido
coverocontain.
Estoy atascado · las imágenes se ven aplastadas
Si has dado width y height a la vez sin object-fit, el navegador estira la imagen hasta esa caja. Añade object-fit: cover.
Y si el problema aparece solo al estrechar la ventana, lo más probable es que te falte height: auto junto al max-width: 100%.
Antes de continuar: las seis tarjetas tienen la misma altura de imagen y ninguna foto está estirada ni aplastada.
Paso 2 · Los estados invisibles · 35 min
Suelta el ratón. Recorre tus cuatro páginas con Tab, Shift + Tab y Enter.
| Elemento | ¿Se ve el foco? | ¿Se distingue del estado normal? | Corrección |
|---|---|---|---|
| Enlaces de navegación | |||
| Enlaces del contenido | |||
| Botones | |||
| Campos del formulario | |||
| Casillas y radios |
Después:
- Define un
:focus-visiblepropio y coherente para todo el sitio. - Comprueba que los enlaces del texto se distinguen del resto sin depender solo del color: alguien daltónico necesita el subrayado.
- Da estilo a
:hoveren enlaces y tarjetas. - Añade un estado visible a los campos obligatorios usando
:required.
Consultar · selectores modernos que simplifican estos estilos
:is() agrupa como la coma, pero funciona dentro de un selector largo, donde la coma obligaría a repetirlo todo:
/* Sin :is() */
main h1, main h2, main h3 { }
/* Con :is() */
main :is(h1, h2, h3) { }
:where() hace lo mismo con especificidad cero, lo cual es muy útil para estilos base que quieras poder sobrescribir sin pelearte:
:where(h1, h2, h3) { margin-block: 0; } /* cualquier clase lo sobrescribe */
Y :has() selecciona un elemento por lo que contiene, algo que CSS no pudo hacer durante veinte años:
.campo:has(input:invalid) {
border-color: #b91c1c;
}
Ahí estás dando estilo al contenedor a partir del estado de su hijo. No hace falta que lo domines: basta con que sepas que los selectores también han evolucionado y que muchos problemas que antes pedían JavaScript ya no lo piden.
Paso 3 · Transiciones con una finalidad · 25 min
- Añade una transición a tus botones y otra a tus tarjetas o enlaces.
- Nombra las propiedades: nada de
all. - Asegúrate de que los estados de
:hovery:focus-visibleson claros y distintos entre sí. - Por cada transición que escribas, anota en un comentario qué comunica. Si no sabes qué escribir, esa transición sobra.
Paso 4 · Respetar la preferencia de movimiento · 10 min
Añade el bloque de prefers-reduced-motion al final de tu hoja y compruébalo de verdad, activando la reducción de movimiento en la configuración de tu sistema o desde DevTools, en el panel de Rendering.
Anota qué cambia en tu sitio al activarla. Si no cambia nada, o el bloque no está bien escrito, o tus transiciones no estaban donde creías.
Paso 5 · Menos es más · 25 min
Ahora la parte determinante: recorre tu sitio y elimina cualquier efecto que no comunique nada.
| Efecto | Dónde está | Qué comunica | ¿Se queda? |
|---|---|---|---|
Es la única tarea de la unidad en la que se puntúa quitar cosas. Cuenta cuántos efectos tenías y cuántos quedan.
Paso 6 · Revisión cruzada sin ratón · 20 min
Intercambia el sitio con un compañero y recorre el suyo usando solo el teclado, sin tocar el ratón en ningún momento.
- Anota el primer punto en el que pierdes de vista el foco.
- Anota cualquier elemento al que no consigas llegar.
- Comprueba si algún estado se distingue únicamente por el color, entrando en DevTools y forzando una simulación de daltonismo desde el panel de Rendering.
- Devuelve las tres respuestas con el formato qué / por qué importa / qué probaría.
El autor decide qué acepta. Si rechaza algo, tiene que justificarlo con lo que hace la interfaz, no con lo que le parece más bonito.
Ampliación si has completado el trabajo
Primero termina y comprueba los seis pasos. Los dos retos se resuelven decidiendo, y uno de ellos consiste casi entero en borrar.
Reto 1 · Las imágenes imposibles
Un cliente entrega estas cinco imágenes para un catálogo de tarjetas iguales:
A · panorámica de 3000 × 600 px (una estantería entera de la tienda)
B · vertical de 800 × 2400 px (una torre de ordenador de cuerpo entero)
C · cuadrada de 500 × 500 px (un ratón sobre fondo blanco)
D · diminuta de 120 × 90 px (un logotipo de fabricante)
E · foto de 4000 × 3000 px (un teclado, con la marca en la esquina inferior)
Consigue un catálogo coherente. Para cada una, decide y justifica:
coverocontain, y por qué.- Qué
object-positionnecesita, si necesita alguno. La E tiene la información en una esquina: piensa qué pasa al recortarla en un formato apaisado. - La D es más pequeña que el hueco. Explica qué ocurre al ampliarla y qué harías en un encargo real, que no siempre es una decisión de CSS.
- La B, recortada a 16 / 9, deja de mostrar el producto. Propón una solución que no sea deformarla ni recortarla.
- Escribe una sola regla de CSS que resuelva bien cuatro de las cinco, y di cuál se queda fuera y por qué.
Reto 2 · Auditoría de movimiento en una interfaz ajena
Elige una web con bastante animación: una tienda grande, una web de producto, una landing.
| Movimiento que has visto | Qué comunica | Decorativo o informativo | ¿Lo conservarías? |
|---|---|---|---|
Localiza al menos seis. Después:
- Cuenta cuántos son informativos y cuántos decorativos.
- Activa la reducción de movimiento en DevTools y recarga. ¿Respeta la web la preferencia, o se mueve igual? Es la comprobación interesante del reto.
- Recorre la página con
Tab. ¿Se ve el foco? ¿Alguna animación lo persigue o lo oculta? - Escribe en tres líneas qué le quitarías a esa interfaz y qué ganaría con ello.
Cierre
5 minutos · comprobación y recuerdo
Lista de verificación de la sesión
- Todas tus imágenes tienen
max-width: 100%y no se deforman. - El foco es visible en todos los elementos interactivos.
- Los enlaces del texto se distinguen sin depender solo del color.
- Tus transiciones nombran propiedades concretas, no
all. - Respetas
prefers-reduced-motion, comprobado activándolo. - Cada efecto que queda tiene escrito al lado qué comunica.
Antes de cerrar · 3 minutos, sin mirar
- ¿Qué par de declaraciones evita que una imagen desborde y se deforme?
- ¿Qué diferencia hay entre
coverycontain? - ¿Por qué no procede escribir
outline: nonesin una alternativa que lo sustituya? - ¿Qué hace
:focus-visibleque no hace:focus? - ¿En qué estado se declara la
transition, y por qué? - ¿Por qué mover algo con
transformes más seguro que conmargin?
Ver respuestas
1 · max-width: 100% y height: auto.
2 · cover llena el hueco recortando lo que sobra; contain mete la imagen entera y deja huecos.
3 · Porque deja sin ninguna indicación a quien navega con teclado. Si se quita, hay que poner otra igual de clara.
4 · Solo aplica el estilo cuando la indicación hace falta, típicamente al llegar con el teclado y no al pulsar con el ratón.
5 · En el estado normal, para que la animación ocurra tanto al entrar como al salir del estado.
6 · Porque transform no cambia el espacio que ocupa el elemento, así que nada se descoloca a su alrededor.
Microprueba semanal 5 · 5–10 minutos
Individual, sin IA y sin apuntes.
- Explica cómo evitar que una imagen se deforme dentro de una tarjeta.
- Escribe un foco visible que no dependa solo del color.
- Añade una transición breve y explica cómo respetarías
prefers-reduced-motion.