CSS has never been more powerful — or more exciting. Here is a deep dive into the techniques reshaping modern UI design: glassmorphism, container queries, cascade layers, and the :has() selector.
CSS Has Had a Renaissance — And Most Developers Missed It
CSS of 2025 is barely recognisable compared to CSS of 2019. Container queries, cascade layers, the :has() selector, logical properties, colour functions, nesting, and scroll-driven animations have landed across all major browsers in the last two years. Combined with the continued evolution of design trends like glassmorphism and fluid typography, the design capability available to developers writing pure CSS is extraordinary.
Glassmorphism: The Design Trend That Stayed
Unlike flat design or neumorphism, glassmorphism has proven durable because it works. The frosted-glass aesthetic — translucent backgrounds, backdrop blur, subtle borders, and layered depth — creates interfaces that feel light, modern, and physically intuitive.
The core CSS recipe:
.glass-card {
background: rgba(255, 255, 255, 0.75);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.55);
border-radius: 16px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08),
0 1px 0 rgba(255, 255, 255, 0.7) inset;
}
The inset box-shadow creates the highlight at the top of the card, simulating light refraction through glass. The key design constraint: glassmorphism requires a rich, colourful background — it does not work on white or flat-colour backgrounds.
Container Queries: The Feature Developers Have Wanted for a Decade
Media queries respond to viewport size. Container queries respond to the size of a parent element. This distinction matters enormously for component-based design.
.card-container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 1fr 2fr;
}
}
With container queries, a card component is genuinely responsive regardless of where it is used — in a sidebar, a full-width layout, or a three-column grid. This is the foundation of truly portable components.
The :has() Selector: CSS Gets Parent Awareness
Previously impossible in CSS: style a parent based on its children. :has() changes this fundamentally.
/* Card with an image gets different padding */
.card:has(img) {
padding-top: 0;
}
/* Form that contains an invalid field */
form:has(:invalid) .submit-button {
opacity: 0.5;
cursor: not-allowed;
}
The implications for design systems are profound — components can now adapt their presentation based on their own content without JavaScript.
Cascade Layers for Design System Architecture
Cascade layers (@layer) provide explicit control over the cascade — no more specificity wars. Define your layers in order from lowest to highest priority:
@layer reset, base, tokens, components, utilities, overrides;
Utilities always win over components. Components always win over base styles. No more !important hacks or specificity arithmetic. This transforms the maintainability of large design systems.
Fluid Typography and Spacing with clamp()
Fluid typography scales smoothly between viewport sizes without breakpoints:
h1 {
font-size: clamp(2rem, 5vw + 1rem, 4.5rem);
}
The clamp() function takes minimum, preferred, and maximum values. The preferred value is a responsive calculation. This produces typography that is perfectly sized at every viewport width — not just at your defined breakpoints.
Scroll-Driven Animations: No JavaScript Required
The animation-timeline: scroll() and view() APIs connect CSS animations to scroll position. Scroll progress bars, reveal-on-scroll effects, and parallax — all in CSS, hardware accelerated, with no JavaScript.
@keyframes reveal {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.section {
animation: reveal linear;
animation-timeline: view();
animation-range: entry 0% entry 30%;
}Expert insights on AI, software engineering, and digital transformation from the TechGeneses team of engineers and strategists.