4 CSS Features Every Front-End Developer Should Know in 2026
CSS evolved significantly in 2025, and most developers missed it.
Four features shipped across all major browsers that fundamentally change how we style web applications. No JavaScript. No build tools. Just CSS.
Here's what you need to know.
1. Scroll State Queries: Delete Your Intersection Observer
Remember writing JavaScript to detect scroll positions?
// The old way
const header = document.querySelector('header');
window.addEventListener('scroll', () => {
if (window.scrollY > 100) {
header.classList.add('scrolled');
} else {
header.classList.remove('scrolled');
}
});That's over.
The CSS Way
@scroll-state(scrolled-from-top > 100px) {
header {
background: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(10px);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
}How It Works: The browser tracks scroll state natively and applies styles when conditions match.
Real-World Example: Sticky Header
header {
position: sticky;
top: 0;
background: transparent;
transition: all 0.3s ease;
}
@scroll-state(scrolled-from-top > 50px) {
header {
background: white;
border-bottom: 1px solid #e0e0e0;
padding-block: 0.5rem;
}
header .logo {
height: 40px; /* Shrink logo when scrolled */
}
}Directional Scroll Detection
@scroll-state(scroll-direction: down) {
header {
transform: translateY(-100%); /* Hide header when scrolling down */
}
}
@scroll-state(scroll-direction: up) {
header {
transform: translateY(0); /* Show header when scrolling up */
}
}Browser Support
- Chrome 126+
- Edge 126+
- Safari 18.1+
- Firefox 135+
Fallback:
/* Modern browsers */
@scroll-state(scrolled-from-top > 100px) {
header { background: black; }
}
/* Fallback for older browsers */
@supports not (scroll-state: scrolled-from-top) {
header.scrolled { background: black; }
}And keep the JavaScript for legacy support.
2. Text-Box-Trim: Fix Typographic Whitespace
Every font has built-in whitespace above and below characters. This ruins vertical alignment.
The Problem:
<button>
<svg class="icon"></svg>
<span>Click me</span>
</button>button {
display: flex;
align-items: center;
gap: 0.5rem;
}Result: Text looks misaligned because the font's built-in whitespace creates visual imbalance.
The Fix
button span {
text-box-trim: both; /* Trim top and bottom whitespace */
text-box-edge: cap alphabetic; /* Use cap height instead of line-height */
}Before:
┌─────────────┐
│ ✓ │ ← Extra space
│ Button │
│ │ ← Extra space
└─────────────┘After:
┌─────────────┐
│ ✓ Button │ ← Perfectly aligned
└─────────────┘Real-World Use Cases
1. Card Titles
.card-title {
text-box-trim: both;
text-box-edge: cap alphabetic;
margin-block: 0; /* No need for negative margins anymore */
}2. Badge Components
.badge {
display: inline-flex;
align-items: center;
padding: 0.25rem 0.75rem;
text-box-trim: both;
text-box-edge: cap alphabetic;
/* Text is now optically centered */
}3. Input Fields
input {
text-box-trim: both;
text-box-edge: cap alphabetic;
/* Placeholder text aligns perfectly with icon */
}Browser Support
- Chrome 128+
- Edge 128+
- Safari 18.2+
- Firefox: In development
Fallback:
.button span {
text-box-trim: both;
/* Fallback for older browsers */
line-height: 1;
margin-top: -0.1em;
}3. Sibling-Index(): Staggered Animations Without JavaScript
Creating staggered animations used to require JavaScript:
// The old way
const items = document.querySelectorAll('.item');
items.forEach((item, index) => {
item.style.animationDelay = `${index * 0.1}s`;
});The CSS Way
.item {
animation: fadeIn 0.5s ease-out;
animation-delay: calc(sibling-index() * 0.1s);
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}Result: Items fade in one after another, automatically.
Advanced Example: Staggered Grid
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
.grid-item {
animation: scaleIn 0.4s ease-out backwards;
animation-delay: calc(sibling-index() * 0.05s);
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}Dynamic Lists
Works with dynamic content too:
<ul class="list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
<!-- Items added dynamically will automatically stagger -->
</ul>.list li {
opacity: 0;
animation: slideIn 0.5s ease-out forwards;
animation-delay: calc(sibling-index() * 0.1s);
}Add new <li> elements via JavaScript, and they'll automatically animate with the correct delay.
Browser Support
- Chrome 129+
- Edge 129+
- Safari 18.2+
- Firefox: Coming soon
Fallback: Use CSS variables set via JavaScript:
document.querySelectorAll('.item').forEach((item, i) => {
item.style.setProperty('--index', i);
});.item {
animation-delay: calc(var(--index, 0) * 0.1s);
}4. Type-Safe attr(): Use HTML Attributes in CSS (Finally)
You could always read HTML attributes in CSS:
.button::before {
content: attr(data-label); /* String only */
}But you couldn't use them for numbers or colors.
CSS attr() in 2026
.progress-bar {
/* Use data-progress attribute as a number */
width: attr(data-progress % , 0%);
background: attr(data-color color, blue);
}<div class="progress-bar" data-progress="75" data-color="#00ff00"></div>Result: The progress bar is 75% wide with a green background.
Real-World Examples
1. Dynamic Tooltips
<button data-tooltip="Save changes">Save</button>button::after {
content: attr(data-tooltip);
position: absolute;
top: calc(100% + 0.5rem);
left: 50%;
transform: translateX(-50%);
background: black;
color: white;
padding: 0.5rem;
border-radius: 4px;
opacity: 0;
transition: opacity 0.2s;
}
button:hover::after {
opacity: 1;
}2. Star Ratings
<div class="rating" data-rating="4.5">Rating</div>.rating {
--rating: attr(data-rating number, 0);
--fill-percent: calc(var(--rating) / 5 * 100%);
background: linear-gradient(
to right,
gold var(--fill-percent),
gray var(--fill-percent)
);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.rating::before {
content: '★★★★★';
}3. Themeable Components
<div class="card"
data-bg-color="#f0f0f0"
data-text-color="#333"
data-border-radius="8">
Content
</div>.card {
background: attr(data-bg-color color, white);
color: attr(data-text-color color, black);
border-radius: attr(data-border-radius px, 0px);
padding: 1rem;
}Now you can theme components via HTML attributes without CSS variables.
Type Safety
.element {
/* Type assertion with fallback */
width: attr(data-width px, 100px);
height: attr(data-height %, 50%);
color: attr(data-color color, blue);
font-size: attr(data-size em, 1em);
opacity: attr(data-opacity number, 1);
}If the attribute is invalid, the fallback is used.
Browser Support
- Chrome 131+
- Edge 131+
- Safari 18.3+
- Firefox: Coming Q2 2026
Fallback: Use CSS custom properties:
<div class="card" style="--bg: #f0f0f0; --color: #333;">.card {
background: var(--bg, white);
color: var(--color, black);
}Bonus: Combining Features
Here's a real-world component using all four features:
<nav class="nav" data-bg-color="#1a1a1a">
<a href="#" class="nav-item">Home</a>
<a href="#" class="nav-item">About</a>
<a href="#" class="nav-item">Contact</a>
</nav>.nav {
position: sticky;
top: 0;
display: flex;
gap: 1rem;
padding: 1rem;
background: transparent;
transition: all 0.3s ease;
}
/* Scroll state query */
@scroll-state(scrolled-from-top > 100px) {
.nav {
background: attr(data-bg-color color, black);
backdrop-filter: blur(10px);
}
}
/* Sibling index stagger */
.nav-item {
padding: 0.5rem 1rem;
text-box-trim: both; /* Trim whitespace */
text-box-edge: cap alphabetic;
opacity: 0;
animation: fadeIn 0.5s ease-out forwards;
animation-delay: calc(sibling-index() * 0.1s);
}
@keyframes fadeIn {
to { opacity: 1; }
}Result:
- Nav starts transparent
- Becomes solid black with blur after scrolling 100px
- Items fade in one by one on page load
- Text is perfectly aligned
All in pure CSS.
Should You Use These Today?
Yes, with fallbacks.
Modern browsers support these features, but always provide graceful degradation:
/* Modern approach */
@scroll-state(scrolled-from-top > 100px) {
header { background: black; }
}
/* Fallback */
@supports not (scroll-state: scrolled-from-top) {
header.scrolled { background: black; }
}Then use JavaScript to add the .scrolled class for older browsers.
Conclusion
CSS in 2026 is powerful enough to replace entire JavaScript libraries:
- Scroll state queries → No more Intersection Observer
- Text-box-trim → No more negative margins
- Sibling-index() → No more stagger animation scripts
- Type-safe attr() → No more inline styles for dynamic values
These features shipped in 2025. They're production-ready now.
Stop relying on JavaScript for styling. Modern CSS can handle it.
---
Resources: