Ya sabes apilar cajas en el flujo, medirlas con el box model y distribuirlas con
Flexbox y Grid. Todo eso coloca los elementos unos junto a otros. Falta la
última pieza del layout: superponerlos (un badge sobre una carta), fijarlos
(una barra que no se va al hacer scroll) y controlar lo que se desborda (un
nombre demasiado largo). Eso es position, z-index y overflow.
Salir del flujo: position#
Por defecto, cada elemento está en el flujo normal: position: static. La
propiedad position te deja cambiarlo. Sus valores:
| Valor | Qué hace |
|---|---|
static | El flujo normal (por defecto). top/left no tienen efecto. |
relative | Se desplaza desde su sitio con top/right/bottom/left, pero conserva su hueco. |
absolute | Sale del flujo y se coloca respecto al ancestro posicionado más cercano. |
fixed | Sale del flujo y se ancla a la ventana: no se mueve al hacer scroll. |
sticky | Normal hasta que el scroll lo sacaría; entonces se “pega” al borde indicado. |
relative: desplazar sin perder el sitio#
Con relative, el elemento se mueve respecto a donde estaría, pero su hueco
original se mantiene (los vecinos no se recolocan):
.aviso {
/* sale del flujo normal sin perder su hueco */
position: relative;
/* baja medio rem desde donde estaría normalmente */
top: 0.5rem;
}Su uso más importante, sin embargo, no es desplazar: es servir de marco de
referencia para un hijo absolute. Eso nos lleva al siguiente valor.
absolute: anclar a un ancestro#
Un elemento absolute sale del flujo (deja de ocupar sitio) y se coloca con
top/right/bottom/left respecto a su ancestro posicionado más cercano: el
primer padre o abuelo con position distinto de static. Si ninguno lo tiene,
se ancla a la página entera.
Por eso, para poner un badge en la esquina de una carta, el patrón es siempre el mismo —el más usado del posicionamiento—:
.carta {
/* marco de referencia, no la mueve */
position: relative;
}
.carta .badge {
/* sale del flujo y se ancla al ancestro posicionado (.carta) */
position: absolute;
/* a 0.5rem del borde superior de .carta */
top: 0.5rem;
/* respecto a la esquina de .carta */
right: 0.5rem;
}El relative de la carta no la desplaza (no le damos top/left): solo la convierte
en el sistema de coordenadas del badge.
fixed y sticky: barras que se quedan#
Las dos mantienen un elemento visible al hacer scroll, pero de forma distinta:
fixedlo ancla a la ventana y lo saca del flujo. Útil para un botón flotante, pero ojo: como deja de ocupar sitio, el contenido se mete debajo y suele haber que compensar con un margen o padding.stickyes híbrido: el elemento ocupa su sitio normal y solo se “pega” al borde (top: 0) cuando el scroll lo alcanzaría. No tapa nada. Es lo habitual para una cabecera que debe quedarse arriba.
/* Ejemplo de position: fixed — botón que flota sobre el contenido */
.boton-flotante {
/* sale del flujo y se ancla a la ventana: no se mueve al hacer scroll */
position: fixed;
/* a 1.5rem del borde inferior de la ventana */
bottom: 1.5rem;
/* a 1.5rem del borde derecho de la ventana */
right: 1.5rem;
/* por encima de todo lo demás */
z-index: 100;
}.barra {
/* híbrido: normal hasta que el scroll lo sacaría, entonces se pega */
position: sticky;
/* el borde al que se pega: el superior */
top: 0;
/* la mantiene por encima del resto al superponerse */
z-index: 10;
}Trampa frecuente con sticky: para que funcione hacen falta dos cosas. Primera,
un offset declarado (top, bottom, left o right): sin él, el elemento nunca
sabe a qué borde pegarse y se comporta como relative. Segunda, que ningún
ancestro tenga overflow: hidden, overflow: auto u overflow: scroll; si
lo tiene, el sticky queda “atrapado” dentro de ese ancestro y deja de pegarse.
Son las dos causas más comunes de “he puesto sticky y no funciona”.
z-index: quién va por encima#
Cuando dos elementos se superponen, ¿cuál se ve delante? Lo decide z-index: a
mayor número, más cerca del usuario. La regla que más despista: solo funciona en
elementos posicionados (con position distinto de static). En una caja normal
no tiene ningún efecto.
/* por encima */
.barra { position: sticky; z-index: 10; }
/* por debajo de la barra */
.badge { position: absolute; z-index: 2; }Cuando dos elementos posicionados no llevan z-index, gana el que va más tarde en
el HTML. Declararlo a mano evita sorpresas.
Hay una trampa que cuesta horas la primera vez: subes el z-index a 9999 y el
elemento sigue apareciendo por detrás de otro con un número mucho menor. La causa
es el contexto de apilamiento. Ciertas propiedades crean una “burbuja” de apilamiento,
y dentro de ella los z-index de los hijos solo compiten entre ellos, no con el resto
de la página: por alto que pongas el número, ese elemento no saldrá por encima de algo
que esté fuera de su burbuja. Las que la crean más a menudo son position con z-index
(que ya conoces) y un par que verás en el capítulo de acabado visual, como transform y
opacity por debajo de 1. La regla práctica: cuando un z-index enorme no funcione,
no lo subas más; busca un ancestro con una de esas propiedades y sube el z-index de ese
ancestro.
overflow: contenido que no cabe#
overflow decide qué pasa cuando el contenido es más grande que su caja:
visible(por defecto): se sale y se dibuja fuera.hidden: se recorta lo que sobra.auto/scroll: aparece una barra de desplazamiento dentro de la caja.
Su uso estrella en interfaces es recortar un texto largo en una sola línea, con puntos suspensivos. Hacen falta tres propiedades juntas:
.hero-name {
/* no parte en varias líneas */
white-space: nowrap;
/* recorta lo que se sale */
overflow: hidden;
/* y lo sustituye por «…» */
text-overflow: ellipsis;
}Quita cualquiera de las tres y el efecto desaparece: sin nowrap el texto salta
de línea, sin overflow: hidden no se recorta, y sin text-overflow se corta en
seco sin avisar.
Pruébalo#
Haz scroll en el preview para ver la barra sticky pegarse arriba. Luego cambia
la position del .badge en styles.css entre absolute, relative y static
y observa la diferencia: absolute lo ancla a la esquina de la carta, relative
lo mueve pero deja su hueco, static lo devuelve al flujo.
Comprueba lo que sabes#
Pregunta 1 de 5
¿Qué hace `position: relative` en un elemento?
Tu turno#
La plantilla tiene tres detalles sin resolver. Coloca el badge en la esquina de su carta, fija la barra al hacer scroll y recorta el nombre largo. Cuando lo tengas, estrecha el preview a 375px para comprobar que aguanta, y despliega las soluciones.
Ejercicio · en esta página
Coloca el badge, fija la barra y recorta el nombre
La plantilla del Team Builder ya tiene su grid y sus cartas. Faltan tres detalles de posicionamiento: el badge "Nuevo" en la esquina de su carta, la barra superior pegada al hacer scroll y el nombre largo recortado con puntos suspensivos. Completa los TODO de starter/styles.css con position, z-index y overflow.
Paso 1: Que funcione
- El badge "Nuevo" aparece superpuesto en la esquina de su carta.
- La barra se mantiene arriba al hacer scroll (aunque sea con la herramienta no ideal).
- El nombre largo no rompe la carta.
Paso 2: La herramienta correcta
- La carta es `position: relative` y el badge `position: absolute` con top/right.
- La barra usa `position: sticky; top: 0` (no `fixed`), así que no tapa el contenido.
- El nombre se recorta con `white-space: nowrap` + `overflow: hidden` + `text-overflow: ellipsis`.
Paso 3: Apilamiento y móvil
- z-index explícito: el badge sobre la carta, la barra sobre todo. El orden no se deja al azar del HTML.
- Entiendes que z-index solo afecta a elementos posicionados.
- A 375px todo aguanta: una columna, el badge en su esquina y el nombre recortado.
Ver soluciones
/* SOLUCIÓN OK — funciona, pero con las herramientas equivocadas.
El badge sí se coloca, pero la barra usa `fixed` (y tapa el contenido) y el
nombre se recorta sin puntos suspensivos, cortado en seco. */
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family:
system-ui,
-apple-system,
"Segoe UI",
Roboto,
sans-serif;
background: #f7f6fb;
color: #1c1b22;
line-height: 1.5;
}
.barra {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
padding: 0.85rem 1.25rem;
background: #1c1b22;
color: #fff;
/* `fixed` la saca del flujo y la fija a la ventana: se queda arriba, sí, pero
el contenido se mete DEBAJO y el título queda tapado. Habría que compensar
con un padding-top en el body. Para algo que solo debe pegarse al borde
superior, la herramienta correcta es `sticky`. */
position: fixed;
top: 0;
left: 0;
right: 0;
}
.barra-marca {
font-weight: 700;
}
.barra-info {
font-size: 0.85rem;
color: #c9c7d6;
}
.contenido {
max-width: 60rem;
margin: 0 auto;
padding: 1.5rem;
}
.titulo {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #5b5966;
margin: 0 0 1rem;
}
.rejilla {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
gap: 1rem;
}
.hero-card {
background: #fff;
border: 1px solid #e6e3ee;
border-radius: 12px;
padding: 1.25rem;
/* La carta es el marco de referencia del badge: esto sí está bien. */
position: relative;
}
.hero-portrait {
width: 3rem;
height: 3rem;
border-radius: 50%;
background: #b8336a;
color: #fff;
font-weight: 700;
display: grid;
place-items: center;
margin-bottom: 0.75rem;
}
.hero-name {
font-size: 1.05rem;
font-weight: 700;
margin: 0;
/* Recorta, pero sin avisar: el nombre se corta en seco a mitad de palabra.
Falta `text-overflow: ellipsis` para los puntos suspensivos. */
overflow: hidden;
white-space: nowrap;
}
.hero-role {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #5b5966;
margin: 0.2rem 0 0;
}
.badge {
background: #b8336a;
color: #fff;
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: 0.2rem 0.5rem;
border-radius: 999px;
position: absolute;
top: 0.5rem;
right: 0.5rem;
} Por qué este nivel
- El badge se coloca bien: la carta es `relative` y el badge `absolute` en su esquina. Esa parte está resuelta.
- La barra usa `position: fixed`: se queda arriba, pero sale del flujo y el contenido se mete debajo, tapando el título. Para pegarse al borde superior, lo correcto es `sticky`.
- El nombre se recorta con `overflow: hidden` y `nowrap`, pero sin `text-overflow: ellipsis`: se corta a mitad de palabra, sin los puntos suspensivos que avisan de que hay más.
/* SOLUCIÓN MEJOR — la herramienta correcta para cada caso.
Badge anclado a su carta, barra `sticky` (se pega sin tapar nada) y nombre
recortado con puntos suspensivos. */
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family:
system-ui,
-apple-system,
"Segoe UI",
Roboto,
sans-serif;
background: #f7f6fb;
color: #1c1b22;
line-height: 1.5;
}
.barra {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
padding: 0.85rem 1.25rem;
background: #1c1b22;
color: #fff;
/* `sticky`: se comporta como normal hasta que el scroll la llevaría fuera, y
entonces se "pega" a top: 0. Sigue en el flujo, así que no tapa el contenido.
z-index la mantiene por encima de las cartas que pasan por debajo. */
position: sticky;
top: 0;
z-index: 10;
}
.barra-marca {
font-weight: 700;
}
.barra-info {
font-size: 0.85rem;
color: #c9c7d6;
}
.contenido {
max-width: 60rem;
margin: 0 auto;
padding: 1.5rem;
}
.titulo {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #5b5966;
margin: 0 0 1rem;
}
.rejilla {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
gap: 1rem;
}
.hero-card {
background: #fff;
border: 1px solid #e6e3ee;
border-radius: 12px;
padding: 1.25rem;
/* Marco de referencia del badge: este `relative` no mueve la carta, solo hace
que el `absolute` de dentro se coloque respecto a ella. */
position: relative;
}
.hero-portrait {
width: 3rem;
height: 3rem;
border-radius: 50%;
background: #b8336a;
color: #fff;
font-weight: 700;
display: grid;
place-items: center;
margin-bottom: 0.75rem;
}
.hero-name {
font-size: 1.05rem;
font-weight: 700;
margin: 0;
/* Las tres juntas recortan en una línea con puntos suspensivos:
nowrap (no parte) + overflow hidden (recorta) + text-overflow ellipsis (…). */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.hero-role {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #5b5966;
margin: 0.2rem 0 0;
}
.badge {
background: #b8336a;
color: #fff;
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: 0.2rem 0.5rem;
border-radius: 999px;
position: absolute;
top: 0.5rem;
right: 0.5rem;
} Por qué es mejor que el anterior
- `position: sticky; top: 0` en la barra: se comporta como normal hasta que el scroll la sacaría, y entonces se pega arriba. Sigue en el flujo, así que no tapa nada. El `z-index` la mantiene sobre las cartas.
- El badge se ancla a su carta porque la carta es `relative` (el marco de referencia) y el badge `absolute`. Sin ese `relative`, el badge buscaría el siguiente ancestro posicionado, o la página.
- Las tres propiedades del recorte trabajan juntas: `nowrap` impide partir la línea, `overflow: hidden` recorta lo que sobra y `text-overflow: ellipsis` pone los puntos suspensivos.
/* SOLUCIÓN EXCELENTE — posicionamiento con el apilamiento bajo control y a
prueba de móvil. Todo lo de "mejor", más una capa de orden explícita y la
comprobación a 375px.
- El badge lleva su propio z-index para quedar por encima del contenido de la
carta; la barra lleva uno mayor (10) para quedar por encima de todo al
hacer scroll. El apilamiento no se deja al azar del orden del HTML.
- La rejilla ya es responsive (auto-fit + minmax): a 375px cae a una columna,
el badge sigue en su esquina y el nombre largo se recorta sin romper nada.
Arrastra el preview a 375px para comprobarlo. */
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family:
system-ui,
-apple-system,
"Segoe UI",
Roboto,
sans-serif;
background: #f7f6fb;
color: #1c1b22;
line-height: 1.5;
}
.barra {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
padding: 0.85rem 1.25rem;
background: #1c1b22;
color: #fff;
position: sticky;
top: 0;
/* Por encima de cualquier carta y de su badge cuando pasan por debajo. */
z-index: 10;
}
.barra-marca {
font-weight: 700;
}
.barra-info {
font-size: 0.85rem;
color: #c9c7d6;
}
.contenido {
max-width: 60rem;
margin: 0 auto;
padding: 1.5rem;
}
.titulo {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #5b5966;
margin: 0 0 1rem;
}
.rejilla {
list-style: none;
margin: 0;
padding: 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
gap: 1rem;
}
.hero-card {
background: #fff;
border: 1px solid #e6e3ee;
border-radius: 12px;
padding: 1.25rem;
position: relative;
}
.hero-portrait {
width: 3rem;
height: 3rem;
border-radius: 50%;
background: #b8336a;
color: #fff;
font-weight: 700;
display: grid;
place-items: center;
margin-bottom: 0.75rem;
}
.hero-name {
font-size: 1.05rem;
font-weight: 700;
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.hero-role {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: #5b5966;
margin: 0.2rem 0 0;
}
.badge {
background: #b8336a;
color: #fff;
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: 0.2rem 0.5rem;
border-radius: 999px;
position: absolute;
top: 0.5rem;
right: 0.5rem;
/* Por encima del contenido de la carta; por debajo de la barra (z-index: 10). */
z-index: 2;
} Por qué es mejor que el anterior
- Mismo resultado que «mejor», pero el apilamiento es explícito: el badge lleva su `z-index` (sobre el contenido de la carta) y la barra uno mayor (sobre todo). No depende del orden del HTML.
- `z-index` solo afecta a elementos posicionados: por eso funciona en el badge (`absolute`) y en la barra (`sticky`), no en una caja normal.
- Comprobado a 375px: la rejilla cae a una columna, el badge sigue en su esquina y el nombre se recorta. Una solución de UI que no aguanta el móvil no es excelente en 2026.