Centrar cualquier cosa
Funciona en todosDos líneas y centrado en los dos ejes. Sirve igual para un icono que para un modal.
.centro {
display: grid;
place-items: center;
}Lo que copio una y otra vez de proyecto en proyecto. Cada receta tiene la demo funcionando al lado: si en tu navegador no se ve, es que aún no lo soporta.
Dos líneas y centrado en los dos ejes. Sirve igual para un icono que para un modal.
.centro {
display: grid;
place-items: center;
}Tantas columnas como quepan, sin media queries. El min() evita que se salga en móviles estrechos.
.rejilla {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(min(100%, 12rem), 1fr));
gap: 1rem;
}Con poco contenido el pie no se queda flotando a media pantalla. dvh tiene en cuenta la barra del navegador del móvil.
body {
min-height: 100dvh;
display: grid;
grid-template-rows: auto 1fr auto;
}Se acabó el truco del padding-top: 56.25%. La imagen es cuadrada y se ve en 16:9: object-fit: cover la recorta en vez de deformarla (va en img o video, en un div no hace nada).
.portada {
aspect-ratio: 16 / 9;
width: 100%;
object-fit: cover;
}El componente cambia según el sitio que tiene, no según la pantalla. Arrastra la esquina de la caja para verlo.
.tarjeta-wrap { container-type: inline-size; }
.tarjeta { display: grid; gap: 1rem; }
@container (min-width: 22rem) {
.tarjeta { grid-template-columns: 6rem 1fr; }
}Títulos de distinta longitud y aun así todos los botones a la misma altura.
.tarjetas {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
gap: 1rem;
}
.tarjetas > * {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid;
}scroll-snap hace que cada tarjeta encaje al soltar. Con el dedo o con el trackpad.
.carrusel {
display: flex;
gap: 1rem;
overflow-x: auto;
scroll-snap-type: x mandatory;
overscroll-behavior-x: contain;
}
.carrusel > * {
flex: 0 0 80%;
scroll-snap-align: center;
}Con una cabecera fija, el título al que saltas queda tapado. scroll-margin lo baja lo justo.
html { scroll-behavior: smooth; }
:target,
h2[id] {
scroll-margin-top: 5rem;
}
@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
}Lleva prefijo -webkit- pero funciona en todos. Para una sola línea, text-overflow: ellipsis.
.tres-lineas {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}Sin esa última línea con una palabra suelta. pretty hace lo mismo para párrafos, donde aún no llega, simplemente no hace nada.
h1, h2, h3 {
text-wrap: balance;
}
p {
text-wrap: pretty;
}Con color: transparent a secas, un navegador que no sepa recortar el fondo dejaría el texto invisible. Dentro de @supports solo se aplica donde funciona; en el resto se ve en morado liso.
.degradado {
color: #663399;
}
@supports (background-clip: text) or (-webkit-background-clip: text) {
.degradado {
background: linear-gradient(90deg, #663399, #e86f9b);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
}El selector que pedimos durante veinte años. Aquí el campo se marca en rojo cuando lo que escribes no es un email.
.campo:has(input:user-invalid) {
border-color: crimson;
}
.campo:has(input:user-invalid) .error {
display: block;
}Lo de una capa posterior gana siempre, tenga la especificidad que tenga. Adiós a los !important para pisar un framework.
@layer reset, framework, mio;
@layer framework {
#app .boton { background: gray; }
}
@layer mio {
.boton { background: rebeccapurple; }
}El CSS ya anida solo. El & funciona como en Sass, y las media queries pueden ir dentro.
.boton {
background: rebeccapurple;
color: white;
&:hover { background: color-mix(in oklch, rebeccapurple, black 20%); }
.icono { margin-inline-end: .4em; }
@media (width < 30rem) { width: 100%; }
}@starting-style da el punto de partida de algo que acaba de aparecer, y allow-discrete deja animar display. Todo sin JS.
[popover] {
transition: opacity .25s, translate .25s,
display .25s allow-discrete, overlay .25s allow-discrete;
opacity: 0;
translate: 0 1rem;
}
[popover]:popover-open {
opacity: 1;
translate: 0 0;
@starting-style {
opacity: 0;
translate: 0 1rem;
}
}Una animación que avanza con el scroll en lugar de con el tiempo. Sin IntersectionObserver. Haz scroll dentro de la demo.
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
.aparece {
animation: aparece linear both;
animation-timeline: view();
animation-range: entry 0% cover 30%;
}
}
}
@keyframes aparece {
from { opacity: 0; scale: .8; }
}Hay gente a la que las animaciones marean de verdad. Esto las apaga casi del todo si lo han pedido en el sistema.
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: .01ms !important;
animation-iteration-count: 1 !important;
transition-duration: .01ms !important;
scroll-behavior: auto !important;
}
}Cada color dice sus dos versiones en una línea, y color-scheme hace que formularios y scrollbars acompañen.
:root {
color-scheme: light dark;
--fondo: light-dark(#fbfaf7, #17121f);
--texto: light-dark(#221a2e, #ece6f5);
--acento: light-dark(rebeccapurple, #c9a7f2);
}
body {
background: var(--fondo);
color: var(--texto);
}Un solo color de marca y el hover, el activo y el fondo suave salen de él. Mezclar en oklch da tonos más limpios.
.boton {
--c: rebeccapurple;
background: var(--c);
}
.boton:hover { background: color-mix(in oklch, var(--c), white 15%); }
.boton:active { background: color-mix(in oklch, var(--c), black 20%); }
.suave { background: color-mix(in oklch, var(--c) 12%, transparent); }Checkboxes, radios, sliders y barras de progreso nativos, con tu color y sin rehacerlos.
:root {
accent-color: rebeccapurple;
}Nunca quites el outline sin más. Con :focus-visible el anillo sale al tabular y no al hacer clic.
:focus-visible {
outline: 3px solid rebeccapurple;
outline-offset: 3px;
border-radius: 4px;
}Para el texto de un botón que solo es un icono. display: none lo escondería también para quien no ve.
.solo-lectores {
position: absolute;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}