CSS Básico

CSS (Cascading Style Sheets) es el lenguaje que define la apariencia visual de cada elemento HTML en la web. Si HTML es la estructura—los huesos—, CSS es la piel, la ropa, el maquillaje. Sin CSS, todos los sitios web se verían como documentos de texto planos con fondo blanco y fuente por defecto. Dominar los fundamentos de CSS es absolutamente esencial antes de pasar a layout avanzado, animaciones o frameworks, porque todo lo demás se construye sobre estos cimientos.

Selectores

Los selectores son el mecanismo que usa CSS para apuntar a elementos HTML específicos y aplicarles estilos. Un selector define qué elementos se ven afectados por una regla. Hay muchos tipos de selectores, desde los más simples (por etiqueta) hasta los más complejos (combinadores, pseudo-clases, pseudo-elementos). Entender bien los selectores te permite escribir CSS más limpio, específico y mantenible, evitando recurrir a clases innecesarias o al sobreuso de !important.

Selectores básicos

Los selectores básicos son los que vas a usar con más frecuencia en cualquier proyecto. El selector de tipo (o elemento) apunta a todas las instancias de una etiqueta HTML. El selector de clase (precedido por un punto) apunta a elementos con ese atributo class específico. El selector de ID (precedido por #) apunta a un único elemento con ese atributo id. El selector universal (*) aplica estilos a absolutamente todos los elementos del DOM.

CSS
/* Selector de tipo (elemento) */
p {
    color: var(--text-primary);
    line-height: 1.6;
}

/* Selector de clase */
.card {
    background: var(--bg-secondary);
    border-radius: 12px;
    padding: 1.5rem;
}

/* Selector de ID */
#site-header {
    position: sticky;
    top: 0;
    z-index: 100;
}

/* Selector universal */
* {
    box-sizing: border-box;
}

/* Selectores de atributo */
input[type="email"] {
    border-color: var(--accent-blue);
}

a[href^="https"]::after {
    content: " \2197";
}
Demo interactivo
div.item.special

p.item

span.item.special
div.item
div.item.active
Hacé click en un selector para ver qué elementos selecciona.

Selectores compuestos y combinadores

Los selectores compuestos combinan múltiples selectores simples para ser más específicos. Puedes encadenar clases (.card.active), usar el combinador descendiente (espacio: .nav a), el combinador hijo directo (>), el combinador de hermano adyacente (+) y el general (~). Estos combinadores te permiten apuntar a elementos según su relación estructural en el DOM, lo cual es clave para estilar componentes complejos sin agregar clases extra.

CSS
/* Combinador descendiente: todos los 'a' dentro de .nav */
.nav a {
    color: var(--text-secondary);
    text-decoration: none;
}

/* Combinador hijo directo: solo 'li' directos de 'ul' */
.menu > li {
    display: inline-block;
}

/* Selector compuesto: elemento que tiene ambas clases */
.card.active {
    border-color: var(--accent-cyan);
    box-shadow: 0 0 0 2px var(--accent-cyan);
}

/* Hermano adyacente: p inmediatamente despues de h2 */
h2 + p {
    font-size: 1.125rem;
    color: var(--text-secondary);
}

/* Hermano general: todos los p despues de h2 */
h2 ~ p {
    margin-top: 0.5rem;
}

/* Selector :not() - negacion */
input:not([disabled]) {
    cursor: pointer;
}

/* :is() - simplifica selectores largos */
:is(h2, h3, h4):hover {
    color: var(--accent-blue);
}

/* :has() - seleccionar segun descendientes */
.card:has(img) {
    grid-row: span 2;
}

Pseudo-clases y pseudo-elementos

Las pseudo-clases (:) seleccionan elementos según un estado o condición dinámica, como :hover, :focus, :first-child, :nth-child(), o :checked. Los pseudo-elementos (::) crean elementos virtuales que no existen en el HTML, como ::before, ::after, ::first-line, o ::placeholder. Juntos te permiten estilar estados interactivos, contenido generado y partes específicas de un elemento sin modificar el HTML.

A continuación, una referencia rápida de las pseudo-clases y pseudo-elementos más usados, con el contexto de cada uno: en qué elementos aplican, para qué sirven y cuándo conviene usarlos. Las pseudo-clases de interacción son fundamentales para la experiencia de usuario, mientras que las estructurales permiten estilar listas, tablas y grids sin agregar clases extra al HTML.

Pseudo-clase / Pseudo-elemento Qué hace Cuándo usarla
:hover Se activa cuando el cursor del mouse está sobre el elemento. Botones, enlaces, cards, filas de tablas—cualquier elemento interactivo. Es la pseudo-clase más usada para feedback visual inmediato.
:focus Se activa cuando un elemento recibe el foco (click, tab, touch). Inputs, textareas, selects, botones—indica qué elemento está activo. Nota: :focus-visible es preferible porque solo muestra el outline al navegar con teclado.
:focus-visible Como :focus, pero solo se activa al navegar con teclado. Botones, enlaces y cualquier elemento focusable. Recomendada para accesibilidad: muestra el outline para usuarios de teclado sin el anillo visual al hacer click con mouse.
:active Se activa en el momento exacto del click (mouse apretado). Botones—da sensación de "presionado". Se combina con :hover para un efecto completo: hover → active → normal.
:visited Se aplica a enlaces que el usuario ya visitó. Links (<a> con href). Permite diferenciar enlaces ya visitados. Por seguridad solo permite cambiar color, background-color, border-color y outline-color.
:first-child Selecciona el primer hijo de su padre. Listas (<li>), filas de tablas (<tr>), cards en un grid. Ideal para eliminar el borde/margen superior del primer ítem y evitar bordes dobles.
:last-child Selecciona el último hijo de su padre. Contraparte de :first-child. Se usa para eliminar el borde/margen inferior del último ítem de una lista o grid.
:nth-child(n) Selecciona hijos según su posición con una fórmula. Tablas (zebra-striping con even/odd), grids (cada N-ésimo ítem), sliders. Acepta números, palabras clave y fórmulas como :nth-child(3n+1).
:first-of-type Selecciona el primer hermano de su tipo. Distinto de :first-child: selecciona el primer <p> dentro de un contenedor sin importar si hay otros elementos antes. Útil cuando los hijos no son todos del mismo tipo.
:last-of-type Selecciona el último hermano de su tipo. Misma lógica que :first-of-type pero para el último. Ideal para estilar el último párrafo de un artículo, por ejemplo.
:not(selector) Selecciona elementos que NO coinciden con el selector dado. Excluir deshabilitados (:not([disabled])), excluir la primera fila de una tabla, excluir clases específicas. Reduce la necesidad de reglas de "override".
:checked Selecciona checkboxes y radios marcados. Inputs tipo checkbox y radio. Se usa para custom checkboxes/radios, o para estilar un label contenedor según el estado del checkbox asociado.
:disabled Selecciona elementos de formulario deshabilitados. Inputs, botones, selects con atributo disabled. Da un estilo visual claro (opacidad reducida, cursor not-allowed) que indica que no se puede interactuar.
:empty Selecciona elementos que no tienen hijos (ni texto). Debugging—resalta elementos vacíos que no deberían estarlo. También útil para ocultar contenedores vacíos en layouts dinámicos.
::before Crea un pseudo-elemento como primer hijo del elemento. Requiere content: "". Se usa para decoraciones (barras, íconos), breadcrumbs (separadores), tooltips y overlays. No existe en el DOM pero sí es visible.
::after Crea un pseudo-elemento como último hijo del elemento. Mismos usos que ::before pero después del contenido. Muy usado para clearfix, labels flotantes (content: attr(data-label)) e iconos decorativos.
::placeholder Estiliza el texto placeholder de inputs y textareas. Inputs y textareas con atributo placeholder. Permite cambiar color, opacidad y fuente del texto de ayuda que desaparece al escribir.
::selection Estiliza el texto que el usuario selecciona con el cursor. Se aplica global (::selection) o a elementos específicos. Permite personalizar el color de fondo y texto de la selección para que matchee tu branding.
::first-line Estiliza la primera línea de texto de un elemento block. Artículos y párrafos largos. Permite hacer un "lead" tipográfico sin <span> extra. Solo acepta propiedades de tipografía y color.
::first-letter Estiliza la primera letra de un elemento block. Drop caps (letrina)—la primera letra grande y decorativa en artículos. Solo acepta propiedades tipográficas, color, background, border, margin y padding.
Demo interactivo
1
2
3
4
5
6
7
8
9
10
11
12
CSS
/* Pseudo-clases de interaccion */
.btn:hover {
    transform: translateY(-2px);
}

.btn:focus-visible {
    outline: 2px solid var(--accent-cyan);
    outline-offset: 2px;
}

/* Pseudo-clases estructurales */
li:first-child {
    border-top: none;
}

li:last-child {
    border-bottom: none;
}

/* nth-child: patrones repetitivos */
tr:nth-child(even) {
    background: var(--bg-tertiary);
}

tr:nth-child(odd) {
    background: transparent;
}

/* nth-child con formula: cada 3er elemento */
.grid-item:nth-child(3n) {
    break-after: column;
}

/* Pseudo-elementos */
.card::before {
    content: "";
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 3px;
    background: linear-gradient(90deg, var(--accent-blue), var(--accent-cyan));
}

.card::after {
    content: attr(data-label);
    position: absolute;
    top: -10px;
    right: -10px;
    background: var(--accent-green);
    color: #000;
    font-size: 0.75rem;
    padding: 2px 8px;
    border-radius: 4px;
}

/* ::placeholder para inputs */
input::placeholder {
    color: var(--text-muted);
    font-style: italic;
}

/* ::selection para texto seleccionado */
::selection {
    background: var(--accent-blue);
    color: #fff;
}

Cuándo usar cada selector

El selector de tipo es útil para resets y estilos base (ej: body, a, img). Las clases son tu herramienta principal—reutilizables, composables y predecibles. Los IDs deberían evitarse en CSS (son para JavaScript y enlaces internos) porque tienen especificidad máxima y no se pueden reutilizar. El universal * se usa para box-sizing y margenes/paddings a cero en resets, pero evitálo en reglas específicas porque afecta a todo el DOM.

Box Model

El Box Model es el concepto fundamental que define cómo el navegador calcula el tamaño total de cada elemento. Cada elemento HTML es tratado como una caja rectangular con cuatro capas concéntricas: content (el contenido real), padding (espacio entre el contenido y el borde), border (la línea del borde) y margin (espacio entre el borde y otros elementos). Entender cómo interactúan estas capas es esencial para controlar el layout sin sorpresas.

El detalle crítico es la diferencia entre content-box (el modelo clásico de CSS2) y border-box (el modelo moderno). Con content-box, el ancho que definís es solo el del contenido—el padding y el border se suman por fuera, haciendo el elemento más grande de lo que esperás. Con border-box, el ancho incluye padding y border, lo que es mucho más intuitivo. La práctica estándar es aplicar box-sizing: border-box a todos los elementos con el selector universal.

Visual
300 × 150
margin
border
padding
content
CSS
/* Regla de oro: aplicar a todo el proyecto */
*,
*::before,
*::after {
    box-sizing: border-box;
}

/* El ancho total del elemento ES el ancho que definis */
.card {
    width: 300px;
    padding: 1.5rem;
    border: 2px solid var(--border-color);
    /* Ancho total = 300px (content + padding + border) */
}

/* Con content-box (default viejo), width es solo el contenido */
.legacy-box {
    box-sizing: content-box;
    width: 300px;
    padding: 1.5rem;  /* 24px cada lado = 48px extra */
    border: 2px solid; /* 4px extra */
    /* Ancho total = 300 + 48 + 4 = 352px (!) */
}

/* Margin: espacio exterior entre elementos */
.card + .card {
    margin-top: 1.5rem;
}

/* Padding: espacio interior del elemento */
.card {
    padding: 1rem;       /* todos los lados */
    padding: 1rem 2rem;   /* arriba/abajo | izq/der */
    padding: 1rem 2rem 3rem; /* arriba | izq/der | abajo */
    padding: 1rem 2rem 3rem 4rem; /* arriba | der | abajo | izq */
}

/* Shorthand de margin (misma logica) */
.spacing-demo {
    margin: 0 auto;       /* 0 arriba/abajo, centrado horizontal */
}

/* Colapso de margenes: solo verticales */
p {
    margin-top: 1rem;
    margin-bottom: 1rem;
    /* Entre dos parrafos, el gap NO es 2rem sino 1rem (colapsan) */
}

El colapso de márgenes verticales

Los márgenes verticales (margin-top y margin-bottom) entre elementos hermanos se colapsan: en lugar de sumarse, el navegador usa el mayor de los dos. Si un <p> tiene margin-bottom: 2rem y el siguiente tiene margin-top: 1rem, el espacio entre ellos será 2rem, no 3rem. Esto no aplica a márgenes horizontales ni a flex/grid. Es la razón principal por la que muchos devs prefieren gap en layouts modernos.

Especificidad

La especificidad es el algoritmo que el navegador usa para decidir qué regla CSS gana cuando hay conflictos (múltiples reglas apuntando al mismo elemento con propiedades diferentes). No es simplemente "el último gana"—eso solo aplica si la especificidad es igual. Cada tipo de selector tiene un peso distinto, y el navegador calcula un "score" para cada regla. El selector con el score más alto se impone, sin importar el orden de declaración.

El sistema de puntuación se puede pensar como tres columnas: (inline, ID, clase | elemento). Los estilos inline (style="") valen más que cualquier selector CSS. Los IDs (&code>#) valen más que las clases (.). Las clases valen más que los elementos (p, div). Y !important rompe toda la cascada (pero no deberías usarlo).

Selector Especificidad Ejemplo
Universal 0, 0, 0 *
Tipo / Elemento 0, 0, 1 p, div, a
Pseudo-elemento 0, 0, 1 ::before, ::after
Clase / Atributo / Pseudo-clase 0, 1, 0 .card, [type="email"], :hover
ID 1, 0, 0 #header
Inline style 1, 0, 0, 0 style="color: red"
!important Gana a todo (anti-patrón) color: red !important
CSS
/* Ejemplo de conflicto de especificidad */

/* Especificidad: 0, 0, 1 */
p {
    color: blue;
}

/* Especificidad: 0, 1, 0 (gana!) */
.text-error {
    color: red;
}

/* Especificidad: 0, 1, 1 (gana a la clase sola!) */
p.text-error {
    color: darkred;
}

/* Especificidad: 1, 0, 0 (gana a todo lo anterior!) */
#unique-paragraph {
    color: green;
}

/* Malas practicas con especificidad */
.card .card .card .card .card .item {
    /* 0, 6, 1 - muy alta sin necesitarlo */
    /* Si necesitas !important para sobreescribir algo,
       tu especificidad esta mal diseñada */
}

/* Buena practica: manteni especificidad baja y plana */
.card-item {
    /* 0, 1, 0 - facil de sobreescribir cuando haga falta */
}

Evitá !important

El uso de !important rompe la cascada natural de CSS y genera una "guerra de especificidades" donde cada nueva regla necesita otro !important para ganar. Si te ves forzado a usarlo, es una señal de que tu arquitectura CSS necesita refactorizarse. Las únicas excepciones aceptables son en resets y utility classes que deliberadamente necesitan ganar siempre. La mejor estrategia: mantener la especificidad plana y baja, y si necesitas sobreescribir, agrega una clase extra o usa la cascada (orden de declaración).

Display

La propiedad display determina cómo se comporta un elemento en el flujo del documento: si ocupa toda la línea, si fluye con el texto, si se oculta, o si se convierte en un contenedor flex o grid. Es una de las propiedades más importantes de CSS porque define el comportamiento fundamental de cada elemento antes de que cualquier otra propiedad de layout tenga efecto.

Los valores principales son block (ocupa toda la línea, respeta width/height), inline (fluye con el texto, ignora width/height), inline-block (fluye con texto pero respeta dimensiones) y none (oculta el elemento completamente, lo saca del DOM visual). Además están flex y grid, que tienen sus propias secciones detalladas en este sitio. Cada elemento HTML tiene un display por defecto (<div> es block, <span> es inline, <img> es inline-block).

CSS
/* block: ocupa toda la linea, acepta width/height */
/* Default: div, p, h1-h6, ul, ol, li, section, article */
.sidebar {
    display: block;
    width: 280px;
}

/* inline: fluye con el texto, NO acepta width/height */
/* Default: span, a, strong, em, code */
.tag {
    display: inline;
    /* width y height son ignorados */
}

/* inline-block: fluye con texto PERO acepta dimensiones */
/* Default: img, input, button */
.avatar {
    display: inline-block;
    width: 48px;
    height: 48px;
    border-radius: 50%;
    /* Se comporta como inline (no ocupa toda la linea)
       pero respeta width/height como block */
}

/* none: oculta completamente el elemento */
.mobile-only {
    display: none;
}

/* Mostrar en mobile, ocultar en desktop */
@media (max-width: 768px) {
    .mobile-only {
        display: block;
    }
    .desktop-only {
        display: none;
    }
}

/* flex y grid: secciones dedicadas */
.container {
    display: flex;
}

.layout {
    display: grid;
}

/* Valores modernos menos comunes */
.flow-root {
    /* Crea un nuevo BFC (Block Formatting Context)
       Contiene floats sin necesidad de clearfix */
    display: flow-root;
}

/* display: contents - el elemento "desaparece"
   y sus hijos se comportan como hijos del padre */
.grid-item-contents {
    display: contents;
}
Valor Comportamiento Acepta width/height?
block Ocupa toda la línea, empieza nueva línea Si
inline Fluye con el texto, comparte línea No
inline-block Fluye con texto, pero respeta dimensiones Si
flex Contenedor flex (hijos = flex items) Si
grid Contenedor grid (hijos = grid items) Si
none Oculta el elemento (no ocupa espacio) No aplica
contents El elemento desaparece, sus hijos ascienden No aplica

display: none vs visibility: hidden vs opacity: 0

Tres formas de ocultar, tres comportamientos distintos: display: none saca el elemento del flujo (no ocupa espacio, no recibe eventos). visibility: hidden lo oculta pero sigue ocupando espacio. opacity: 0 es transparente pero sigue ocupando espacio y recibe eventos (puede hacerse click). Para animaciones de entrada/salida, usá opacity con transform y pointer-events: none. Para ocultar accesiblemente, usá la clase .sr-only (screen reader only) que posiciona fuera de pantalla sin display: none.

Position

La propiedad position controla cómo un elemento es posicionado dentro del documento y en relación con otros elementos. Es la base para crear headers sticky, tooltips, modales, menúes desplegables y cualquier layout que necesite elementos fuera del flujo normal. Cada valor de position habilita propiedades específicas (top, right, bottom, left) que funcionan de manera distinta según el contexto.

CSS
/* static (default): flujo normal, top/left/ bottom/right no tienen efecto */
.element {
    position: static;
}

/* relative: se mueve respecto a su posicion normal
   Sigue ocupando su espacio original en el flujo */
.card {
    position: relative;
    /* top/left lo desplazan desde donde estaria normalmente */
}

.card:hover {
    top: -4px;    /* se mueve 4px para arriba */
    left: 2px;    /* se mueve 2px para la derecha */
}

/* absolute: se posiciona respecto al ancestro posicionado mas cercano
   Si no hay ninguno, usa el viewport (html) */
.card .badge {
    position: absolute;
    top: 10px;
    right: 10px;
    /* Se posiciona dentro de .card porque .card tiene position: relative */
}

/* fixed: se posiciona respecto al viewport (no se mueve con scroll)
   Ideal para headers, botones flotantes, toasts */
.site-header {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    z-index: 100;
}

.back-to-top {
    position: fixed;
    bottom: 2rem;
    right: 2rem;
    /* Siempre visible en la esquina inferior derecha */
}

/* sticky: se comporta como relative hasta un punto de scroll,
   luego se "pega" como si fuera fixed
   Necesita al menos un top/bottom declarado */
.table-header th {
    position: sticky;
    top: 0;         /* Se pega cuando el scroll llega arriba */
    z-index: 10;
    background: var(--bg-secondary);
}

/* z-index: controla el orden de apilamiento (solo funciona con position) */
.tooltip {
    position: absolute;
    z-index: 50;    /* Por encima de los demas elementos */
}

/* Nota: z-index solo funciona en elementos posicionados
   (position distinto de static) o en elementos flex/grid */

Patrón position: relative + absolute

Este es uno de los patrones más usados en CSS. Al poner position: relative en un contenedor, creas un "contexto de posicionamiento" para sus hijos. Luego podés usar position: absolute en cualquier hijo para posicionarlo exactamente dentro de ese contenedor. Es ideal para badges sobre cards, tooltips sobre botones, overlays sobre imágenes y menúes desplegables. Sin el relative en el padre, el absolute del hijo se escaparía al ancestro posicionado más cercano (o al viewport).

Overflow

La propiedad overflow controla qué pasa cuando el contenido de un elemento es más grande que su contenedor. Tiene cuatro valores principales: visible (el contenido se desborda y es visible, es el default), hidden (el contenido que excede se corta sin indicador), scroll (siempre muestra barras de scroll) y auto (muestra scroll solo cuando es necesario). Puedes controlar los ejes independientemente con overflow-x y overflow-y.

overflow: hidden es especialmente útil para crear efectos de recorte (clip), contenedores de slideshows, o resolver problemas de margin colapso (crea un nuevo Block Formatting Context). Sin embargo, tené cuidado: overflow: hidden corta no solo el contenido visible sino también los shadows, tooltips y dropdowns que se extiendan fuera del contenedor, lo que puede ser una fuente de bugs difíciles de rastrear.

CSS
/* visible (default): el contenido se desborda normalmente */
.overflow-visible {
    overflow: visible;
}

/* hidden: corta el contenido que excede */
.card-image {
    overflow: hidden;
    height: 200px;
    /* La imagen se corta si es mas alta que 200px */
}

/* scroll: siempre muestra barras (incluso si no es necesario) */
.code-block {
    overflow: scroll;
    /* Barras siempre visibles - poco usado hoy */
}

/* auto: scroll solo cuando es necesario (el mas comun) */
.code-block {
    overflow: auto;
    /* Scroll horizontal si el codigo es largo,
       scroll vertical si el contenido excede la altura */
}

/* Control por eje */
.container {
    overflow-x: auto;   /* Scroll horizontal para carousel */
    overflow-y: hidden;  /* Sin scroll vertical */
}

/* Texto largo en una linea con scroll horizontal */
.text-nowrap {
    white-space: nowrap;
    overflow-x: auto;
}

/* Crear un BFC con overflow para contener floats */
.clearfix-modern {
    overflow: hidden; /* Crea un Block Formatting Context */
}

/* Clip de imagenes con border-radius */
.rounded-image {
    overflow: hidden;
    border-radius: 12px;
}

/* Contenedor de carousel horizontal */
.carousel {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    gap: 1rem;
}

.carousel-item {
    scroll-snap-align: start;
    flex: 0 0 80%;
}

Float y Clear

float fue la primera herramienta de layout en CSS, diseñada originalmente para que el texto fluya alrededor de imágenes (como en periódicos). Antes de Flexbox y Grid, era la técnica principal para crear layouts multi-columna. Hoy en día, su uso para layout está completamente obsoleto—Flexbox y Grid son superiores en todos los aspectos. Sin embargo, float sigue siendo útil para su propósito original: texto alrededor de imágenes y layouts editorial-like.

El problema clásico de float es que el contenedor padre no "ve" la altura de sus hijos flotados—se colapsa como si estuviera vacío. Esto se solucionaba con el "clearfix hack" (un pseudo-elemento ::after con clear: both) o con overflow: hidden en el padre. Hoy, la alternativa moderna para crear un BFC es display: flow-root, que hace lo mismo sin los efectos secundarios de overflow: hidden.

CSS
/* Uso moderno de float: texto alrededor de imagen */
.article-image {
    float: left;
    width: 250px;
    margin: 0 1.5rem 0.5rem 0;
}

.article-image.right {
    float: right;
    margin: 0 0 0.5rem 1.5rem;
}

/* clear: impedir que un elemento se ubique al lado de un float */
.clear-below {
    clear: both;    /* ambos lados */
    clear: left;   /* solo izquierdo */
    clear: right;  /* solo derecho */
}

/* Clearfix hack (solucion clasica para el colapso del padre) */
.clearfix::after {
    content: "";
    display: table;
    clear: both;
}

/* Solucion moderna: flow-root (reemplaza clearfix) */
.container-moderno {
    display: flow-root;
    /* Crea un BFC que contiene a los floats
       Sin efectos secundarios de overflow: hidden */
}

/* NO uses float para layout - usa flex o grid */
.antipatron {
    /* float: left;       NO */
    /* width: 33.333%;    NO */
    /* Usa grid en su lugar: */
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1rem;
}

/* image-rendering para imagenes flotadas */
img.float-img {
    float: left;
    shape-outside: circle();  /* El texto rodea la forma circular */
    border-radius: 50%;
    margin-right: 1rem;
}

shape-outside: float avanzado

La propiedad shape-outside permite que el texto fluya alrededor de formas no rectangulares cuando usas float. Con shape-outside: circle(50%) y border-radius: 50% en una imagen, el texto rodeará perfectamente el círculo en lugar del recuadro rectangular. Puedes usar circle(), ellipse(), inset() y hasta polygon() para formas personalizadas. Es la única razón legítima para usar float en layouts modernos.

Propiedades Shorthand Comunes

Las propiedades shorthand permiten declarar múltiples valores relacionados en una sola línea, haciendo tu CSS más conciso y fácil de leer. CSS tiene docenas de shorthands, pero hay algunas que vas a usar constantemente en cualquier proyecto. Entender el orden de los valores y los valores por defecto de cada shorthand es esencial para no sorprenderte con resultados inesperados.

CSS
/* === margin === */
/* 1 valor: todos los lados */
margin: 1rem;              /* top right bottom left = 1rem */

/* 2 valores: vertical | horizontal */
margin: 1rem 2rem;         /* top/bottom = 1rem, left/right = 2rem */

/* 3 valores: top | horizontal | bottom */
margin: 1rem 2rem 3rem;    /* top=1rem, left/right=2rem, bottom=3rem */

/* 4 valores: top | right | bottom | left (sentido horario) */
margin: 1rem 2rem 3rem 4rem;

/* === padding: mismo orden que margin === */
padding: 1rem 2rem;

/* === border: width style color === */
border: 1px solid var(--border-color);
border-top: 2px dashed var(--accent-blue);
border-radius: 8px;
border-radius: 50% 0 50% 0;  /* esquinas individuales */

/* === background: color image position/size repeat === */
background: var(--bg-primary);
background: url("img.jpg") center/cover no-repeat;
/* Es lo mismo que:
   background-color: var(--bg-primary);
   background-image: url("img.jpg");
   background-position: center;
   background-size: cover;
   background-repeat: no-repeat; */

/* === font: style weight size/line-height family === */
font: italic 600 1.5rem/1.6 "Inter", sans-serif;
/* Nota: font-size y font-family son obligatorios en el shorthand */

/* === transition: property duration timing-function delay === */
transition: transform 0.3s ease 0s;
transition: all 0.3s ease;       /* atencion: all puede generar problemas */
transition: transform 0.3s ease, opacity 0.3s ease;

/* === animation: name duration timing-function delay iteration-count direction fill-mode === */
animation: fadeIn 0.6s ease forwards;

/* === flex shorthand (en el item, no el contenedor) === */
flex: 1;              /* flex-grow: 1; flex-shrink: 1; flex-basis: 0% */
flex: 0 0 200px;      /* no crece, no se achica, mide 200px */
flex: 1 1 auto;       /* crece, se achica, tamano del contenido */

/* === list-style: type position image === */
list-style: square inside;
list-style: none;      /* quitar bullets */

/* === outline (para focus, debugging) === */
outline: 2px solid var(--accent-cyan);
outline-offset: 2px;

Regla mnemotécnica: TRouBLe (Top, Right, Bottom, Left)

Cuando usas 4 valores en margin, padding o border-radius, el orden es siempre en sentido horario empezando desde arriba: Top, Right, Bottom, Left. Con 2 valores es vertical primero, horizontal después. Con 3 valores es top, horizontal, bottom (left copia right). Una vez que internalizás este patrón, escribir CSS se vuelve mucho más rápido.

Probá en MiniDevTools

Si querés experimentar con lo que vimos en esta sección, probá Shadow Generator, Border Radius, Gradient Generator, Color Palette o Image Color Picker.