7 CSS Features in 2026 That Every Front-End Developer Should Know
Hello HaWkers, if you work with front-end development, you have probably felt the frustration of writing dozens of lines of JavaScript to solve something that should be CSS's responsibility. Tooltips that reposition themselves, responsive layouts based on component size, scroll detection — all of this required JavaScript until recently.
The good news? CSS in 2026 is more powerful than ever. Browsers are shipping features that eliminate the need for many JS libraries and hacks. Let's explore the 7 features that are transforming how we build interfaces on the web.
1. Container Queries: True Component-Level Responsiveness
If you have worked with design systems or micro-frontends, you know that traditional media queries have a fundamental limitation: they respond to the browser window size, not the container size where the component lives. This means the same component might work perfectly on one page but break on another because the available space is different.
Container Queries solve exactly this. Now you can style a component based on the size of its parent container, making each component truly independent and reusable.
/* Define the container */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* Base card styles */
.card {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 1rem;
}
/* When the container is at least 600px wide */
@container card (min-width: 600px) {
.card {
flex-direction: row;
align-items: center;
}
.card__image {
width: 40%;
flex-shrink: 0;
}
.card__content {
flex: 1;
}
}
/* When the container is very small */
@container card (max-width: 300px) {
.card {
padding: 0.5rem;
font-size: 0.875rem;
}
.card__image {
display: none;
}
}The key advantage here is that this card will adapt regardless of where you place it — whether in a narrow sidebar, a 3-column grid, or taking up the full screen. The page layout does not matter; the component knows how to adapt to the space it has available.
All major browsers now support size Container Queries. In 2026, support has reached 96% of browsers in global use, which means you can safely use them in production.
2. CSS Anchor Positioning: Tooltips and Popovers Without JavaScript
How many times have you installed Floating UI, Popper.js, or Tippy.js just to position a tooltip? CSS Anchor Positioning completely eliminates that need. You can anchor one element to another using only CSS, and the browser handles all the positioning logic, including fallbacks when there is not enough space.
/* Anchor element */
.trigger-button {
anchor-name: --my-anchor;
}
/* Anchored tooltip */
.tooltip {
/* Position relative to the anchor */
position: fixed;
position-anchor: --my-anchor;
/* Position above the button, centered */
bottom: anchor(top);
left: anchor(center);
translate: -50% 0;
/* Fallback: if it does not fit above, flip below */
position-try-fallbacks: flip-block;
/* Visual styling */
background: #1a1a2e;
color: white;
padding: 0.5rem 1rem;
border-radius: 6px;
white-space: nowrap;
}The position-try-fallbacks: flip-block is the magic here. It tells the browser: "try positioning above, but if it does not fit, flip it below." This is something that previously required dozens of lines of JavaScript with bounding rect calculations and scroll event listeners.
Safari 26 shipped anchor positioning support, which means 2 out of 3 major browsers now support the feature natively. Firefox is working on their implementation and full support is expected by mid-2026.
3. Scroll-State Queries: Detect Scroll State with Pure CSS
Have you ever needed to add a shadow to a sticky header when the user starts scrolling? Or change the style of an element when it becomes "stuck"? Before, you needed an IntersectionObserver or scroll event listeners. Now, CSS handles this natively.
/* Scrollable container */
.scroll-container {
container-type: scroll-state;
overflow-y: auto;
height: 100vh;
}
/* Sticky header that changes when stuck */
.sticky-header {
position: sticky;
top: 0;
background: white;
transition: box-shadow 0.3s ease;
}
/* Apply shadow when the header is in stuck state */
@container scroll-state(stuck: top) {
.sticky-header {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
backdrop-filter: blur(8px);
background: rgba(255, 255, 255, 0.95);
}
}The beauty of this feature is that it is declarative. You do not need to manage state, add classes via JavaScript, or worry about scroll handler performance. The browser does everything optimized internally.
Beyond the stuck state, you can detect if the container has available scroll (scrollable) and the scroll position (snapped), which is perfect for carousels and horizontal scroll navigations.
4. The sibling-index() Function: Staggered Animations Without JS
If you have ever created staggered animations in lists, you know that it typically requires JavaScript or individual CSS variants for each item (nth-child). The new sibling-index() function allows you to use an element's position among its siblings directly in CSS calculations.
/* Staggered animation in a list */
.staggered-list li {
opacity: 0;
transform: translateY(20px);
animation: fadeInUp 0.4s ease forwards;
/* Each item delays based on its position */
animation-delay: calc(sibling-index() * 80ms);
}
@keyframes fadeInUp {
to {
opacity: 1;
transform: translateY(0);
}
}
/* Grid with variable sizes based on position */
.dynamic-grid > * {
grid-row: span calc(1 + mod(sibling-index(), 3));
}Before this, you would need something like:
/* Old approach - repetitive and limited */
li:nth-child(1) { animation-delay: 0ms; }
li:nth-child(2) { animation-delay: 80ms; }
li:nth-child(3) { animation-delay: 160ms; }
/* ... and so on for each item */With sibling-index(), it does not matter how many items exist in the list — the animation adapts automatically.
5. @starting-style: Entry Animations for Dynamic Elements
Animating the entry of elements that are dynamically added to the DOM has always been tricky. The @starting-style rule defines the initial state of an element before its first render, allowing CSS transitions to work automatically when the element appears.
/* Dialog with entry animation */
dialog[open] {
opacity: 1;
transform: scale(1) translateY(0);
transition: opacity 0.3s ease,
transform 0.3s ease,
overlay 0.3s ease allow-discrete,
display 0.3s ease allow-discrete;
}
/* Initial state before first render */
@starting-style {
dialog[open] {
opacity: 0;
transform: scale(0.95) translateY(-10px);
}
}
/* Exit state */
dialog:not([open]) {
opacity: 0;
transform: scale(0.95) translateY(10px);
transition: opacity 0.2s ease,
transform 0.2s ease,
overlay 0.2s ease allow-discrete,
display 0.2s ease allow-discrete;
}This is especially useful for popovers, dialogs, toasts, and any element that dynamically appears and disappears. Before, you needed libraries like Framer Motion or complex JavaScript techniques to achieve the same result.
6. @property: Typed CSS Variables with Animation
@property allows you to register custom properties (CSS variables) with specific types, default values, and inheritance control. The big benefit? Typed variables can be animated, something that regular custom properties cannot do.
/* Register a typed custom property */
@property --gradient-angle {
syntax: "<angle>";
initial-value: 0deg;
inherits: false;
}
@property --color-intensity {
syntax: "<percentage>";
initial-value: 50%;
inherits: true;
}
/* Continuously animated gradient */
.animated-gradient {
--gradient-angle: 0deg;
background: conic-gradient(
from var(--gradient-angle),
#6366f1,
#a855f7,
#ec4899,
#6366f1
);
animation: rotate-gradient 3s linear infinite;
}
@keyframes rotate-gradient {
to {
--gradient-angle: 360deg;
}
}
/* Button with animatable color intensity */
.dynamic-button {
--color-intensity: 50%;
background: hsl(230 var(--color-intensity) 50%);
transition: --color-intensity 0.3s ease;
}
.dynamic-button:hover {
--color-intensity: 70%;
}Without @property, animating a gradient like this would be impossible with pure CSS. The browser would not know how to interpolate between two custom property values — it would just make an abrupt switch. With the type declaration, the browser understands that --gradient-angle is an angle and knows how to animate it smoothly.
7. light-dark() and Automatic Theming
The light-dark() function drastically simplifies dark mode implementation. Instead of duplicating all color rules inside media queries or using conditional logic, you define both values in a single place.
/* Define the supported color scheme */
:root {
color-scheme: light dark;
}
/* Use light-dark() for adaptive colors */
body {
background-color: light-dark(#ffffff, #0f0f23);
color: light-dark(#1a1a2e, #e2e8f0);
}
.card {
background: light-dark(#f8f9fa, #1e1e3f);
border: 1px solid light-dark(#e2e8f0, #2d2d5e);
box-shadow: light-dark(
0 2px 8px rgba(0, 0, 0, 0.08),
0 2px 8px rgba(0, 0, 0, 0.4)
);
}
.primary-button {
background: light-dark(#6366f1, #818cf8);
color: light-dark(#ffffff, #0f0f23);
}
/* Links with adaptive colors */
a {
color: light-dark(#2563eb, #60a5fa);
}
a:hover {
color: light-dark(#1d4ed8, #93bbfd);
}Compare this with the traditional approach using media queries:
/* Old approach - verbose and hard to maintain */
body { background: #ffffff; color: #1a1a2e; }
.card { background: #f8f9fa; border-color: #e2e8f0; }
@media (prefers-color-scheme: dark) {
body { background: #0f0f23; color: #e2e8f0; }
.card { background: #1e1e3f; border-color: #2d2d5e; }
}With light-dark(), each theme's colors live together in the same declaration, making the code much more readable and easier to maintain. Any color change is made in a single place, without needing to search for the corresponding media query.
Browser Support: What Can I Use Right Now?
A fair concern is: "Can I use this in production?" Here is the current support landscape:
| Feature | Chrome | Safari | Firefox | Safe to Use? |
|---|---|---|---|---|
| Container Queries (size) | 105+ | 16+ | 110+ | Yes |
| Anchor Positioning | 125+ | 26+ | In progress | With fallback |
| Scroll-State Queries | 133+ | Partial | In progress | With fallback |
| sibling-index() | 143+ | Partial | In progress | Progressive |
| @starting-style | 117+ | 17.4+ | Partial | Yes |
| @property | 85+ | 15.4+ | 128+ | Yes |
| light-dark() | 123+ | 17.5+ | 120+ | Yes |
Container Queries, @starting-style, @property, and light-dark() already have broad support and can be used in production. For the rest, the ideal strategy is progressive enhancement — the layout works without the feature but looks even better when the browser supports it.
Is CSS Replacing JavaScript?
This is not about replacement, but about giving CSS back what should have always been its responsibility. Positioning, component responsiveness, scroll detection, theming — these are all visual presentation concerns. When we solve these problems with pure CSS, JavaScript is free to handle business logic, state management, and complex interactions.
The practical results are:
- Fewer dependencies — less need for libraries like Floating UI, Framer Motion, or IntersectionObserver polyfills
- Better performance — the browser optimizes internally, without event listener overhead
- More resilient code — CSS fails gracefully, JavaScript can break the entire page
- Smaller bundles — less JS means faster loading times
If you feel inspired by these new CSS possibilities, I recommend checking out another article: Why Developers Are Ditching Frameworks and Going Back to Vanilla JavaScript where you will discover how the simplification trend is transforming web development.
Let's go! 🦅
💻 Master JavaScript for Real
The knowledge you gained in this article is just the beginning. There are techniques, patterns, and practices that transform beginner developers into sought-after professionals.
Invest in Your Future
I have prepared complete material for you to master JavaScript:
Payment options:
- 1x of $4.90 no interest
- or $4.90 at sight

