En esta página
CSS moderno en 2026
El estado de CSS en 2026
CSS ha evolucionado drasticamente en los ultimos años. Funcionalidades que antes requerian preprocesadores o JavaScript ahora son nativas del navegador. Esta leccion cubre las adiciones más importantes.
CSS Nesting nativo
Ya no necesitas Sass o Less para anidar selectores. CSS nativo soporta nesting:
.nav {
display: flex;
gap: 1rem;
& a {
color: inherit;
text-decoration: none;
&:hover {
color: #b056ff;
}
}
@media (width < 768px) {
flex-direction: column;
}
}El & representa al selector padre. Para pseudo-clases y pseudo-elementos, el & es opcional. Para selectores de tipo (p, h1), es obligatorio.
El selector :has()
:has() es el "selector padre" que CSS nunca tuvo. Permite estilizar un elemento basandose en lo que contiene:
/* Card que contiene imagen: diferente layout */
.card:has(img) {
display: grid;
grid-template-rows: auto 1fr;
}
/* Formulario con campos invalidos: boton deshabilitado */
form:has(:invalid) button[type="submit"] {
opacity: 0.5;
pointer-events: none;
}
/* Sidebar abierta: ajustar main */
body:has(.sidebar.abierta) .main {
margin-inline-start: 280px;
}Casos de uso comunes de :has()
- Validación visual de formularios sin JavaScript
- Layouts condicionales segun el contenido
- Estados globales (sidebar abierta, modal visible)
- Estilos de hermanos (
.item:has(+ .item-activo))
La función light-dark()
Simplifica enormemente la tematizacion light/dark:
:root {
color-scheme: light dark;
}
body {
background: light-dark(#fff, #0a0a0f);
color: light-dark(#1a1a2e, #e8e8e8);
}Ya no necesitas duplicar bloques con @media (prefers-color-scheme: dark) para cada propiedad.
@scope: encapsulacion de estilos
@scope limita el alcance de los estilos a un arbol DOM específico, evitando colisiones:
@scope (.panel) {
h2 { font-size: 1.5rem; }
p { color: #555; }
}Con el limite inferior (to), puedes excluir partes del arbol:
@scope (.panel) to (.panel__slot) {
/* Estilos que NO afectan al contenido del slot */
p { margin: 0; }
}Anchor positioning
Posicionar tooltips, popovers y menus desplegables relativo a un elemento ancla, sin JavaScript:
.trigger {
anchor-name: --mi-boton;
}
.popover {
position: fixed;
position-anchor: --mi-boton;
top: anchor(bottom);
left: anchor(center);
translate: -50% 0.5rem;
}Funciones matematicas avanzadas
CSS ahora incluye funciones matematicas completas:
| Función | Uso |
|---|---|
round() |
Redondear valores |
mod() |
Módulo (resto) |
rem() |
Resto con signo del dividendo |
abs() |
Valor absoluto |
sign() |
Signo del valor (-1, 0, 1) |
pow(), sqrt(), log() |
Operaciones avanzadas |
sin(), cos(), tan() |
Funciones trigonometricas |
.elemento {
/* Redondear al multiplo más cercano de 4px */
padding: round(nearest, 1.3rem, 4px);
}Scroll-driven animations
Animar elementos basandose en la posición del scroll, sin JavaScript:
@keyframes aparecer {
from { opacity: 0; scale: 0.9; }
to { opacity: 1; scale: 1; }
}
.sección {
animation: aparecer linear both;
animation-timeline: view();
animation-range: entry 10% entry 40%;
}view() crea una timeline basada en cuando el elemento entra y sale del viewport.
View Transitions API
Transiciones animadas entre vistas de una SPA o entre páginas:
@view-transition {
navigation: auto;
}
::view-transition-old(root) {
animation: fade-out 300ms ease;
}
::view-transition-new(root) {
animation: fade-in 300ms ease;
}Estilizando formularios
Los formularios son una de las partes más dificiles de estilizar en CSS. Las nuevas pseudo-clases y propiedades modernas cambian esto por completo.
Estados de validacion: `:valid`, `:invalid`, `:user-invalid`
CSS puede reaccionar al estado de validacion de los campos de formulario:
/* Se aplica SOLO despues de interaccion del usuario */
.campo:user-invalid {
border-color: #e74c3c;
box-shadow: 0 0 0 3px rgb(231 76 60 / 20%);
}
.campo:valid {
border-color: #2ecc71;
}[!TIP] Prefiere
:user-invalidsobre:invalid. La pseudo-clase:invalidmarca los campos como invalidos inmediatamente al cargar la pagina, mientras que:user-invalidespera a que el usuario interactue con el campo.
Etiquetas flotantes con `:placeholder-shown`
El patron de "floating label" se puede lograr sin JavaScript, usando :placeholder-shown y el combinador de hermano adyacente ~:
.grupo-campo {
position: relative;
}
.grupo-campo .campo::placeholder {
color: transparent; /* Oculta el placeholder real */
}
.grupo-campo .etiqueta {
position: absolute;
top: 50%;
left: 0.75rem;
translate: 0 -50%;
transition: all 200ms ease;
color: #888;
pointer-events: none;
}
/* Cuando el campo tiene texto o esta enfocado, la etiqueta sube */
.grupo-campo .campo:not(:placeholder-shown) ~ .etiqueta,
.grupo-campo .campo:focus ~ .etiqueta {
top: 0;
translate: 0 -50%;
font-size: 0.75rem;
background: white;
padding-inline: 0.25rem;
color: #b056ff;
}`accent-color` y `caret-color`
Tematizar los controles nativos del navegador (checkboxes, radios, range sliders) ahora es trivial:
input[type="checkbox"],
input[type="radio"],
input[type="range"] {
accent-color: #b056ff;
}
input, textarea {
caret-color: #b056ff; /* Color del cursor de texto */
}accent-color tiñe el color principal de los controles nativos. El navegador se encarga de los contrastes automaticamente.
`field-sizing: content` para textareas
Una propiedad nueva que permite que los <textarea> crezcan automaticamente segun su contenido:
.textarea-auto {
field-sizing: content;
min-height: 3lh; /* Minimo 3 lineas */
max-height: 10lh; /* Maximo 10 lineas */
}[!INFO] La unidad
lhequivale a la altura de linea (line-height) computada del elemento. Es ideal para definir alturas basadas en lineas de texto.
Personalizar `
/* CSS Nesting nativo (sin preprocesadores) */
.card {
background: white;
border-radius: 12px;
padding: 1.5rem;
border: 1px solid #e0e0e0;
transition: border-color 200ms ease;
&:hover {
border-color: #b056ff;
}
& .título {
font-size: 1.25rem;
font-weight: 600;
margin-block-end: 0.5rem;
}
& .descripción {
color: #666;
line-height: 1.6;
}
/* Nesting de media queries */
@media (width >= 768px) {
padding: 2rem;
}
}
/* Scope: encapsular estilos */
@scope (.panel) to (.panel__contenido) {
p { color: #333; font-size: 0.9rem; }
a { color: #b056ff; }
}
/* Anchor positioning */
.tooltip-trigger {
anchor-name: --mi-trigger;
}
.tooltip {
position: fixed;
position-anchor: --mi-trigger;
top: anchor(bottom);
left: anchor(center);
translate: -50% 8px;
background: #1a1a2e;
color: white;
padding: 0.5rem 1rem;
border-radius: 6px;
}
/* :has() - el selector padre que faltaba */
.formulario:has(:invalid) .boton-enviar {
opacity: 0.5;
pointer-events: none;
}
/* Card con imagen vs sin imagen */
.card:has(> img) {
grid-template-rows: 200px 1fr;
}
.card:not(:has(> img)) {
padding-block-start: 2rem;
}
/* Estilos basados en estado de checkbox */
.filtro:has(input:checked) {
background: oklch(90% 0.1 260);
border-color: #b056ff;
}
/* Funciones matematicas avanzadas */
.grid-items {
--cols: 3;
display: grid;
grid-template-columns: repeat(var(--cols), 1fr);
gap: round(nearest, 1.5rem, 0.25rem);
}
/* light-dark() para temas */
:root {
color-scheme: light dark;
}
.surface {
background: light-dark(#ffffff, #1a1a2e);
color: light-dark(#1a1a2e, #e8e8e8);
border: 1px solid light-dark(
rgb(0 0 0 / 10%),
rgb(255 255 255 / 10%)
);
}
/* Estados de validación */
.campo:user-invalid {
border-color: #e74c3c;
box-shadow: 0 0 0 3px rgb(231 76 60 / 20%);
}
.campo:valid {
border-color: #2ecc71;
}
/* Etiquetas flotantes con :placeholder-shown */
.grupo-campo {
position: relative;
}
.grupo-campo .campo::placeholder {
color: transparent;
}
.grupo-campo .etiqueta {
position: absolute;
top: 50%;
left: 0.75rem;
translate: 0 -50%;
transition: all 200ms ease;
color: #888;
pointer-events: none;
}
.grupo-campo .campo:not(:placeholder-shown) ~ .etiqueta,
.grupo-campo .campo:focus ~ .etiqueta {
top: 0;
translate: 0 -50%;
font-size: 0.75rem;
background: white;
padding-inline: 0.25rem;
color: #b056ff;
}
/* Tematizar controles nativos */
.campo-checkbox,
.campo-radio,
.campo-rango {
accent-color: #b056ff;
}
.campo {
caret-color: #b056ff;
}
/* Textarea auto-dimensionable */
.textarea-auto {
field-sizing: content;
min-height: 3lh;
max-height: 10lh;
}
/* Select personalizado */
.select-personalizado {
appearance: none;
background: white url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23666' d='M6 8L1 3h10z'/%3E%3C/svg%3E") no-repeat right 0.75rem center;
padding: 0.5rem 2rem 0.5rem 0.75rem;
border: 1px solid #ccc;
border-radius: 6px;
}
/* :focus-visible vs :focus */
.campo:focus-visible {
outline: 2px solid #b056ff;
outline-offset: 2px;
}
.boton:focus-visible {
outline: 2px solid #b056ff;
outline-offset: 2px;
}
/* :focus sin :focus-visible — solo para mouse */
.boton:focus:not(:focus-visible) {
outline: none;
}
Inicia sesión para guardar tu progreso