Skip to content

CSS Hoja de referencia

Lenguaje de hojas de estilo para describir la presentación de páginas web.

01

Selectores

Selectores básicos

Los selectores CSS apuntan a elementos HTML para estilizar. Los type selectors coinciden nombres de elementos (p, div). Los class selectors (.) coinciden atributos class y pueden reusarse. Los ID selectors (#) coinciden un solo elemento y deberían ser únicos. El universal selector (*) coincide todo. Agrupa selectores con comas para compartir estilos.

css
/* Type selector - all <p> elements */
p { color: #333; }

/* Class selector - elements with class="btn" */
.btn { padding: 8px 16px; }

/* ID selector - element with id="header" */
#header { background: #fff; }

/* Universal selector - all elements */
* { box-sizing: border-box; }

/* Grouping selector */
h1, h2, h3 { font-family: Arial, sans-serif; }

Combinators

Los combinators definen relaciones entre elementos. Descendant (espacio) coincide cualquier elemento anidado. Child (>) coincide solo children directos. Adjacent sibling (+) coincide el elemento inmediatamente después. General sibling (~) coincide todos los siblings después. Entender los combinators es esencial para estilizado preciso sin classes extra.

css
/* Descendant - any <a> inside .nav */
.nav a { text-decoration: none; }

/* Child - direct <li> children of <ul> */
ul > li { list-style: none; }

/* Adjacent sibling - <p> right after <h1> */
h1 + p { font-size: 1.2em; }

/* General sibling - all <p> after <h1> */
h1 ~ p { color: gray; }

Attribute selectors

Los attribute selectors coinciden elementos basándose en valores de atributo. [attr] coincide presencia. [attr=val] coincide valor exacto. ^= coincide prefijo, $= coincide sufijo, *= coincide substring. Estos son potentes para estilizar form inputs, links por tipo o elementos con data attributes sin añadir classes extra.

css
/* Elements with type attribute */
[type] { border: 1px solid #ccc; }

/* Exact match */
[type="text"] { padding: 4px; }

/* Starts with */
a[href^="https"] { color: green; }

/* Ends with */
a[href$=".pdf"] { color: red; }

/* Contains */
a[href*="example"] { font-weight: bold; }

Pseudo-classes

Las pseudo-classes seleccionan elementos basándose en estado o posición. Interactivas: :hover, :focus, :active, :visited, :disabled. Estructurales: :first-child, :last-child, :nth-child(n), :nth-of-type(n). :not() niega un selector. :nth-child(odd/even) crea rayas de cebra. :nth-child(3n) selecciona cada 3er elemento. Estas reducen la necesidad de classes extra.

css
/* Interactive states */
a:hover { color: red; }
a:visited { color: purple; }
input:focus { border-color: blue; }
input:disabled { background: #eee; }
button:active { transform: scale(0.98); }

/* Structural */
li:first-child { font-weight: bold; }
li:last-child { border: none; }
li:nth-child(odd) { background: #f9f9f9; }
li:nth-child(3n) { color: blue; }
p:not(.highlight) { color: #333; }

Pseudo-elements

Los pseudo-elements (::double-colon) estilizan partes específicas de elementos. ::before y ::after insertan contenido generado (requiere propiedad content). ::first-letter y ::first-line estilizan porciones de texto. ::selection estiliza texto resaltado. ::placeholder estiliza placeholders de input. Nota: ::before/::after son inline por defecto — establece display:block para comportamiento block.

css
/* First line of a paragraph */
p::first-line { font-weight: bold; }

/* First letter (drop cap) */
p::first-letter { font-size: 3em; float: left; }

/* Insert content before/after */
.quote::before { content: "\201C"; }
.quote::after { content: "\201D"; }

/* Selection styling */
::selection { background: yellow; color: black; }

/* Placeholder styling */
input::placeholder { color: #999; }

Specificity y !important

La specificity determina qué regla aplica cuando los selectores conflictúan. Estilos inline > IDs > classes > types. Cuando la specificity empata, la última regla gana. !important sobrescribe todo pero rompe la cascada — evítalo. Usa DevTools para inspeccionar specificity. Prefiere selectores basados en class para mantenibilidad. Aumentar specificity repitiendo classes (.btn.btn) es un hack — evítalo.

css
/* Specificity hierarchy (low to high):
   1. Type selectors (p, div)         = 0,0,0,1
   2. Class selectors (.btn)          = 0,0,1,0
   3. ID selectors (#header)          = 0,1,0,0
   4. Inline styles (style="...")     = 1,0,0,0
   5. !important                      = overrides all
*/

/* ID beats class */
#nav .link { color: red; }   /* wins */
.nav .link { color: blue; }

/* Avoid !important unless necessary */
.critical { color: red !important; }
02

Box model

Margin y padding

Cada elemento es una caja con content, padding, border y margin. Padding está dentro del border (afecta background), margin está fuera (transparente). Shorthand: 1 valor = todos los lados, 2 valores = top/bottom left/right, 3 = top left/right bottom, 4 = top right bottom left (clockwise). margin: 0 auto centra elementos block con un width definido.

css
.box {
  /* TRBL: top right bottom left */
  margin: 10px 20px 10px 20px;

  /* Shorthand: top/bottom  left/right */
  margin: 10px 20px;

  /* All sides */
  padding: 15px;

  /* Individual sides */
  margin-top: 0;
  padding-left: 24px;

  /* Center horizontally (block) */
  margin: 0 auto;
}

Border y outline

border añade una línea alrededor del padding, afectando layout. outline dibuja fuera del border sin afectar layout — útil para indicadores de focus. border-radius redondea esquinas (valor único o por esquina: top-left top-right bottom-right bottom-left). outline-offset añade espacio entre border y outline. Siempre proporciona estilos de focus visibles para accesibilidad.

css
.box {
  /* Shorthand: width style color */
  border: 2px solid #333;

  /* Individual sides */
  border-bottom: 3px dashed red;
  border-radius: 8px;

  /* Outline (doesn't affect layout) */
  outline: 2px solid blue;
  outline-offset: 4px;
}

/* Remove default focus, add custom */
button:focus {
  outline: none;
  box-shadow: 0 0 0 3px rgba(0,123,255,0.5);
}

Box sizing

box-sizing: content-box (default) significa que width/height aplican solo al content — padding y border se añaden al tamaño total. box-sizing: border-box incluye padding y border en el width/height, haciendo el sizing predecible. Siempre establece border-box globalmente para layouts consistentes. Este es uno de los CSS resets más importantes.

css
/* Default: width = content only */
.content-box {
  box-sizing: content-box;
  width: 200px;
  padding: 20px;
  border: 5px solid;
  /* Total width: 200 + 40 + 10 = 250px */
}

/* Recommended: width includes padding+border */
.border-box {
  box-sizing: border-box;
  width: 200px;
  padding: 20px;
  border: 5px solid;
  /* Total width: 200px */
}

/* Apply globally */
*, *::before, *::after {
  box-sizing: border-box;
}

Box shadow

box-shadow añade efectos de sombra: offset-x offset-y blur-radius spread-radius color. Offsets positivos mueven la sombra right/down. inset crea una sombra interior. Múltiples sombras se layeran (primera = top). Usa rgba para sombras semi-transparentes que se mezclan naturalmente. Las sombras son geniales para profundidad pero el uso excesivo daña el rendimiento — prefiere sombras sutiles.

css
/* offset-x offset-y blur spread color */
.card {
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

/* Multiple shadows */
.button {
  box-shadow:
    0 1px 2px rgba(0,0,0,0.1),
    0 4px 8px rgba(0,0,0,0.05);
}

/* Inset shadow */
.input:focus {
  box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
}

/* No shadow */
.no-shadow { box-shadow: none; }

Display y visibility

display: none remueve el elemento del layout completamente (sin espacio). visibility: hidden lo oculta pero mantiene su espacio. opacity: 0 lo hace transparente pero aún interactivo. Los elementos block toman full width y rompen líneas. Los elementos inline fluyen con texto. inline-block combina flujo inline con dimensiones block (width, height). Usa display: none para toggle de contenido.

css
/* Display types */
div { display: block; }           /* full width, line break */
span { display: inline; }         /* content width, no break */
.inline-block { display: inline-block; } /* inline but block props */
.flex { display: flex; }          /* flex container */
.grid { display: grid; }          /* grid container */
.none { display: none; }          /* removed from layout */

/* Visibility (keeps space) */
.hidden { visibility: hidden; }

/* Opacity (keeps space, can interact) */
.fade { opacity: 0; }
03

Colores y unidades

Formatos de color

CSS soporta múltiples formatos de color. Hex (#rrggbb) es el más común. RGB/RGBA añade transparencia alpha. HSL (Hue 0-360, Saturation%, Lightness%) es intuitivo — cambia hue para desplazar color, lightness para shades. currentColor hereda la propiedad color del elemento. CSS moderno también soporta oklch() y color() para gamas más amplias. Usa rgba/hsla para transparencia.

css
/* Named colors */
.color { color: red; background: tomato; }

/* Hexadecimal */
.hex { color: #ff6600; }        /* 6-digit */
.hex-short { color: #f60; }     /* 3-digit shorthand */

/* RGB / RGBA */
.rgb { color: rgb(255, 102, 0); }
.rgba { background: rgba(0, 0, 0, 0.5); }

/* HSL / HSLA (hue, saturation, lightness) */
.hsl { color: hsl(20, 100%, 50%); }
.hsla { background: hsla(120, 100%, 50%, 0.3); }

/* Current color keyword */
.box { color: blue; border: 2px solid currentColor; }

Unidades de longitud

px es absoluto y predecible. em es relativo al font-size del padre (se compone al anidarse). rem es relativo al font-size del root (html) — consistente y preferido para accesibilidad. % es relativo al padre. vh/vw son relativos al viewport (100vh = altura completa). Usa rem para tamaños de fuente, % para layouts, y px para borders/detalles finos.

css
/* Absolute units */
.px { font-size: 16px; }     /* 1px = 1/96 inch */
.pt { font-size: 12pt; }     /* 1pt = 1/72 inch */

/* Relative to parent font-size */
.em { font-size: 1.5em; }    /* 1.5x parent */

/* Relative to root font-size (html) */
.rem { font-size: 1.2rem; }  /* 1.2x root (usually 16px) */

/* Relative to parent dimensions */
.pct { width: 50%; }         /* 50% of parent */

/* Viewport units */
.vh { height: 100vh; }       /* 100% of viewport height */
.vw { width: 50vw; }         /* 50% of viewport width */
.vmin { font-size: 2vmin; }  /* 2% of smaller dimension */

Funciones CSS

calc() realiza matemática con unidades mixtas (ej., 100% - 250px). min() elige el valor más pequeño (genial para max-widths responsive). max() elige el más grande. clamp(min, preferred, max) crea valores fluidos que escalan entre bounds — perfecto para tipografía responsive. var() referencia custom properties con un fallback opcional. Estas funciones hacen CSS dinámico sin media queries.

css
/* calc() - mathematical calculations */
.sidebar { width: calc(100% - 250px); }
.gap { margin: calc(1rem + 10px); }

/* min() / max() - choose smaller/larger */
.responsive { width: min(90%, 1200px); }
.min-width { width: max(300px, 50%); }

/* clamp() - fluid sizing with bounds */
.title { font-size: clamp(1.5rem, 4vw, 3rem); }

/* var() - use custom properties */
.btn { color: var(--primary, #007bff); }

Gradientes

Los gradientes crean transiciones suaves de color. linear-gradient(direction, color1, color2) va en una dirección (to right, 45deg). radial-gradient se expande desde un punto central. conic-gradient rota alrededor de un centro. Puedes añadir color stops con posiciones: linear-gradient(to right, red 0%, blue 50%, green 100%). Los gradientes son imágenes, no colores, y pueden usarse en cualquier lugar donde se acepte background-image.

css
/* Linear gradient */
.bg1 {
  background: linear-gradient(to right, #ff0000, #0000ff);
}

/* Diagonal with angle */
.bg2 {
  background: linear-gradient(45deg, #ff0000, #00ff00, #0000ff);
}

/* Radial gradient */
.bg3 {
  background: radial-gradient(circle, #ff0000, #0000ff);
}

/* Conic gradient */
.bg4 {
  background: conic-gradient(red, yellow, green, blue, red);
}

Filtros y blend modes

filter aplica efectos visuales: blur(), brightness(), contrast(), grayscale(), sepia(), hue-rotate(), invert(), opacity(), saturate(). Múltiples filtros se encadenan. mix-blend-mode mezcla un elemento con lo que está detrás (multiply, screen, overlay, etc.). backdrop-filter aplica filtros al área detrás de un elemento (genial para glassmorphism). Los filtros pueden impactar el rendimiento — úsalos con moderación.

css
/* Filters */
img.bw { filter: grayscale(100%); }
img.blur { filter: blur(5px); }
img.bright { filter: brightness(1.5); }
img.sepia { filter: sepia(0.8); }

/* Multiple filters */
img.vintage {
  filter: sepia(0.5) contrast(1.2) brightness(0.9);
}

/* Blend mode */
.overlay {
  background: rgba(255,0,0,0.5);
  mix-blend-mode: multiply;
}
04

Tipografía

Propiedades de fuente

font-family acepta una lista de fallback — el navegador usa el primero disponible. Siempre termina con una familia genérica (serif, sans-serif, monospace). font-weight va de 100 (thin) a 900 (black). line-height: 1.5 significa 1.5x el tamaño de fuente — 1.4-1.6 es ideal para legibilidad de body text. El shorthand font debe incluir size y family como mínimo.

css
body {
  /* Font shorthand: style weight size/line-height family */
  font: italic bold 16px/1.5 Arial, sans-serif;

  /* Individual properties */
  font-family: "Helvetica Neue", Arial, sans-serif;
  font-size: 16px;
  font-weight: 400;  /* 100-900, normal=400, bold=700 */
  font-style: italic; /* normal, italic, oblique */
  line-height: 1.5;
}

Estilizado de texto

text-align controla alineación horizontal. text-decoration añade líneas (underline, overline, line-through). text-transform cambia casing. letter-spacing (tracking) y word-spacing ajustan espaciado entre caracteres y palabras. white-space: nowrap previene wrapping de texto. Para alineación vertical, usa vertical-align (inline) o alineación de flexbox/grid (block).

css
p {
  text-align: justify;        /* left, right, center, justify */
  text-decoration: underline; /* none, overline, line-through */
  text-transform: capitalize; /* uppercase, lowercase, capitalize */
  text-indent: 2em;           /* indent first line */
  letter-spacing: 0.05em;     /* tracking */
  word-spacing: 0.1em;
  white-space: nowrap;        /* prevent wrapping */
}

a {
  text-decoration: none;
  color: #0066cc;
}

Text overflow y wrapping

Para truncar texto con elipsis, necesitas tres propiedades: white-space: nowrap, overflow: hidden, y text-overflow: ellipsis. overflow-wrap: break-word rompe palabras largas para prevenir overflow. word-break: break-all rompe en cualquier carácter. text-wrap: balance (más nuevo) balancea longitudes de línea para encabezados. Siempre establece un max-width o width para que la truncación funcione.

css
/* Truncate with ellipsis */
.truncate {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 200px;
}

/* Break long words */
.break-word {
  overflow-wrap: break-word;
  word-break: break-all;
}

/* Balance text wrapping (newer) */
h1 {
  text-wrap: balance;
}

Web fonts (@font-face)

@font-face carga fuentes personalizadas. woff2 es el formato moderno (mejor compresión). Proporciona woff como fallback. font-display: swap muestra texto de fallback inmediatamente, luego swap cuando la fuente carga (previene texto invisible). Siempre declara una familia de fuente fallback. Google Fonts proporciona fuentes hosted vía @import o <link>. Preloada fuentes críticas para rendimiento.

css
@font-face {
  font-family: 'MyCustomFont';
  src: url('fonts/custom.woff2') format('woff2'),
       url('fonts/custom.woff') format('woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

body {
  font-family: 'MyCustomFont', Arial, sans-serif;
}

/* Google Fonts import */
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');

Estilizado de listas y links

list-style: none remueve viñetas default para navegación personalizada o listas estilizadas. list-style-type cambia la viñeta (disc, circle, square, decimal, none). list-style-position: inside pone viñetas dentro del área de contenido. Para links, el orden LVHA importa: :link, :visited, :hover, :active. Esto asegura la cascada correcta de estados de link.

css
/* Reset list styles */
ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

/* Custom bullets */
ul.custom {
  list-style-type: square;
  list-style-position: inside;
}

/* Image bullets */
ul.image {
  list-style-image: url('bullet.png');
}

/* Link states (order matters!) */
a:link { color: blue; }
a:visited { color: purple; }
a:hover { color: red; text-decoration: underline; }
a:active { color: orange; }
05

Layout con Flexbox

Flex container

display: flex crea un flex container. justify-content alinea items a lo largo del main axis (horizontal en row). align-items alinea a lo largo del cross axis (vertical). flex-direction cambia el main axis. flex-wrap permite que los items hagan wrap a nuevas líneas. gap establece espaciado entre items (reemplaza hacks de margin). Flexbox es ideal para layouts 1D (filas O columnas).

css
.container {
  display: flex;

  /* Main axis alignment */
  justify-content: space-between;
  /* flex-start, center, flex-end, space-around, space-evenly */

  /* Cross axis alignment */
  align-items: center;
  /* flex-start, center, flex-end, stretch, baseline */

  /* Direction */
  flex-direction: row;
  /* row, row-reverse, column, column-reverse */

  /* Wrapping */
  flex-wrap: wrap;
  /* nowrap, wrap, wrap-reverse */

  /* Gap between items */
  gap: 16px;
}

Flex items

flex-grow controla cómo los items comparten espacio extra (0 = no crecer, 1 = crecer igualmente). flex-shrink controla cómo los items se encogen cuando el espacio es limitado. flex-basis establece el tamaño inicial. El shorthand flex: 1 significa flex-grow:1, flex-shrink:1, flex-basis:0%. align-self sobrescribe el align-items del container para un item. order reordena items visualmente sin cambiar el DOM.

css
.item {
  /* Grow: how much space to take (0 = don't grow) */
  flex-grow: 1;

  /* Shrink: how much to shrink when space is tight */
  flex-shrink: 0;

  /* Basis: initial size before growing/shrinking */
  flex-basis: 200px;

  /* Shorthand: flex: grow shrink basis */
  flex: 1 0 200px;

  /* Override align-items for this item */
  align-self: flex-end;

  /* Order (default 0, lower = earlier) */
  order: -1;
}

Patrones comunes de Flexbox

Flexbox hace el centrado trivial: justify-content: center + align-items: center. Para un sticky footer, haz body un flex column con min-height: 100vh y deja que el contenido main flex: 1. Para navbars, justify-content: space-between empuja el primer y último item a extremos opuestos. Estos patrones resuelven problemas de layout comunes que eran difíciles antes de flexbox.

css
/* Center anything */
.center {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
}

/* Sticky footer */
body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
.main { flex: 1; }

/* Navbar: logo left, links right */
.nav {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

Flex direction y wrap

flex-direction: column crea layouts verticales (útil para stacking). flex-wrap: wrap permite que los items fluyan a nuevas líneas cuando se quedan sin espacio. flex: 1 1 300px significa que los items comienzan en 300px, crecen para llenar espacio, y se encojen según sea necesario. align-content controla espaciado entre líneas wraped (solo funciona con wrap). Este patrón crea grids de tarjetas responsive.

css
/* Horizontal (default) */
.row { display: flex; flex-direction: row; }

/* Vertical */
.column { display: flex; flex-direction: column; }

/* Reverse */
.row-rev { display: flex; flex-direction: row-reverse; }

/* Wrap to new lines */
.cards {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
}
.card { flex: 1 1 300px; } /* grow, shrink, basis */

/* Align wrapped lines */
.cards { align-content: space-between; }

Alineación de Flexbox en profundidad

En flex-direction: column, el main axis es vertical y el cross axis es horizontal — justify-content y align-items intercambian roles. margin: auto en un flex item absorbe todo el espacio disponible, centrándolo perfectamente. Esto es una alternativa a justify-content/align-items. Entender qué axis es 'main' vs 'cross' es clave para dominar la alineación de flexbox.

css
.container {
  display: flex;
  flex-direction: column;

  /* Main axis = vertical (column) */
  justify-content: flex-start;
  /* push items to top, center, bottom, or spread */

  /* Cross axis = horizontal (column) */
  align-items: stretch;
  /* stretch items to full width */
}

/* Center a single item perfectly */
.wrapper {
  display: flex;
  min-height: 100vh;
}
.centered {
  margin: auto; /* centers in both axes */
}
06

Layout con CSS Grid

Grid container

display: grid crea un layout 2D. grid-template-columns define tamaños de columna: unidades fr distribuyen espacio libre proporcionalmente. repeat(3, 1fr) crea 3 columnas iguales. repeat(auto-fit, minmax(250px, 1fr)) crea un grid responsive que auto-ajusta el conteo de columnas — este es el 'holy grail' de los grids responsive. gap reemplaza margins para espaciado.

css
.grid {
  display: grid;

  /* Define columns */
  grid-template-columns: 200px 1fr 200px;
  /* fixed, flexible, fixed */

  /* Or repeat */
  grid-template-columns: repeat(3, 1fr);
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));

  /* Define rows */
  grid-template-rows: auto 1fr auto;

  /* Gap (formerly grid-gap) */
  gap: 20px;
  row-gap: 10px;
  column-gap: 20px;
}

Placement de grid items

Los grid items pueden abarcar múltiples celdas con grid-column: span N. También puedes usar números de línea: grid-column: 1 / 3 significa comenzar en línea 1, terminar en línea 3. Las líneas se numeran desde 1 (left/top) hasta N+1 (right/bottom). grid-area asigna items a áreas con nombre. Grid permite placement 2D preciso — los items pueden abarcar tanto filas como columnas simultáneamente.

css
.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 10px;
}

/* Span multiple columns */
.featured {
  grid-column: span 2;
}

/* Explicit placement */
.sidebar {
  grid-column: 1 / 3;  /* start at line 1, end at line 3 */
  grid-row: 1 / 4;
}

/* Named areas */
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }

Grid template areas

grid-template-areas crea un mapa visual del layout usando strings con nombre. Cada string representa una fila; cada nombre representa una columna. Los items se colocan estableciendo grid-area al nombre coincidente. Esta es la forma más legible de crear layouts complejos. Usa '.' para celdas vacías. El mismo nombre puede abarcar múltiples celdas. Cambia areas en media queries para layouts responsive.

css
.layout {
  display: grid;
  grid-template-columns: 200px 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
  gap: 10px;
  min-height: 100vh;
}

.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }

Alineación de grid

Grid tiene 6 propiedades de alineación. align-items/justify-items alinean items dentro de sus celdas grid. align-content/justify-content alinean el track de grid entero dentro del container (solo visible si el grid es más pequeño). align-self/justify-self sobrescriben por item. place-items: center es shorthand para align-items + justify-items. place-content: center combina ambas alineaciones de contenido.

css
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);

  /* Align items within their cell (cross axis) */
  align-items: center;  /* start, center, end, stretch */

  /* Justify items within their cell (main axis) */
  justify-items: center;

  /* Align the entire grid (if smaller than container) */
  align-content: center;
  justify-content: center;
}

/* Override for individual item */
.item {
  align-self: end;
  justify-self: start;
}

Grid responsive (auto-fit)

repeat(auto-fit, minmax(300px, 1fr)) crea un grid que automáticamente ajusta el conteo de columnas basándose en el espacio disponible — los items son al menos 300px y crecen para llenar. Esto elimina media queries para layouts de tarjetas. auto-fill mantiene columnas vacías (los items se quedan a la izquierda), auto-fit las colapsa (los items se estiran). Usa auto-fit para la mayoría de casos. Combina con max-width en el container para resultados óptimos.

css
/* Auto-responsive grid - no media queries needed! */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 20px;
}

/* Fixed number of columns per breakpoint */
@media (min-width: 768px) {
  .cards {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 1024px) {
  .cards {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* Auto-fill vs auto-fit:
   auto-fit collapses empty tracks
   auto-fill keeps empty tracks */
07

Posicionamiento

Tipos de position

static es el default (flujo normal del documento). relative offset desde su posición normal sin afectar otros elementos (crea contexto de posicionamiento). absolute remueve del flujo y posiciona relativo al ancestro posicionado más cercano. fixed posiciona relativo al viewport (se queda en scroll). sticky togglea entre relative y fixed — scrollea normalmente luego se pega en el umbral.

css
/* Static (default) - normal flow */
.static { position: static; }

/* Relative - offset from normal position */
.relative {
  position: relative;
  top: 10px;
  left: 20px;
}

/* Absolute - positioned relative to nearest positioned ancestor */
.absolute {
  position: absolute;
  top: 0;
  right: 0;
}

/* Fixed - positioned relative to viewport */
.fixed {
  position: fixed;
  bottom: 0;
  width: 100%;
}

/* Sticky - scrolls then sticks */
.sticky {
  position: sticky;
  top: 0;
}

Posicionamiento absolute

el posicionamiento absolute requiere un ancestro posicionado (relative, absolute, fixed o sticky). Sin uno, se posiciona relativo al viewport. Las propiedades top/right/bottom/left offset desde el borde correspondiente. Para centrar un elemento absolutamente posicionado, usa top:50%, left:50% con transform: translate(-50%,-50%). El posicionamiento absolute remueve elementos del flujo normal.

css
/* Parent must be positioned */
.container {
  position: relative;
  width: 400px;
  height: 300px;
}

/* Position relative to container */
.badge {
  position: absolute;
  top: 10px;
  right: 10px;
}

/* Center absolutely positioned element */
.modal {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

Posicionamiento sticky

position: sticky es un híbrido — los elementos scrollean normalmente hasta que alcanzan un umbral (ej., top: 0), luego se pegan. Genial para sticky headers, sidebars y headers de tabla. El elemento se pega dentro de su container padre (deja de pegarse cuando el padre scrollea pasado). Sticky no funciona si cualquier ancestro tiene overflow: hidden/auto/scroll. Siempre establece un z-index para prevenir problemas de overlap.

css
/* Sticky header */
.header {
  position: sticky;
  top: 0;
  background: white;
  z-index: 100;
  padding: 1rem;
}

/* Sticky sidebar */
.sidebar {
  position: sticky;
  top: 80px; /* offset for fixed header */
  height: calc(100vh - 80px);
  overflow-y: auto;
}

/* Note: sticky doesn't work if a parent has
   overflow: hidden or overflow: auto */

Z-index y stacking context

z-index controla el orden de apilamiento de elementos posicionados (mayor = encima). Solo funciona en elementos posicionados (no static). Un stacking context lo crean elementos posicionados con z-index, opacity < 1, transform o filter. Dentro de un stacking context, los valores z-index child son relativos a ese contexto — un child nunca puede aparecer encima de un sibling de su padre con mayor z-index. Esta es una fuente común de confusión.

css
/* Higher z-index = closer to viewer */
.modal-overlay {
  position: fixed;
  z-index: 9999;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(0,0,0,0.5);
}

.modal {
  position: fixed;
  z-index: 10000; /* above overlay */
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
}

/* Stacking context: z-index only works
   within the same stacking context */
.parent { position: relative; z-index: 1; }
.child { position: absolute; z-index: 9999; }
/* child can't exceed parent's stacking level */

Float y clear (legacy)

float era el método de layout primario antes de flexbox/grid. Remueve elementos del flujo normal y los empuja left/right, con texto envolviéndose alrededor. clear previene que elementos aparezcan al lado de floats. El hack clearfix fuerza a un container a envolver floated children. Hoy, usa float solo para su propósito intencionado: envolver texto alrededor de imágenes. Para layouts, usa flexbox o grid.

css
/* Float text around an image */
img.float-left {
  float: left;
  margin: 0 1rem 1rem 0;
}

/* Clear floats */
.clear { clear: both; }
.clear-left { clear: left; }
.clear-right { clear: right; }

/* Clearfix hack for containers */
.clearfix::after {
  content: "";
  display: table;
  clear: both;
}

/* Modern alternative: use flexbox or grid */
/* Float is mainly for text wrapping now */
08

Diseño responsive

Media queries

Las media queries aplican estilos basándose en condiciones. min-width (mobile-first) es preferido — los estilos base apuntan a móvil, luego se mejoran para pantallas más grandes. max-width (desktop-first) es lo inverso. Otras condiciones: prefers-color-scheme (dark/light), print, orientation (portrait/landscape), prefers-reduced-motion. Siempre establece una meta tag viewport en HTML para que las media queries funcionen en móvil.

css
/* Mobile-first: base styles first, then min-width */

/* Base (mobile) */
.container { flex-direction: column; }

/* Tablet and up */
@media (min-width: 768px) {
  .container { flex-direction: row; }
}

/* Desktop and up */
@media (min-width: 1024px) {
  .container { max-width: 1200px; margin: 0 auto; }
}

/* Print styles */
@media print {
  .no-print { display: none; }
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
  body { background: #1a1a1a; color: #eee; }
}

Tipografía fluida

clamp(min, preferred, max) crea tipografía fluida que escala suavemente con el tamaño del viewport — sin media queries. El valor preferred (a menudo basado en vw) escala entre bounds min y max. min() elige el valor más pequeño (genial para max-widths responsive). Este enfoque reduce el número de media queries necesitadas y crea escalado más suave. Siempre establece min/max razonables para legibilidad.

css
/* clamp() for fluid font sizes */
h1 {
  font-size: clamp(1.5rem, 5vw, 3rem);
  /* min: 1.5rem, preferred: 5vw, max: 3rem */
}

/* Fluid spacing */
.container {
  padding: clamp(1rem, 3vw, 3rem);
}

/* Fluid width */
.content {
  width: min(90%, 1200px);
  margin: 0 auto;
}

/* Viewport-based sizing */
.hero {
  height: 100vh;
  font-size: clamp(2rem, 8vw, 5rem);
}

Container queries

Las container queries (CSS Containment Module Level 3) te permiten estilizar elementos basándose en el tamaño del container padre, no del viewport. container-type: inline-size declara un container. @container aplica estilos cuando el container cumple la condición. Esto es más modular que las media queries — los componentes se adaptan a su container, no a la pantalla. Genial para componentes reutilizables en diferentes contextos de layout.

css
/* Define a container */
.card-container {
  container-type: inline-size;
  /* or: container: sidebar / inline-size; */
}

/* Query the container's size */
@container (min-width: 400px) {
  .card {
    display: flex;
    flex-direction: row;
  }
}

@container (max-width: 399px) {
  .card {
    display: flex;
    flex-direction: column;
  }
}

Imágenes y media responsive

max-width: 100% + height: auto hace las imágenes responsive (escalan hacia abajo pero nunca hacia arriba). El hack padding-bottom crea embeds de video 16:9 responsive. La propiedad moderna aspect-ratio es más limpia — establece el ratio y el navegador calcula la altura. Siempre establece atributos width y height en imágenes para prevenir layout shift (CLS). Usa object-fit: cover para recortar imágenes dentro de dimensiones fijas.

css
/* Responsive image */
img {
  max-width: 100%;
  height: auto;
}

/* Responsive video (16:9 aspect ratio) */
.video-wrapper {
  position: relative;
  padding-bottom: 56.25%; /* 9/16 */
  height: 0;
}
.video-wrapper iframe {
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  height: 100%;
}

/* Aspect ratio property */
.box {
  aspect-ratio: 16 / 9;
  width: 100%;
}

Estrategia mobile-first

Mobile-first significa escribir estilos móviles como base, luego mejorar progresivamente para pantallas más grandes con media queries min-width. Esto asegura que los usuarios móviles (a menudo en conexiones más lentas) descarguen menos CSS. Breakpoints comunes: 768px (tablet), 1024px (desktop), 1440px (large). Usa unidades relativas (rem, %, vw) en lugar de px fijos para mejor escalabilidad. Testea en dispositivos reales, no solo DevTools del navegador.

css
/* 1. Start with mobile base styles */
.nav { display: none; } /* hidden on mobile */
.menu-toggle { display: block; }

/* 2. Enhance for larger screens */
@media (min-width: 768px) {
  .nav { display: flex; }
  .menu-toggle { display: none; }
}

/* 3. Common breakpoints */
/* Mobile: < 768px */
/* Tablet: 768px - 1023px */
/* Desktop: 1024px - 1439px */
/* Large: >= 1440px */

/* 4. Use relative units */
body { font-size: 1rem; } /* not 16px */
09

Transiciones y animaciones

Transiciones

transition crea cambios suaves entre valores de propiedad. Sintaxis: transition: property duration timing-function delay. Timing functions: ease (default), linear, ease-in, ease-out, ease-in-out, cubic-bezier(). Solo las propiedades animables transicionan. Evita transition: all (rendimiento). Las transiciones se disparan en cambios de pseudo-class (:hover, :focus) o cambios de class vía JavaScript.

css
.button {
  background: #007bff;
  transition: background 0.3s ease, transform 0.2s ease;
}

.button:hover {
  background: #0056b3;
  transform: translateY(-2px);
}

/* Transition all properties (use sparingly) */
.card {
  transition: all 0.3s ease;
}

/* Shorthand: property duration timing-function delay */
.box {
  transition: opacity 0.5s ease-in 0.2s;
}

Transform

transform modifica un elemento sin afectar layout (a diferencia de margin/position). translate lo mueve (translateX, translateY, o translate(x,y)). scale lo redimensiona (1 = 100%). rotate lo gira (deg, rad, turn). skew lo distorsiona. Múltiples transforms se encadenan en orden. Los transforms son GPU-accelerated — geniales para rendimiento. Siempre párealo con transition para efectos suaves. transform-origin cambia el punto pivote.

css
.box {
  transition: transform 0.3s ease;
}

/* Translate (move) */
.box:hover { transform: translate(10px, 20px); }
.box:hover { transform: translateX(50%); }

/* Scale (resize) */
.box:hover { transform: scale(1.1); }

/* Rotate */
.box:hover { transform: rotate(45deg); }

/* Skew */
.box:hover { transform: skew(10deg, 5deg); }

/* Combine transforms */
.box:hover {
  transform: translate(10px, 0) scale(1.1) rotate(5deg);
}

Animaciones keyframe

@keyframes define pasos de animación. 0%/from es el inicio, 100%/to es el final. Puedes añadir pasos intermedios (25%, 50%, 75%). El shorthand animation: name duration timing-function delay iteration-count direction fill-mode. infinite loopea para siempre. direction: alternate revierte en iteraciones pares. fill-mode: forwards mantiene el estado final. Las animaciones se ejecutan automáticamente, a diferencia de las transiciones.

css
@keyframes bounce {
  0%   { transform: translateY(0); }
  50%  { transform: translateY(-20px); }
  100% { transform: translateY(0); }
}

@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.ball {
  animation: bounce 0.5s ease infinite;
}

.element {
  animation: fadeIn 1s ease forwards;
  /* name duration timing delay iteration direction fill-mode */
}

Propiedades de animación

Las propiedades de animación individuales dan control fino. animation-iteration-count puede ser un número o infinite. animation-direction: alternate reproduce hacia adelante luego hacia atrás (genial para efectos ping-pong). animation-fill-mode: forwards mantiene el estado final después de terminar. animation-play-state: paused congela la animación — útil para hover-to-pause. Múltiples animaciones se separan con coma: animation: spin 1s, fade 2s;.

css
.spinner {
  animation-name: spin;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-delay: 0s;
  animation-iteration-count: infinite;
  animation-direction: normal; /* alternate, reverse */
  animation-fill-mode: none;   /* forwards, backwards, both */
  animation-play-state: running; /* paused */
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* Pause on hover */
.spinner:hover {
  animation-play-state: paused;
}

Rendimiento y reduced motion

Para animaciones suaves de 60fps, solo anima transform y opacity — son GPU-accelerated y no disparan layout (reflow). Animar width, margin, top, etc. fuerza al navegador a recalcular layout para cada frame, causando jank. Siempre respeta prefers-reduced-motion: reduce — algunos usuarios experimentan motion sickness o tienen desórdenes vestibulares. Proporciona transiciones instantáneas o deshabilita animaciones para estos usuarios.

css
/* Only animate transform and opacity for performance */
.good {
  transition: transform 0.3s, opacity 0.3s;
}

/* Avoid animating these (cause reflow/repaint): */
/* margin, padding, width, height, top, left */

/* Respect user's motion preference */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}
10

Variables y funciones avanzadas

Custom properties (variables)

Las custom properties (variables CSS) almacenan valores reutilizables. Defínelas en :root para acceso global. Referencia con var(--name). Se cascaden y pueden sobrescribirse en cualquier scope — perfectas para theming (dark/light mode). A diferencia de las variables de preprocesador, son dinámicas (cambian en runtime) y pueden manipularse con JavaScript. Siempre proporciona fallbacks: var(--primary, #007bff). Esta es la forma moderna de gestionar design tokens.

css
:root {
  /* Colors */
  --primary: #007bff;
  --primary-dark: #0056b3;
  --text: #333;
  --bg: #fff;

  /* Spacing scale */
  --space-sm: 0.5rem;
  --space-md: 1rem;
  --space-lg: 2rem;

  /* Font sizes */
  --font-base: 16px;
  --font-lg: 1.5rem;
}

.button {
  background: var(--primary);
  padding: var(--space-sm) var(--space-md);
  font-size: var(--font-base);
  color: white;
}

/* Override in a scope */
.dark-theme {
  --bg: #1a1a1a;
  --text: #eee;
}

calc() y funciones matemáticas

calc() realiza matemática con cualquier unidad, incluyendo mezclar px, %, em, rem, vw. Es esencial para layouts responsive. Puedes anidar calc() pero es innecesario — calc(100% - 2rem) / 3 funciona sin calc interno. min() devuelve el valor más pequeño, max() el más grande, clamp() acota un valor entre min y max. Estas funciones hacen CSS dinámico sin JavaScript o media queries.

css
/* Mixed unit calculations */
.sidebar {
  width: calc(100% - 250px);
}

/* With variables */
.box {
  padding: calc(var(--space-md) + 10px);
}

/* Nested calc */
.responsive {
  width: calc(calc(100% - 2rem) / 3);
}

/* min(), max(), clamp() */
.fluid {
  width: min(90%, 1200px);
  font-size: clamp(1rem, 2vw + 1rem, 2rem);
  height: max(200px, 50vh);
}

Object fit y position

object-fit controla cómo imágenes/video llenan su container (como background-size para <img>). cover llena el container y recorta overflow (genial para avatares/thumbnails). contain encaja enteramente sin recortar (puede dejar espacio vacío). fill estira (distorsiona). object-position ajusta alineación (como background-position). Esto reemplaza la necesidad de hacks de background-image para imágenes responsive.

css
/* Cover: fill container, crop overflow */
.avatar {
  width: 100px;
  height: 100px;
  object-fit: cover;
  border-radius: 50%;
}

/* Contain: fit entirely, may letterbox */
.preview {
  width: 200px;
  height: 200px;
  object-fit: contain;
  background: #eee;
}

/* Position the image within the frame */
.image {
  object-fit: cover;
  object-position: top center;
}

Propiedades de background

background es un shorthand potente. background-size: cover llena el container (recortando); contain encaja sin recortar. background-attachment: fixed crea un efecto parallax (la imagen se queda quieta durante scroll). Múltiples backgrounds se layeran con comas (primero = top layer). Usa linear-gradient como background para overlays: background: linear-gradient(rgba(0,0,0,0.5), transparent), url('image.jpg').

css
.hero {
  /* Shorthand */
  background: url('hero.jpg') center/cover no-repeat fixed;

  /* Individual properties */
  background-image: url('hero.jpg');
  background-size: cover;     /* cover, contain, or px/% */
  background-position: center; /* top, bottom, left, right, or x y */
  background-repeat: no-repeat;
  background-attachment: fixed; /* scroll, fixed, local */

  /* Multiple backgrounds */
  background:
    url('overlay.png') no-repeat center,
    url('hero.jpg') center/cover;
}

CSS nesting (moderno)

CSS moderno soporta nesting nativo (como Sass/Less). Usa & para referenciar el selector padre. El nesting mejora la legibilidad y reduce repetición. Las media queries pueden anidarse directamente dentro de reglas. El soporte del navegador es bueno en navegadores modernos (2023+). El & se requiere para pseudo-classes (&:hover) y combinators (& > .child). Evita nesting profundo (3+ niveles) ya que aumenta la specificity y reduce mantenibilidad.

css
/* Native CSS nesting (no preprocessor needed) */
.card {
  background: white;
  border-radius: 8px;

  & .title {
    font-size: 1.5rem;
    font-weight: bold;
  }

  & .body {
    padding: 1rem;

    & p {
      line-height: 1.6;
    }
  }

  &:hover {
    box-shadow: 0 4px 12px rgba(0,0,0,0.1);
  }

  @media (max-width: 768px) {
    padding: 0.5rem;
  }
}
11

CSS custom properties (variables) en profundidad

Definición y uso de variables

Las custom properties de CSS (variables) se definen con --name y se usan con var(). Define variables globales en :root para valores de tema. A diferencia de las variables de Sass, las variables CSS son live — cambiarlas actualiza todos los usos instantáneamente. Están scoped al elemento en el que se definen y heredadas por descendientes. var() acepta un valor fallback como segundo argumento, y los fallbacks pueden encadenarse. Esto las hace mucho más potentes que las variables de preprocesador para theming dinámico.

css
:root {
  --primary: #3498db;
  --spacing: 16px;
  --radius: 8px;
  --max-width: 1200px;
}

.button {
  background: var(--primary);
  padding: var(--spacing);
  border-radius: var(--radius);
  max-width: var(--max-width);
}

/* Fallback values */
color: var(--text-color, #333);
/* Chained fallbacks */
color: var(--text, var(--default, black));

Theming dinámico con JavaScript

Las variables CSS habilitan theming en runtime que los preprocesadores no pueden hacer. Establece data-theme en <html> y sobrescribe variables por tema. JavaScript puede leer (getComputedStyle) y establecer (style.setProperty) variables en runtime, habilitando color pickers dinámicos, preferencias de usuario y previews live. Este es el enfoque estándar para dark mode, personalización de marca y temas seleccionables por el usuario. Las variables se cascaden y heredan, así que sobrescribir en un elemento child solo afecta ese subtree.

css
/* Define theme variables */
:root {
  --bg: #ffffff;
  --text: #333333;
}

[data-theme="dark"] {
  --bg: #1a1a1a;
  --text: #e0e0e0;
}

body {
  background: var(--bg);
  color: var(--text);
}

/* Toggle theme with JS */
<script>
document.documentElement.setAttribute(
  "data-theme",
  isDark ? "dark" : "light"
);

/* Or set a single variable directly */
document.documentElement.style
  .setProperty("--primary", "#e74c3c");
</script>

Variables scoped y herencia

Las variables CSS heredan como otras propiedades. Una variable definida en :root está disponible en todas partes; una definida en .card solo afecta .card y sus descendientes. Este scoping habilita personalización a nivel de componente. Puedes sobrescribir variables en media queries para crear valores de variables responsive — la variable misma no puede usarse en condiciones @media, pero puedes cambiar su valor dentro de un bloque media query. Este patrón es potente para diseño responsive sin repetir declaraciones de propiedad.

css
:root {
  --padding: 20px;
}

.card {
  --padding: 12px; /* overrides only for .card subtree */
  padding: var(--padding);
}

.card .inner {
  padding: var(--padding); /* inherits 12px from .card */
}

/* Variables in media queries don't work, but... */
.sidebar {
  --width: 250px;
  width: var(--width);
}
@media (max-width: 768px) {
  .sidebar { --width: 100%; }
}

Variables con calc()

Combinar variables CSS con calc() crea design systems potentes. Define un tamaño base y factor de escala, luego computa tamaños derivados. Esto habilita escalas tipográficas y sistemas de espaciado consistentes. calc() funciona con unidades mixtas (px, %, em, vw) y variables. Incluso puedes usar variables sin fallbacks (var(--nav-h, 60px) proporciona un default). Este enfoque es la base de los design systems modernos — cambia una variable y la escala entera se ajusta proporcionalmente.

css
:root {
  --base-size: 16px;
  --scale: 1.25;
}

h1 { font-size: calc(var(--base-size) * var(--scale) * 2); }
h2 { font-size: calc(var(--base-size) * var(--scale) * 1.5); }
p  { font-size: var(--base-size); }

/* Spacing scale */
:root {
  --space: 8px;
}
.margin { margin: calc(var(--space) * 2); }
.padding { padding: calc(var(--space) * 3); }

/* Mixed units */
.header { height: calc(var(--nav-h, 60px) + var(--content-h)); }

Registro de custom properties (@property)

@property registra custom properties con un tipo (syntax), valor inicial y flag de herencia. Esto habilita que las variables CSS sean animadas y transicionadas — sin @property, las variables se tratan como strings y no pueden interpolarse. syntax acepta tipos como <angle>, <color>, <length>, <number>, <percentage> o '*'. Esto desbloquea animaciones suaves de gradientes, transforms y colores impulsadas por variables. El soporte del navegador es bueno en Chromium y Safari, con Firefox añadiendo soporte. Un avance mayor para las capacidades de animación CSS.

css
/* Type-safe custom properties */
@property --angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

@property --color {
  syntax: "<color>";
  initial-value: #3498db;
  inherits: false;
}

/* Now animatable! */
@keyframes spin {
  to { --angle: 360deg; }
}

.loader {
  --angle: 0deg;
  animation: spin 1s linear infinite;
  background: conic-gradient(
    from var(--angle),
    var(--color), transparent
  );
}
12

Transforms 3D

Funciones de transform 3D

Los transforms 3D añaden profundidad a los elementos. perspective() como función de transform aplica a un solo elemento; la propiedad perspective en un padre aplica a todos los children uniformemente. rotateX/rotateY/rotateZ rotan alrededor de ejes; translateZ mueve a lo largo del eje Z (hacia/lejos del viewer). Valores de perspective más bajos crean efectos 3D más dramáticos (punto de vista más cercano). Siempre establece perspective en el container padre para espacio 3D consistente a través de múltiples children transformados. Combina múltiples transforms para posicionamiento 3D complejo.

css
.card {
  transform: perspective(1000px) rotateY(45deg) rotateX(15deg);
}

/* Using the perspective property instead */
.container {
  perspective: 1000px;
}
.container .card {
  transform: rotateY(45deg) rotateX(15deg);
}

/* translateZ moves toward/away from viewer */
.layer {
  transform: translateZ(100px);
}

/* scale3d for 3D scaling */
.box {
  transform: scale3d(1.5, 1.5, 1.5);
}

transform-style y backface-visibility

transform-style: preserve-3d mantiene el espacio 3D para elementos child (flat es el default, que aplana children). backface-visibility: hidden oculta el lado posterior de un elemento cuando se rota — esencial para efectos de flip card. El flip card clásico usa dos caras absolutamente posicionadas: front mira hacia adelante, back está pre-rotado 180deg. En hover, el padre rota 180deg, intercambiando qué cara es visible. Este es uno de los patrones 3D CSS más populares.

css
.container {
  perspective: 1000px;
}

.flip-card {
  transform-style: preserve-3d;
  transition: transform 0.6s;
  position: relative;
}

.flip-card:hover {
  transform: rotateY(180deg);
}

.flip-card .front,
.flip-card .back {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
}

.flip-card .back {
  transform: rotateY(180deg);
}

Cubo 3D

Un cubo 3D se construye con 6 caras, cada una posicionada con rotate + translateZ. translateZ(100px) empuja la cara la mitad del ancho del cubo hacia afuera. Cada cara se pre-rota para mirar en su dirección antes de ser empujada. transform-style: preserve-3d en el container del cubo es esencial — sin él, las caras se aplanan. La animación rota el cubo en ambos ejes X e Y. Esto demuestra el poder completo de los transforms 3D CSS. Ajusta translateZ para coincidir con la mitad del tamaño de tu cubo para geometría correcta.

css
.scene {
  perspective: 800px;
  width: 200px; height: 200px;
}
.cube {
  position: relative;
  width: 100%; height: 100%;
  transform-style: preserve-3d;
  animation: rotate 8s infinite linear;
}
@keyframes rotate {
  from { transform: rotateX(0) rotateY(0); }
  to   { transform: rotateX(360deg) rotateY(360deg); }
}
.face {
  position: absolute;
  width: 200px; height: 200px;
  opacity: 0.85;
}
.front  { transform: translateZ(100px); background: red; }
.back   { transform: rotateY(180deg) translateZ(100px); background: blue; }
.right  { transform: rotateY(90deg) translateZ(100px); background: green; }
.left   { transform: rotateY(-90deg) translateZ(100px); background: yellow; }
.top    { transform: rotateX(90deg) translateZ(100px); background: purple; }
.bottom { transform: rotateX(-90deg) translateZ(100px); background: orange; }

Efecto de tilt de tarjeta 3D

El efecto de tilt de tarjeta rastrea la posición del mouse y rota la tarjeta en 3D basándose en el offset del cursor desde el centro. La matemática divide el offset por un factor (10) para limitar el ángulo de rotación. En mouseleave, la tarjeta se resetea a flat. La transición crea movimiento suave. Añadir un overlay de glare/shine con un radial gradient que sigue al mouse mejora el efecto. Este es un patrón de UI interactivo popular para tarjetas de producto y hero sections. La perspective en el string de transform asegura el rendering 3D incluso sin una propiedad perspective padre.

css
.tilt-card {
  transition: transform 0.1s ease-out;
  transform-style: preserve-3d;
}

/* JavaScript-driven tilt */
<script>
const card = document.querySelector(".tilt-card");
card.addEventListener("mousemove", (e) => {
  const rect = card.getBoundingClientRect();
  const x = e.clientX - rect.left;
  const y = e.clientY - rect.top;
  const centerX = rect.width / 2;
  const centerY = rect.height / 2;
  const rotateX = (y - centerY) / 10;
  const rotateY = (centerX - x) / 10;
  card.style.transform =
    `perspective(1000px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`;
});
card.addEventListener("mouseleave", () => {
  card.style.transform = "perspective(1000px) rotateX(0) rotateY(0)";
});
</script>

perspective-origin y espaciado 3D

perspective-origin establece la posición del punto de fuga (como mover tu cabeza). El default es 50% 50% (centro). Moverlo crea ángulos de visualización dinámicos. Para efectos parallax, elementos en diferentes valores translateZ se mueven a diferentes velocidades cuando el container scrollea o rota. Los elementos más atrás (translateZ negativo) necesitan scale() para compensar el shrinking de perspective. Esta técnica crea profundidad en UI sin JavaScript — parallax puro CSS. Combinado con animaciones scroll-driven, crea experiencias de scrolling inmersivas.

css
.stage {
  perspective: 1200px;
  perspective-origin: 50% 30%; /* x y position of vanishing point */
  transform-style: preserve-3d;
}

.layer-1 { transform: translateZ(0); }
.layer-2 { transform: translateZ(50px); }
.layer-3 { transform: translateZ(100px); }

/* Parallax-like depth */
.depth-bg    { transform: translateZ(-200px) scale(1.2); }
.depth-mid   { transform: translateZ(-100px) scale(1.1); }
.depth-fg    { transform: translateZ(0); }
13

Filtros CSS y backdrop filters

Funciones de filter

Los filtros CSS aplican efectos visuales a elementos. grayscale, sepia e invert cambian colores; blur suaviza; brightness/contrast/saturate ajustan intensidad; hue-rotate desplaza colores. Múltiples filtros se encadenan izquierda-a-derecha. drop-shadow es superior a box-shadow para imágenes PNG/SVG porque sigue la forma alpha (las áreas transparentes no se sombrean). Los filtros son GPU-accelerated y rinden bien. Uso común: grayscale en hover para galerías de imágenes, efectos de foto vintage y accesibilidad (modos de alto contraste).

css
img.grayscale { filter: grayscale(100%); }
img.blur { filter: blur(5px); }
img.bright { filter: brightness(1.5); }
img.contrast { filter: contrast(200%); }
img.sepia { filter: sepia(80%); }
img.saturate { filter: saturate(2); }
img.hue { filter: hue-rotate(90deg); }
img.invert { filter: invert(100%); }

/* Combine multiple filters */
img.vintage {
  filter: sepia(50%) contrast(110%) brightness(90%) saturate(120%);
}

/* Drop shadow (follows alpha shape) */
img.png-shadow {
  filter: drop-shadow(2px 4px 6px rgba(0,0,0,0.4));
}

backdrop-filter (glassmorphism)

backdrop-filter aplica filtros al área detrás de un elemento (no al elemento mismo), creando el efecto glassmorphism. El elemento necesita un background semi-transparente para que el efecto sea visible. blur es el backdrop filter más común para vidrio esmerilado. Siempre incluye el prefix -webkit- para soporte de Safari. Combina con borders semi-transparentes y box-shadows sutiles para un look de vidrio pulido. El rendimiento puede ser un problema con áreas grandes blurred — úsalo con moderación. Esta es una tendencia definitoria en diseño UI moderno (iOS, macOS, Windows Acrylic).

css
.glass {
  background: rgba(255, 255, 255, 0.2);
  backdrop-filter: blur(10px) saturate(180%);
  -webkit-backdrop-filter: blur(10px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 16px;
  padding: 24px;
}

.glass-dark {
  background: rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(20px);
  border: 1px solid rgba(255, 255, 255, 0.1);
}

Filtros SVG (referencia url)

CSS filter: url(#id) referencia filtros SVG para efectos más allá de las funciones integradas. El filtro 'gooey' crea merging blob-like de elementos (popular para animaciones de loading y UI orgánica). feGaussianBlur + feColorMatrix con un multiplicador alpha alto crea el efecto gooey sharpening edges blurred. feTurbulence + feDisplacementMap crea efectos wavy/distorted. Los filtros SVG son potentes pero pueden ser performance-intensive. Habilitan efectos imposibles con CSS solo: morphing líquido, displacement, lighting y compositing personalizado.

css
<!-- Define SVG filter -->
<svg style="display:none">
  <filter id="gooey">
    <feGaussianBlur in="SourceGraphic" stdDeviation="10" />
    <feColorMatrix values="
      1 0 0 0 0
      0 1 0 0 0
      0 0 1 0 0
      0 0 0 20 -10" />
  </filter>
</svg>

<!-- Apply via CSS -->
.gooey-container {
  filter: url(#gooey);
}

<!-- Displacement map for wavy text -->
<filter id="wavy">
  <feTurbulence baseFrequency="0.02" numOctaves="3" />
  <feDisplacementMap in="SourceGraphic" scale="10" />
</filter>

Animaciones de filter

Los filtros pueden animarse y transicionarse suavemente. El efecto pulse-glow combina brightness y drop-shadow para un glow neón. Grayscale-to-color en hover es una interacción de galería clásica. Las transiciones de filter son GPU-accelerated y rinden mejor que animar box-shadow o background-color. Sin embargo, animar blur o filtros complejos puede ser costoso — testea en móvil. El hue-rotate scroll-driven es un efecto divertido pero puede causar problemas de rendimiento en páginas largas; usa will-change: filter para hint al navegador para optimización.

css
@keyframes pulse-glow {
  0%, 100% {
    filter: brightness(1) drop-shadow(0 0 5px #3498db);
  }
  50% {
    filter: brightness(1.3) drop-shadow(0 0 20px #3498db);
  }
}

.glow-button {
  animation: pulse-glow 2s ease-in-out infinite;
}

/* Hover filter transition */
.gallery img {
  filter: grayscale(100%);
  transition: filter 0.3s ease;
}
.gallery img:hover {
  filter: grayscale(0%);
}

/* Color shift on scroll (with JS) */
window.addEventListener("scroll", () => {
  const hue = window.scrollY * 0.5;
  document.body.style.filter = `hue-rotate(${hue}deg)`;
});

mix-blend-mode y background-blend-mode

mix-blend-mode determina cómo los píxeles de un elemento se mezclan con el contenido detrás. multiply oscurece (bueno para sombras y watermarks); screen aclara (bueno para glows); difference crea efectos psicodélicos; overlay combina multiply y screen para contraste. background-blend-mode mezcla múltiples capas de background (imágenes y gradientes) en el mismo elemento. Los blend modes son esenciales para compositing creativo, efectos duotone y overlays de texto sobre imagen. isolation: isolate crea un nuevo blending context para contener efectos de blend.

css
/* mix-blend-mode: how element blends with backdrop */
.overlay {
  mix-blend-mode: multiply; /* darken */
}
.overlay-screen {
  mix-blend-mode: screen; /* lighten */
}
.overlay-difference {
  mix-blend-mode: difference; /* invert */
}

/* Common blend modes: multiply, screen, overlay,
   darken, lighten, color-dodge, color-burn,
   hard-light, soft-light, difference, exclusion,
   hue, saturation, color, luminosity */

/* background-blend-mode: blend background layers */
.gradient-texture {
  background-image: url(texture.png),
                    linear-gradient(45deg, red, blue);
  background-blend-mode: multiply;
}
14

Container queries

Fundamentos de container query

Las container queries dejan que los componentes respondan al tamaño de su container en lugar del viewport. container-type: inline-size hace el elemento un query container basado en su dimensión inline (width). La regla @container aplica estilos cuando el container cumple la condición. Esto es revolucionario para diseño basado en componentes — un componente de tarjeta se adapta ya sea que esté en un sidebar o área main full-width, independientemente del tamaño de pantalla. Esto resuelve la limitación fundamental de las media queries, que solo responden al tamaño del viewport.

css
/* Define a container */
.card-container {
  container-type: inline-size;
  /* or: container-type: size; (both dimensions) */
}

/* Query the container's size */
@container (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 1fr 2fr;
  }
}

@container (max-width: 399px) {
  .card {
    display: flex;
    flex-direction: column;
  }
}

Containers con nombre

Los containers con nombre te permiten apuntar a containers específicos cuando múltiples están anidados. container-name da al container una etiqueta; @container name (condition) consulta solo ese container. Esto previene conflictos cuando los componentes están anidados con diferentes contextos de container. El shorthand container combina name y type: container: panel / inline-size. Los containers con nombre son esenciales para layouts complejos donde múltiples container queries independientes coexisten. Sin nombres, @container consulta el container ancestro más cercano.

css
/* Name a container for specific queries */
.sidebar {
  container-type: inline-size;
  container-name: sidebar;
}

.main-content {
  container-type: inline-size;
  container-name: main;
}

/* Query specific container by name */
@container sidebar (min-width: 300px) {
  .widget { display: grid; grid-template-columns: 1fr 1fr; }
}

@container main (min-width: 600px) {
  .article { columns: 2; }
}

/* Shorthand: container: name / type */
.panel {
  container: panel / inline-size;
}

Unidades de container query

Las unidades de container query (cqw, cqh, cqmin, cqmax) son como unidades de viewport (vw, vh) pero relativas al query container en lugar del viewport. 1cqw = 1% del ancho del container. Esto habilita tipografía y espaciado verdaderamente component-responsive — el texto escala con el ancho del componente, no de la pantalla. Combinado con clamp(), obtienes tipografía fluida que se adapta al tamaño del container con bounds min/max. Esto es ideal para design systems donde los componentes deben funcionar en varios contextos de layout. Nota: cqh/cqmin/cqmax requieren container-type: size (ambas dimensiones).

css
.card {
  container-type: inline-size;
}

.card-title {
  /* cqw: 1% of container width */
  font-size: 5cqw;

  /* cqh: 1% of container height (needs size type) */
  padding: 2cqh;

  /* cqmin: 1% of smaller container dimension */
  margin: 2cqmin;

  /* cqmax: 1% of larger container dimension */
  border-radius: 1cqmax;
}

/* Fluid typography that responds to container */
.headline {
  font-size: clamp(1rem, 8cqw, 3rem);
}

Container style queries

Las container style queries (experimentales, solo Chromium a fecha de 2025) te permiten consultar valores de custom property en un container, no solo dimensiones. Esto habilita rendering condicional basado en estilo: un componente puede cambiar apariencia basándose en una variable --theme establecida en su container. container-type: style (o no se necesita container-type para style queries) habilita esto. Esto es potente para theming — establece una variable en un padre y todos los children se adaptan. El soporte completo del navegador aún está pendiente; úsalo con progressive enhancement o checks @supports.

css
/* Query custom property values on container */
.card-wrapper {
  container-type: style;
  container-name: card;
}

@container card style(--theme: dark) {
  .card {
    background: #1a1a1a;
    color: #fff;
  }
}

@container card style(--theme: light) {
  .card {
    background: #fff;
    color: #333;
  }
}

/* Set the theme variable */
<div class="card-wrapper" style="--theme: dark">
  <div class="card">...</div>
</div>

Patrón de componente responsive

Este patrón crea un componente verdaderamente reutilizable que se adapta a su placement. La tarjeta de producto muestra un layout vertical en containers estrechos, horizontal en medianos, y un grid de 3 columnas en containers anchos. El mismo componente funciona en un sidebar (estrecho), grid (mediano) o hero (ancho) sin ninguna lógica condicional basada en props. Este es el killer use case para container queries — componentes que son genuinamente context-independent. Combinado con unidades de container query para tipografía, obtienes componentes completamente auto-adaptativos.

css
/* Component that adapts to any container */
.product-card {
  container-type: inline-size;
  container-name: product;
}

.product-card .layout {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

/* Medium container: horizontal layout */
@container product (min-width: 350px) {
  .product-card .layout {
    flex-direction: row;
  }
  .product-card .image {
    width: 40%;
  }
}

/* Large container: full feature layout */
@container product (min-width: 600px) {
  .product-card .layout {
    display: grid;
    grid-template-columns: 1fr 2fr 1fr;
  }
  .product-card .actions {
    display: flex;
    flex-direction: column;
  }
}
15

Scroll snap y animaciones scroll-driven

Fundamentos de scroll snap

Scroll snap crea scrolling magnético que alinea elementos a snap points. scroll-snap-type: x/y establece el eje; mandatory fuerza snapping (siempre aterriza en un punto); proximity hace snap solo cuando está cerca. scroll-snap-align: start/center/end define dónde el child hace snap dentro del container. Las secciones de scrolling full-page (como slides de presentación) usan y mandatory con secciones 100vh. Los carruseles horizontales usan x mandatory. Siempre testea en dispositivos touch — el snapping mandatory puede sentirse restrictivo si el contenido es más alto que el viewport. Usa proximity para comportamiento más indulgente.

css
/* Container with scroll snapping */
.gallery {
  scroll-snap-type: x mandatory;
  overflow-x: auto;
  display: flex;
  gap: 1rem;
}

/* Children snap to positions */
.gallery section {
  scroll-snap-align: start;
  flex: 0 0 100%;
  height: 300px;
}

/* Vertical snapping */
.full-page {
  scroll-snap-type: y mandatory;
  height: 100vh;
  overflow-y: scroll;
}
.full-page section {
  scroll-snap-align: start;
  height: 100vh;
}

Scroll snap con padding

scroll-padding offset los snap points desde el borde del container — útil cuando tienes headers fijos o quieres margen alrededor de items snapped. scroll-snap-stop: always previene fast scrolling de saltar múltiples items (fuerza snapping uno a la vez). scroll-snap-align: center hace snap de items al centro del container, creando un efecto cover-flow. Estas propiedades dan control fino sobre el comportamiento de snapping. Para carruseles de imágenes, combina con scroll-behavior: smooth para transiciones animadas entre snaps.

css
.carousel {
  scroll-snap-type: x mandatory;
  scroll-padding: 0 20px; /* offset snap points */
  overflow-x: auto;
  display: flex;
  gap: 1rem;
  padding: 0 20px;
}

.carousel .item {
  scroll-snap-align: start;
  scroll-snap-stop: always; /* don't skip items */
  flex: 0 0 80%;
}

/* Center-aligned snapping */
.carousel-center .item {
  scroll-snap-align: center;
  flex: 0 0 60%;
}

Animaciones scroll-driven (animation-timeline)

Las animaciones scroll-driven (¡solo CSS, sin JS!) vinculan animaciones a la posición de scroll. animation-timeline: scroll() vincula la animación al progreso de scroll de la página (0% arriba, 100% abajo). animation-timeline: view() lo vincula al elemento entrando/saliendo del viewport. animation-range define cuándo la animación comienza/termina relativo al scroll (entry, exit, cover, contain). Esto habilita barras de progreso de scroll, reveal-on-scroll y efectos parallax sin JavaScript ni scroll event listeners. Soportado en Chrome 115+, progressive enhancement recomendado para otros navegadores.

css
/* Animate as user scrolls */
@keyframes fade-in {
  from { opacity: 0; transform: translateY(50px); }
  to   { opacity: 1; transform: translateY(0); }
}

.reveal {
  animation: fade-in linear;
  animation-timeline: view();
  animation-range: entry 0% cover 40%;
}

/* Progress bar based on scroll position */
.progress-bar {
  position: fixed;
  top: 0; left: 0;
  height: 4px;
  background: #3498db;
  width: 100%;
  transform-origin: left;
  animation: grow linear;
  animation-timeline: scroll();
}

@keyframes grow {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

View timeline ranges

Los view timeline ranges controlan exactamente cuándo ocurre la animación. 'entry' es cuando el elemento entra al viewport; 'exit' cuando sale; 'cover' abarca desde entry start hasta exit end; 'contain' es cuando el elemento está completamente visible. Los porcentajes ajustan dentro de cada range. animation-range: entry 10% entry 90% significa que la animación corre desde 10% en entry hasta 90% en entry. Para sticky headers, scroll() con un range de píxeles (0 200px) anima basándose en distancia de scroll absoluta. Esto reemplaza scroll handlers complejos de JavaScript con CSS declarativo.

css
/* Element animates as it enters viewport */
@keyframes slide-up {
  from { opacity: 0; transform: translateY(100px); }
  to   { opacity: 1; transform: translateY(0); }
}

.section {
  animation: slide-up linear both;
  animation-timeline: view();
  /* Ranges: entry, exit, cover, contain */
  animation-range: entry 10% entry 90%;
}

/* Sticky header that shrinks on scroll */
.header {
  position: sticky;
  top: 0;
  animation: shrink linear both;
  animation-timeline: scroll();
  animation-range: 0 200px;
}
@keyframes shrink {
  to { padding: 0.5rem 1rem; font-size: 0.9em; }
}

scroll-behavior y smooth scroll

scroll-behavior: smooth en html (o cualquier scroll container) hace que la navegación de anchor links y scrollIntoView() se anime suavemente en lugar de saltar. Esto es un reemplazo de una línea para librerías de smooth-scroll de JavaScript. Siempre respeta prefers-reduced-motion — algunos usuarios experimentan motion sickness, así que deshabilita smooth scrolling para ellos. El scroll-snap-type: proximity (vs mandatory) se parea bien con smooth scrolling para un feel natural. scrollIntoView con block: 'start'/'center'/'end' controla la alineación vertical del target.

css
/* Smooth scrolling for anchor links */
html {
  scroll-behavior: smooth;
}

/* With scroll snap */
html {
  scroll-behavior: smooth;
  scroll-snap-type: y proximity;
}

/* Target specific elements */
.container {
  scroll-behavior: smooth;
  overflow-y: auto;
}

/* JavaScript: scroll to element */
<script>
document.querySelector(".section-3")
  .scrollIntoView({ behavior: "smooth", block: "start" });

/* Scroll to top */
window.scrollTo({ top: 0, behavior: "smooth" });
</script>

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}
16

Contadores CSS

Contador básico

Los contadores CSS auto-numeran elementos sin JavaScript. counter-reset inicializa un contador (en el padre o cualquier ancestro). counter-increment lo incrementa (usualmente en el elemento siendo numerado). counter(name) muestra el valor actual en content. El contador maneja automáticamente la numeración a través del documento. Esto es ideal para numerar encabezados, list items, figuras o cualquier contenido secuencial. Los contadores están scoped al elemento donde se resetean y sus descendientes.

css
/* Initialize counter on parent */
body {
  counter-reset: section;
}

/* Increment and display */
h2::before {
  counter-increment: section;
  content: "Section " counter(section) ": ";
  color: #3498db;
  font-weight: bold;
}

/* Result: Section 1: Introduction, Section 2: Methods... */

Contadores anidados

Los contadores anidados crean numeración jerárquica (1.1, 1.2, 2.1, etc.). Resetea el sub-contador en el encabezado padre (counter-reset: section en h2). Cada h2 comienza un nuevo capítulo y resetea el contador de section. Cada h3 incrementa el contador de section dentro del capítulo actual. La propiedad content combina múltiples llamadas counter() con separadores. Esto refleja cómo se numeran libros y documentos técnicos. Los contadores se cascaden naturalmente basándose en estructura DOM y puntos de reset.

css
body {
  counter-reset: chapter;
}

h2 {
  counter-reset: section;
  counter-increment: chapter;
}
h2::before {
  content: "Chapter " counter(chapter) ". ";
}

h3 {
  counter-increment: section;
}
h3::before {
  content: counter(chapter) "." counter(section) " ";
}

/* Result: Chapter 1.  / 1.1 / 1.2 / Chapter 2. / 2.1 ... */

Estilos de contador

counter() acepta un argumento de estilo: decimal (default), decimal-leading-zero (01, 02), lower/upper-alpha (a/b o A/B), lower/upper-roman (i/ii o I/II). @counter-style define estilos de contador personalizados con símbolos cíclicos, sistemas aditivos o notación simbólica. system: cyclic repite símbolos; system: additive crea sistemas tipo Roman-numeral. Los estilos de contador personalizados son potentes para listas internacionalizadas, viñetas personalizadas o numeración decorativa. El soporte del navegador para @counter-style es bueno en navegadores modernos.

css
/* Different number styles */
ol {
  counter-reset: item;
  list-style: none;
}
li::before {
  counter-increment: item;
  /* decimal, decimal-leading-zero, lower-alpha,
     upper-alpha, lower-roman, upper-roman */
  content: counter(item, upper-roman) ". ";
}

/* Leading zeros: 01, 02, 03... */
li::before {
  content: counter(item, decimal-leading-zero) ". ";
}

/* Custom @counter-style */
@counter-style thumbs {
  system: cyclic;
  symbols: "👍" "👎";
  suffix: " ";
}
.thumbs-list { list-style-type: thumbs; }

Función counters() (anidado)

La función counters() (plural) devuelve el path completo del contador como string, con niveles separados por el delimitador especificado. Esto maneja automáticamente scopes de contador anidados — cada ol anidado crea un nuevo scope de contador. counters(nested, '.') produce 1, 1.1, 1.1.1 para listas profundamente anidadas. Esto es diferente de counter() que solo muestra el nivel actual. Usa counters() para numeración estilo outline de listas anidadas, tabla de contenidos o menús jerárquicos. El string separador puede ser cualquier carácter (. , - > etc.).

css
/* counters() creates full path string */
ol {
  counter-reset: nested;
  list-style: none;
}
li::before {
  counter-increment: nested;
  /* counters() returns all levels separated by a string */
  content: counters(nested, ".") " - ";
  font-weight: bold;
}

/* For nested <ol> elements:
   1 - Top level
   1.1 - Nested
   1.1.1 - Deeper
   2 - Top level again
*/

Contador para figuras y tablas

Los contadores son perfectos para auto-numerar figuras, tablas y ecuaciones en documentación. Resetea por capítulo (counter-reset en h2) para que la numeración se reinicie cada capítulo. El pseudo-elemento ::before en figcaption/caption prependa el número. Esto asegura numeración consistente y automática que se actualiza cuando el contenido se reordena. Para cross-references ('ver Figura 3'), los contadores CSS no pueden ayudar directamente — necesitarías JS o HTML anchors. Pero para numeración de display, esta es una solución limpia y libre de mantenimiento.

css
body {
  counter-reset: figure table;
}

figure figcaption::before {
  counter-increment: figure;
  content: "Figure " counter(figure) ": ";
  font-weight: bold;
  color: #2c3e50;
}

table caption::before {
  counter-increment: table;
  content: "Table " counter(table) ": ";
  font-weight: bold;
  color: #2c3e50;
}

/* Reset figure counter per chapter */
h2 {
  counter-reset: figure;
}
18

Dark mode y esquemas de color

prefers-color-scheme

prefers-color-scheme: dark detecta la preferencia de dark mode del OS/navegador del usuario. Define variables de tema en :root para light mode, luego sobrescríbelas en la media query dark. Usar variables CSS significa que solo cambias los valores de variables — todos los componentes se actualizan automáticamente. Este es el enfoque estándar para dark mode. La media query también soporta 'light' y 'no-preference'. Testea togglando tu dark mode del OS o usando la pestaña Rendering de Chrome DevTools (Emulate CSS prefers-color-scheme).

css
/* Light mode (default) */
:root {
  --bg: #ffffff;
  --text: #1a1a1a;
  --surface: #f5f5f5;
  --border: #e0e0e0;
  --accent: #3498db;
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1a1a1a;
    --text: #e0e0e0;
    --surface: #2a2a2a;
    --border: #404040;
    --accent: #5dade2;
  }
}

body {
  background: var(--bg);
  color: var(--text);
}

Toggle de tema manual

Para toggle de tema manual, usa un atributo data-theme en <html> que sobrescribe la preferencia del sistema. Almacena la elección del usuario en localStorage para que persista. En carga de página, verifica localStorage antes de renderizar para evitar flash de tema incorrecto (FOUC). color-scheme: light dark le dice al navegador que renderice elementos UI nativos (scrollbars, form controls) en el esquema apropiado. Para mejor UX, default a preferencia del sistema cuando no existe elección guardada: verifica matchMedia('(prefers-color-scheme: dark)') como fallback.

css
/* Default to system preference, allow override */
:root {
  color-scheme: light dark;
}

/* Manual dark mode via data attribute */
[data-theme="dark"] {
  --bg: #1a1a1a;
  --text: #e0e0e0;
}

[data-theme="light"] {
  --bg: #ffffff;
  --text: #1a1a1a;
}

/* Toggle button */
<button onclick="toggleTheme()">Toggle Theme</button>
<script>
function toggleTheme() {
  const current = document.documentElement.dataset.theme;
  const next = current === "dark" ? "light" : "dark";
  document.documentElement.dataset.theme = next;
  localStorage.setItem("theme", next);
}
// Load saved preference
const saved = localStorage.getItem("theme");
if (saved) document.documentElement.dataset.theme = saved;
</script>

Propiedad color-scheme

La propiedad color-scheme le dice al navegador qué esquemas de color soporta la página, afectando elementos UI nativos: scrollbars, form controls (inputs, buttons, dropdowns), color de background/canvas default y el color ::placeholder. Sin ella, los form controls pueden aparecer light incluso en dark mode (mismatch discordante). Establece color-scheme: light dark en :root para dejar que los elementos nativos se adapten automáticamente. Esto es un fix de una línea que mejora dramáticamente el polish del dark mode. Es separado de tu theming de variables CSS — solo afecta el rendering browser-native.

css
:root {
  /* Tell browser this page supports both schemes */
  color-scheme: light dark;
}

/* Force light only */
.light-only {
  color-scheme: light;
}

/* Force dark only */
.dark-only {
  color-scheme: dark;
}

/* This affects native UI: scrollbars, form controls,
   default colors (canvas, text), and form elements */
input, textarea, select {
  /* Will use dark form controls in dark mode */
  color-scheme: dark;
}

Imágenes y media en dark mode

Las imágenes necesitan manejo especial en dark mode. Los diagramas de background blanco pueden invertirse con filter: invert(1) hue-rotate(180deg) — el hue-rotate previene distorsión de color. Las fotos de personas nunca deberían invertirse. Reduce brightness ligeramente para menos eye strain. Para logos e iconos, usa <picture> con sources media-query para servir versiones dark-optimized. Los SVGs con currentColor se adaptan automáticamente. Para imágenes de background, proporciona alternativas dark-mode vía media queries. Siempre testea imágenes en ambos modos para legibilidad.

css
@media (prefers-color-scheme: dark) {
  /* Invert images that are essentially white-background */
  img[src*="logo"],
  img[src*="diagram"] {
    filter: invert(1) hue-rotate(180deg);
  }

  /* Reduce brightness of photos */
  img.photo {
    filter: brightness(0.85);
  }

  /* Don't invert photos with people */
  img[src*="photo"]:not(.invertable) {
    filter: none;
  }
}

/* Use dark-mode-friendly SVG */
<picture>
  <source srcset="logo-dark.svg"
          media="(prefers-color-scheme: dark)">
  <img src="logo-light.svg" alt="Logo">
</picture>

Transición suave de tema

Añadir transiciones de color a todos los elementos crea una animación suave de switch de tema. Sin embargo, esto puede causar un flash de colores animados en la carga inicial de página. La solución: añade una class .no-transition a <html> durante switches de tema y en carga inicial, luego remuévela después de un frame. Siempre respeta prefers-reduced-motion para deshabilitar transiciones para usuarios que solicitan movimiento reducido. Sé selectivo — transicionar todas las propiedades puede impactar el rendimiento. Solo transiciona propiedades relacionadas con color (background-color, color, border-color, box-shadow).

css
/* Transition colors when theme changes */
* {
  transition: background-color 0.3s ease,
              color 0.3s ease,
              border-color 0.3s ease;
}

/* But not during initial load (prevent flash) */
.no-transition * {
  transition: none !important;
}

<script>
// Disable transitions during theme switch
function toggleTheme() {
  document.documentElement.classList.add("no-transition");
  document.documentElement.dataset.theme = newTheme;
  // Re-enable after switch
  requestAnimationFrame(() => {
    document.documentElement.classList.remove("no-transition");
  });
}
</script>

/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
}
19

Funciones CSS en profundidad

clamp(), min(), max()

clamp(min, preferred, max) es el holy grail de la tipografía y espaciado fluidos — escala con el valor preferred pero nunca excede los bounds min/max. min() elige el valor más pequeño (genial para max-width responsive). max() elige el valor más grande (genial para tamaños mínimos). Estas eliminan muchas media queries proporcionando escalado fluido continuo. El valor preferred en clamp típicamente usa unidades de viewport (vw, vh) o unidades de container (cqw). Este es el enfoque moderno para diseño responsive sin breakpoints.

css
/* clamp(min, preferred, max) */
h1 {
  font-size: clamp(1.5rem, 5vw, 3rem);
  /* Never smaller than 1.5rem, never larger than 3rem,
     preferred is 5vw */
}

/* min(): use the smaller value */
.container {
  width: min(100% - 2rem, 1200px);
  /* Full width minus padding, but max 1200px */
}

/* max(): use the larger value */
.hero {
  font-size: max(2rem, 4vw);
  /* At least 2rem, grows with viewport */
}

/* Combining for responsive padding */
.card {
  padding: clamp(1rem, 3vw, 2rem);
}

calc() avanzado

calc() realiza cálculos con unidades mixtas (px, %, em, vw, etc.). La suma y resta requieren espacios alrededor del operador (calc(100% - 20px), no calc(100%-20px)). La multiplicación y división solo funcionan con números, no longitudes (calc(10px * 2) es válido; calc(10px * 10px) no). calc() puede anidarse pero los paréntesis también funcionan. Combinado con variables CSS, calc() habilita design systems dinámicos y computados. Es esencial para layouts responsive que combinan dimensiones fijas y fluidas.

css
/* Mixed units */
.sidebar {
  width: calc(100% - 250px); /* full width minus fixed sidebar */
  height: calc(100vh - 60px); /* viewport minus header */
}

/* Nested calc */
.element {
  margin: calc(calc(100% - 50px) / 2);
  /* Simplified: */
  margin: calc((100% - 50px) / 2);
}

/* With CSS variables */
:root { --gap: 20px; }
.grid {
  gap: calc(var(--gap) * 2);
}

/* Math operations: + - * / */
.padding {
  padding: calc(1rem + 2px);
  width: calc(50% - 10px);
}

Función attr()

attr() recupera valores de atributos HTML, más comúnmente usado en content para pseudo-elementos. Es la base de tooltips solo CSS (atributo data-tooltip → ::after content). attr() funciona confiablemente solo en la propiedad content. Usar attr() para otras propiedades (width, color, etc.) con type casting (attr(data-size px)) es parte de CSS Values Level 5 pero tiene soporte mínimo del navegador. Por ahora, usa variables CSS para valores dinámicos desde HTML: establece style='--size: 100px' y usa var(--size).

css
/* Display attribute values */
a[href]::after {
  content: attr(href);
}

/* data attributes for tooltips */
.tooltip::after {
  content: attr(data-tooltip);
  display: none;
  position: absolute;
}
.tooltip:hover::after {
  display: block;
}

/* HTML: <div class="tooltip" data-tooltip="Help text">?</div> */

/* Note: attr() for non-content properties is experimental */
/* This doesn't work widely yet: */
/* .box { width: attr(data-width px); } */

var() con fallbacks

var() acepta un fallback como segundo argumento, usado cuando la variable no está definida. Los fallbacks pueden encadenarse: var(--a, var(--b, var(--c, default))). Esto habilita progressive enhancement y theming multi-nivel. Un valor de variable vacío (--defined: ;) aún está 'set', así que el fallback no se disparará — esto habilita patrones CSS condicionales. Los fallbacks pueden incluir cualquier valor CSS válido, incluyendo calc() y otras funciones. Usa fallbacks para variables de tema opcionales que podrían no estar definidas en todos los contextos.

css
/* Single fallback */
color: var(--text-color, #333);

/* Chained fallbacks */
color: var(--text, var(--default-text, black));

/* Fallback in shorthand */
background: var(--bg, white) url(image.png);

/* Fallback with calc */
width: calc(100% - var(--sidebar, 250px));

/* Check if variable is set */
:root {
  --defined: ; /* empty space = valid value */
}
.conditional {
  --use-default: var(--defined, initial);
  /* If --defined is empty, --use-default = initial */
}

Funciones de gradient

Los gradientes CSS crean transiciones suaves entre colores sin imágenes. linear-gradient (dirección + colores), radial-gradient (desde el centro hacia afuera) y conic-gradient (rotando alrededor de un punto) son los tres tipos. Los hard stops (mismo porcentaje para dos colores) crean bandas nítidas. repeating-linear-gradient crea patrones como rayas. Los gradientes pueden layerarse con múltiples backgrounds (separados por coma, primero = top layer). Los conic gradients habilitan pie charts y color wheels en CSS puro. Los gradientes son resolution-independent y rinden mejor que archivos de imagen.

css
/* Linear gradient */
.bg { background: linear-gradient(45deg, #3498db, #e74c3c); }
.bg2 { background: linear-gradient(to right, red, orange, yellow); }

/* Radial gradient */
.radial { background: radial-gradient(circle, white, blue); }
.radial2 { background: radial-gradient(circle at top left, #fff, #000); }

/* Conic gradient */
.pie { background: conic-gradient(red 0% 30%, blue 30% 70%, green 70% 100%); }

/* Repeating gradients */
.stripes { background: repeating-linear-gradient(45deg, #ccc 0 10px, #fff 10px 20px); }

/* Hard color stops (sharp edges) */
.hard { background: linear-gradient(to right, #000 50%, #fff 50%); }

/* Multiple backgrounds */
.multi {
  background:
    linear-gradient(rgba(0,0,0,0.5), transparent),
    url(photo.jpg);
}
20

Variables CSS

Definición y uso de variables

Las custom properties de CSS (variables) se definen con --name y se usan con var(). Decláralas en :root para acceso global. A diferencia de las variables de Sass, son live — cambiar una variable vía JavaScript actualiza instantáneamente cada elemento que la usa.

css
:root {
  --primary: #3498db;
  --spacing: 16px;
  --radius: 8px;
}

.button {
  background: var(--primary);
  padding: var(--spacing);
  border-radius: var(--radius);
}

Valores de fallback

var() acepta un segundo argumento como fallback cuando la variable no está definida. Los fallbacks pueden anidarse, permitiéndote encadenar defaults. Para navegadores que no soportan custom properties, declara un valor normal primero, luego sobrescribe con var().

css
.box {
  color: var(--brand, #333);
  background: var(--bg, var(--default-bg, white));
}

/* Provide fallback for older browsers */
.element {
  color: #333;
  color: var(--text-color, #333);
}

Interacción con JavaScript

JavaScript lee variables vía getComputedStyle().getPropertyValue() y las establece con setProperty(). Esto hace el theming en runtime trivial — flipa algunas variables para reestilizar la app entera.

css
// Read a variable
const color = getComputedStyle(document.documentElement)
  .getPropertyValue('--primary');

// Set a variable on an element
document.documentElement.style.setProperty('--primary', '#e74c3c');

// Toggle a theme
document.documentElement.setAttribute('data-theme', 'dark');

Scoping y herencia

Las custom properties heredan, así que una variable establecida en :root se cascada a cada elemento. Redefinirla en .card la sobrescribe solo para ese subtree. Este scoping habilita theming a nivel de componente sin guerras de specificity.

css
:root { --size: 16px; }

.card {
  --size: 20px; /* Overrides only within .card */
  font-size: var(--size);
}

.card .text { font-size: var(--size); } /* 20px */
.other { font-size: var(--size); }       /* 16px */

Variables responsive

Redefine variables dentro de media queries para hacer que el layout entero responda a breakpoints sin reescribir reglas. Cambia una variable y cada elemento que la usa se adapta — mucho más limpio que sobrescribir cada propiedad individualmente.

css
:root {
  --container-width: 1200px;
  --font-size: 18px;
}

@media (max-width: 768px) {
  :root {
    --container-width: 100%;
    --font-size: 16px;
  }
}

.container { max-width: var(--container-width); }
body { font-size: var(--font-size); }
21

Animaciones y keyframes

@keyframes y shorthand de animation

@keyframes define los estados start (from/0%) y end (to/100%) de una animación. El shorthand animation combina name, duration, timing-function, delay, iteration-count, direction, fill-mode y play-state. forwards mantiene el estado final.

css
@keyframes slide-in {
  from { transform: translateX(-100%); opacity: 0; }
  to   { transform: translateX(0); opacity: 1; }
}

.element {
  animation: slide-in 0.5s ease-out forwards;
}

/* Shorthand: name duration timing delay count direction fill-mode */
.bounce {
  animation: bounce 1s cubic-bezier(0.68, -0.55, 0.27, 1.55) 0s infinite alternate;
}

Transiciones

Las transiciones interpolan suavemente entre valores de propiedad cuando cambian. Especifica qué propiedades transicionan y su duration/timing. transition: all es conveniente pero puede dañar el rendimiento — prefiere listar propiedades específicas.

css
.button {
  background: blue;
  transition: background 0.3s ease, transform 0.2s ease-out;
}

.button:hover {
  background: darkblue;
  transform: scale(1.05);
}

/* Transition all changed properties */
.card { transition: all 0.3s ease; }

Timing functions

Las timing functions controlan la curva de aceleración. ease-out comienza rápido y frena (bueno para entradas); ease-in comienza lento y acelera (bueno para salidas). cubic-bezier te permite crear curvas personalizadas. steps() crea un efecto discreto.

css
/* Built-in */
.ease-linear { transition: all 1s linear; }
.ease-out    { transition: all 1s ease-out; }

/* Custom cubic-bezier(x1, y1, x2, y2) */
.custom { transition: all 1s cubic-bezier(0.68, -0.55, 0.27, 1.55); }

/* Steps */
.steps { transition: all 1s steps(5, end); }

Eventos de animación

JavaScript puede escuchar animationstart, animationiteration (se dispara cada loop) y animationend. Controla playback con la propiedad animation-play-state (paused/running). Combina con animationend para encadenar animaciones.

css
const el = document.querySelector('.animate');
el.addEventListener('animationstart', () => console.log('Started'));
el.addEventListener('animationiteration', () => console.log('Looped'));
el.addEventListener('animationend', () => console.log('Finished'));

// Pause and resume
el.style.animationPlayState = 'paused';
el.style.animationPlayState = 'running';

Rendimiento: transform y opacity

Anima solo transform y opacity para rendimiento de 60fps — se ejecutan en la GPU sin disparar layout. Animar width, height, top o margin fuerza recálculo de layout cada frame. Usa will-change con moderación para hint al navegador.

css
/* GOOD: GPU-accelerated, cheap */
.fade { transition: opacity 0.3s; }
.move { transition: transform 0.3s; }

/* BAD: triggers layout/paint, expensive */
.resize { transition: width 0.3s, height 0.3s; }

/* Hint the browser to optimize */
.card { will-change: transform; }
22

Transforms 3D

Transforms 2D

Los transforms 2D mueven, rotan, escalan y skew elementos sin afectar el layout circundante. Los transforms se compositan en la GPU, haciéndolos performantes para animación. El orden importa: rotate luego translate mueve a lo largo del eje rotado.

css
.box { transform: translate(50px, 20px); }
.box { transform: rotate(45deg); }
.box { transform: scale(1.5); }
.box { transform: skew(20deg, 10deg); }

/* Combined — order matters */
.combo { transform: translate(100px, 0) rotate(90deg) scale(1.2); }

Transforms 3D y perspective

Los transforms 3D añaden rotateX, rotateY, rotateZ, translateZ y scaleZ. perspective en un padre da a todos los children un punto de fuga compartido — valores más bajos exageran el efecto 3D. transform-style: preserve-3d mantiene elementos anidados en 3D.

css
.scene { perspective: 1000px; }

.card {
  transform: rotateY(45deg);
  transform-style: preserve-3d;
}

/* Perspective on the element itself */
.self { transform: perspective(800px) rotateX(30deg); }

/* Transform origin */
.pivot { transform-origin: top left; transform: rotate(45deg); }

Efecto flip card

El flip card clásico usa preserve-3d en un container, con caras front y back absolutamente posicionadas. backface-visibility: hidden oculta el reverso de cada cara. La cara back está pre-rotada 180deg para que se muestre cuando el container flipa.

css
.card { perspective: 1000px; }
.inner {
  position: relative;
  transform-style: preserve-3d;
  transition: transform 0.6s;
}
.card:hover .inner { transform: rotateY(180deg); }
.front, .back {
  position: absolute;
  backface-visibility: hidden;
}
.back { transform: rotateY(180deg); }

Cubo con 6 caras

Un cubo 3D se construye desde seis caras, cada una translada 50px (la mitad del ancho del cubo) hacia afuera a lo largo de su eje. preserve-3d mantiene las caras posicionadas en espacio 3D. Anima el transform del cubo para girarlo.

css
.cube {
  position: relative;
  width: 100px; height: 100px;
  transform-style: preserve-3d;
  transform: rotateX(-20deg) rotateY(30deg);
}
.face { position: absolute; width: 100px; height: 100px; }
.front  { transform: translateZ(50px); }
.back   { transform: rotateY(180deg) translateZ(50px); }
.right  { transform: rotateY(90deg) translateZ(50px); }
.left   { transform: rotateY(-90deg) translateZ(50px); }
.top    { transform: rotateX(90deg) translateZ(50px); }
.bottom { transform: rotateX(-90deg) translateZ(50px); }

Matrix y rendimiento

matrix() y matrix3d() expresan cualquier transform como una sola matrix — útil al computar transforms en JavaScript. translateZ(0) o will-change: transform fuerzan GPU acceleration, mejorando la suavidad de animación. Evita overusar layers.

css
/* matrix(a, b, c, d, e, f) encodes translate, rotate, scale, skew */
.transformed { transform: matrix(1, 0, 0, 1, 100, 50); }

/* 3D matrix */
.cube { transform: matrix3d(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1); }

/* Hardware acceleration hint */
.gpu { transform: translateZ(0); }
23

CSS Grid avanzado

Grid template areas

grid-template-areas nombra celdas grid visualmente, haciendo obvia la intención del layout. Cada fila quoted representa una fila grid; nombres idénticos abarcan celdas. Las celdas vacías usan un dot (.). Las areas deben formar rectángulos — las L-shapes son inválidas.

css
.layout {
  display: grid;
  grid-template-columns: 200px 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "header header"
    "sidebar main"
    "footer footer";
  gap: 16px;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main   { grid-area: main; }
.footer { grid-area: footer; }

minmax y auto-fit

repeat(auto-fit, minmax(200px, 1fr)) crea un grid responsive sin media queries: las columnas son al menos 200px y se estiran para llenar la fila. auto-fit colapsa tracks vacíos; auto-fill los mantiene reservados.

css
.grid {
  display: grid;
  /* Columns auto-fit, each at least 200px, sharing leftover space */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 20px;
}

/* Fixed number that wraps */
.wrap {
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}

Alineación de grid

La alineación de grid funciona en dos niveles: el container (justify/align-items y justify/align-content) y el item (justify/align-self). justify-* controla el eje inline, align-* el eje block. place-items: center es shorthand para ambos.

css
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  justify-items: center;
  align-items: center;
  justify-content: space-between;
  align-content: center;
}

.item {
  justify-self: end;
  align-self: start;
}

Spanning y line placement

Coloca items por número de línea grid (1 / 3 significa desde línea 1 hasta línea 3) o por span (span 2). Las líneas negativas cuentan desde el final (-1 es la última línea). grid-column: 1 / -1 hace que un item abarque el ancho completo.

css
.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
}
.header { grid-column: 1 / -1; }       /* full width */
.sidebar { grid-column: 1 / 3; }       /* columns 1-2 */
.main { grid-column: span 2; }          /* span 2 columns */
.tall { grid-row: 1 / span 2; }         /* span 2 rows */

Subgrid

subgrid deja que un grid child herede las definiciones de track del padre, para que las columnas o filas se alineen perfectamente a través de grids anidados. Sin subgrid, los grids anidados definen sus propios tracks y pueden desalinearse. Soportado en todos los navegadores modernos desde 2023.

css
.card {
  display: grid;
  grid-template-rows: auto 1fr auto;
  /* Inherit column tracks from parent */
  grid-template-columns: subgrid;
  grid-column: span 3;
}

.parent {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
}
24

Funciones CSS

calc()

calc() realiza matemática en unidades mixtas (px, %, vw, em, rem). Siempre rodea + y - con espacios (calc(100%-20px) falla; calc(100% - 20px) funciona). La multiplicación y división necesitan que un lado sea un número sin unidad.

css
.sidebar {
  width: calc(100% - 250px);
  padding: calc(16px + 2vw);
  font-size: calc(14px + 0.5vw);
}

/* Nested calc */
.full { width: calc(calc(100% - 20px) / 2); }

/* With variables */
.box { margin: calc(var(--gap) * 2); }

clamp()

clamp() acota un valor entre un mínimo y un máximo, con un valor preferred en el medio. Es la forma más limpia de construir tipografía y espaciado fluidos — el valor escala pero nunca va por debajo o por encima de los bounds.

css
/* clamp(MIN, PREFERRED, MAX) */
h1 {
  font-size: clamp(1.5rem, 4vw, 3rem);
}

.sidebar {
  width: clamp(200px, 25vw, 400px);
}

.padding {
  padding: clamp(1rem, 2vw + 1rem, 2rem);
}

min() y max()

min() devuelve el argumento más pequeño; max() devuelve el más grande. A diferencia de clamp (que toma exactamente tres valores), min/max aceptan cualquier número de argumentos. Usa min() para acotar un valor y max() para floorear un valor.

css
/* Picks the smallest/largest of comma-separated values */
.box {
  width: min(100% - 32px, 800px);
  font-size: max(16px, 2vw);
}

/* With multiple units */
.responsive {
  padding: min(5vw, 2rem);
  margin: max(1rem, 3vw);
}

var() con fallbacks

El segundo argumento de var() se usa solo cuando la custom property no está definida. Para valores inválidos, la propiedad recurre a su valor heredado o inicial. Anida llamadas var() para encadenar fallbacks.

css
.button {
  background: var(--btn-bg, #007bff);
  color: var(--text, var(--default-text, black));
  width: var(--width, 100%);
}

color-mix() y funciones de color

color-mix() mezcla dos colores en un color space especificado (srgb, oklch, lab). Es la forma moderna de derivar tints y shades de un color de marca. La relative color syntax (keyword from) te permite ajustar canales individuales.

css
/* Mix two colors */
.muted { color: color-mix(in srgb, red 30%, blue); }

/* Adjust relative lightness */
.lighter { background: color-mix(in oklch, var(--brand) 70%, white); }
.darker  { background: color-mix(in oklch, var(--brand) 70%, black); }

/* Relative color syntax (newest) */
.tint { background: oklch(from var(--brand) calc(l + 0.1) c h); }
25

Animaciones scroll-driven

Fundamentos de animation-timeline

animation-timeline: scroll(root) vincula una animación a la posición de scroll de la página — mientras el usuario scrollea, la animación progresa. No se necesita JavaScript. Esto es mucho más suave que scroll event listeners.

css
@keyframes grow {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

.progress-bar {
  animation: grow linear;
  animation-timeline: scroll(root);
  transform-origin: left;
}

Timeline view()

view() crea un timeline vinculado a un elemento entrando y saliendo del viewport. animation-range controla qué porción de la entrada dispara la animación. Perfecto para efectos scroll-reveal sin IntersectionObserver.

css
@keyframes reveal {
  from { opacity: 0; transform: translateY(40px); }
  to   { opacity: 1; transform: translateY(0); }
}

.card {
  animation: reveal linear;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

Barra de progreso scroll-linked

Una barra de progreso fija que se llena mientras scrolleas la página. animation-timeline: scroll(root) mapea el scroll de la página al range de animación 0-100%. transform-origin: 0 50% hace que la barra crezca desde la izquierda.

css
body { min-height: 200vh; }
.progress {
  position: fixed;
  top: 0; left: 0;
  height: 4px;
  width: 100%;
  background: linear-gradient(to right, #6366f1, #ec4899);
  transform-origin: 0 50%;
  animation: scale-x linear;
  animation-timeline: scroll(root);
}
@keyframes scale-x {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

Scroll timelines con nombre

Cuando necesitas vincular una animación a un elemento scrollable específico (no el root), define scroll-timeline-name en el container y referéncialo en animation-timeline. scroll-timeline-axis elige qué eje trackear (block/inline/x/y).

css
.scroll-container {
  scroll-timeline-name: --cards;
  scroll-timeline-axis: block;
  overflow-y: scroll;
  height: 400px;
}

.card {
  animation: fade-in linear;
  animation-timeline: --cards;
}

@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

Ajustes de view range

animation-range ajusta cuándo se ejecuta la animación relativa a la visibilidad del elemento. cover 0% a cover 100% abarca desde cuando el elemento entra hasta que sale completamente. contain 0% a 100% abarca solo cuando está completamente dentro.

css
.image {
  animation: parallax linear;
  animation-timeline: view();
  animation-range: cover 0% cover 100%;
}

@keyframes parallax {
  from { transform: translateY(-20%); }
  to   { transform: translateY(20%); }
}

/* Only animate while in view */
.pulse {
  animation: pulse 1s infinite;
  animation-timeline: view();
  animation-range: contain 0% contain 100%;
}
26

CSS layers

Definiendo @layer

@layer agrupa estilos en cascade layers con nombre. El orden en la línea de declaración establece prioridad: layers posteriores ganan sobre anteriores, independientemente de la specificity del selector. Esto aporta orden predecible a stylesheets grandes.

css
@layer reset, base, components, utilities;

@layer reset {
  * { margin: 0; padding: 0; box-sizing: border-box; }
}

@layer base {
  body { font-family: sans-serif; line-height: 1.6; }
}

@layer utilities {
  .hidden { display: none !important; }
}

Prioridad de layer

Dentro de layers, aplican las reglas de specificity normales. Pero a través de layers, las layers posteriores siempre ganan — una sola class en components beats un ID en base. Los estilos unlayered toman prioridad sobre todos los estilos layered.

css
@layer base, components;

@layer base {
  /* Even though this is more specific, base loses */
  div.button { color: black; }
}

@layer components {
  .button { color: blue; } /* This wins */
}

/* Unlayered styles always beat layered styles */
.button { color: red; }

Importando a layers

@import ... layer(name) coloca un stylesheet entero en una layer con nombre. Esto es esencial para domar CSS de terceros: pon Bootstrap en una layer vendor, y tus estilos personalizados (en una layer posterior o unlayered) siempre ganarán.

css
@import url("reset.css") layer(reset);
@import url("bootstrap.css") layer(vendor);

@layer reset, vendor, custom;

@layer custom {
  .btn { background: hotpink; }
}

Layers anidados

Las layers pueden anidarse, creando una jerarquía. framework.theme se refiere al sub-layer theme dentro de framework. Las layers anidadas siguen las mismas reglas de prioridad: dentro de framework, theme beats base.

css
@layer framework {
  @layer base, theme;

  @layer base {
    .btn { padding: 8px; }
  }
  @layer theme {
    .btn { color: navy; }
  }
}

/* Reference nested layer */
@layer framework.theme {
  .btn { color: hotpink; }
}

Lógica condicional de layer

Los bloques @layer pueden anidarse dentro de @media y @supports, para que la membresía de layer se adapte a condiciones. Esto mantiene los estilos feature-specific organizados dentro de su layer en lugar de filtrarse al espacio unlayered.

css
@layer base, components;

@media (max-width: 600px) {
  @layer components {
    .nav { flex-direction: column; }
  }
}

@supports (display: grid) {
  @layer components {
    .layout { display: grid; }
  }
}
27

Backdrop filters

Fundamentos de backdrop-filter

backdrop-filter aplica un filter al contenido detrás de un elemento, creando efectos de vidrio esmerilado. Siempre incluye el prefix -webkit- para Safari. Combina con un background semi-transparente para que el blur sea visible.

css
.glass {
  background: rgba(255, 255, 255, 0.2);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 12px;
}

Múltiples filtros

Encadena múltiples filtros separados por espacio: blur, brightness, contrast, grayscale, hue-rotate, invert, opacity, saturate, sepia. Cada uno aplica al backdrop. Parea con un background tinted para dirigir el mood.

css
.vibrant {
  backdrop-filter: blur(8px) saturate(180%) brightness(1.1);
}

.dark-glass {
  background: rgba(0, 0, 0, 0.4);
  backdrop-filter: blur(12px) grayscale(50%);
}

Blend modes

mix-blend-mode controla cómo un elemento se mezcla con el contenido debajo (separado de backdrop-filter). multiply oscurece, screen aclara, difference invierte. Ten cuidado con la legibilidad del texto.

css
.overlay {
  background: rgba(255, 0, 100, 0.3);
  backdrop-filter: blur(4px);
  mix-blend-mode: multiply;
}

.difference {
  background: white;
  mix-blend-mode: difference;
  backdrop-filter: invert(1);
}

Rendimiento y fallbacks

backdrop-filter es GPU-intensive, especialmente con radios de blur grandes. Proporciona un fallback de background sólido para navegadores sin soporte, luego mejora con @supports. Evita animar backdrop-filter — puede causar jank severo.

css
/* Fallback for unsupported browsers */
.glass {
  background: rgba(255, 255, 255, 0.9);
}

/* Progressive enhancement */
@supports (backdrop-filter: blur(10px)) {
  .glass {
    background: rgba(255, 255, 255, 0.2);
    backdrop-filter: blur(10px);
  }
}

Tarjeta glassmorphism

Una tarjeta glassmorphism clásica combina un background translúcido, backdrop blur, un border ligero sutil (simulando reflexión de edge) y una sombra suave. El filter saturate boostea los colores vistos a través del vidrio.

css
.card {
  background: rgba(255, 255, 255, 0.15);
  backdrop-filter: blur(16px) saturate(180%);
  -webkit-backdrop-filter: blur(16px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 16px;
  padding: 24px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
}

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.