Marcell CD

10 New CSS Features You Should Know About For 2026

10 New CSS Features You Should Know About For 2026

CSS is entering a new era. Beyond colors and layouts, it now offers functions, conditionals, scroll-aware styling, and genuinely expressive shapes—often replacing UI logic we’d previously reach for JavaScript to handle. Better Stack’s “10 NEW CSS Features You Need To Know For 2026” highlights what’s coming and how these features can simplify real-world interfaces.

CSS is quietly becoming a runtime for UI logic, and if you’re building React or any modern frontend, these new features let you ship fewer hooks, fewer event listeners, and more declarative styles in 2026.


1. Smarter Corners With corner-shape

React devs love component tokens. corner-shape lets you turn “rounded” into a real design primitive instead of just border-radius: 8px.

You can create consistent brands (squircles, notches, bevels) directly in CSS:

/* Design tokens */
:root {
  --card-radius: 18px;
  --corner-style: squircle;
}

.card {
  border-radius: var(--card-radius);
  corner-shape: var(--corner-style);
}

In a React app, you can map themes to CSS variables at the root and keep your JSX clean:

function AppShell({theme}: {theme: "default" | "playful"}) {
  return <div className={`app theme-${theme}`}>{/* … */}</div>
}
.app.theme-default {
  --corner-style: round;
}

.app.theme-playful {
  --corner-style: squircle;
}

No extra props on every <Card />, but your whole app’s corner personality can change in one place.


2. shape() for Responsive Masks and Motion

Instead of SVGs or hard-coded clip-path polygons, shape() gives you responsive shapes you can use for avatars, hero blobs, or motion paths.

Example: hero illustration orbiting a logo, without use of any JavaScript:

.hero-orbit {
  position: relative;
  width: min(400px, 80vw);
  aspect-ratio: 1 / 1;
}

/* The path the satellite will follow */
.hero-orbit::before {
  content: "";
  position: absolute;
  inset: 0;
  offset-path: shape(nonzero, circle(45% at 50% 50%));
  offset-rotate: auto;
  animation: orbit 12s linear infinite;
  background: url(/satellite.png) center / contain no-repeat;
}

@keyframes orbit {
  to {
    offset-distance: 100%;
  }
}

Drop this into a React hero component and you’ve got a motion design that’s purely CSS and scales across breakpoints without recalculating coordinates in JS.


3. appearance: base-select – Finally, Customizable Native Selects

If you’ve ever imported a “custom select” npm package just to get a styled dropdown, this one is for you. appearance: base-select exposes parts of the <select> so you can theme it without replacing the native element. This means you keep all the built-in accessibility, keyboard support, and form integration, while still getting a brand-aligned look. Finally after years of hacks, we can style dropdowns natively.

Example: design-system friendly <Select />:

export function Select(props: JSX.IntrinsicElements["select"]) {
  return <select className="ui-select" {...props} />
}
.ui-select {
  appearance: base-select;
  padding: 0.5rem 2.5rem 0.5rem 0.75rem;
  border-radius: 0.75rem;
  border: 1px solid #e5e7eb;
  background: white;
  font: inherit;
}

/* Chevron icon */
.ui-select::picker-icon {
  transition: transform 150ms ease;
}

/* Rotate when open */
.ui-select:open::picker-icon {
  transform: rotate(180deg);
}

/* Options */
.ui-select option {
  padding: 0.5rem 0.75rem;
}

.ui-select option:checked {
  background: #0f766e;
  color: white;
}

.ui-select option::checkmark {
  content: "✓";
  margin-right: 0.5rem;
}

You keep native keyboard support, screen reader behavior, and form integration, while still getting a brand-aligned look.


4. Zero-JS Carousels With Scroll Markers

Instead of wiring up useRef, scrollIntoView, and onClick handlers for every carousel, you can lean on ::scroll-marker and ::scroll-button. It’s a CSS-native way to add pagination dots and prev/next buttons to any scrollable container.

HTML inside your React component stays minimal:

export function FeatureCarousel() {
  return (
    <div className="carousel">
      <article>Framework-agnostic</article>
      <article>Ship less JS</article>
      <article>Use CSS logic</article>
    </div>
  )
}

CSS handles the interaction:

.carousel {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* Attach markers under the element */
  scroll-marker-group: after;
}

/* Marker row */
.carousel::scroll-marker-group {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  padding-top: 0.75rem;
}

/* Each marker (dot) */
.carousel::scroll-marker {
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 999px;
  background: #e5e7eb;
}

/* Active slide */
.carousel::scroll-marker:target {
  background: #0f766e;
}

/* Optional prev/next buttons */
.carousel::scroll-button {
  inline-size: 2.5rem;
  block-size: 2.5rem;
  border-radius: 999px;
  background: #0f172a;
  color: white;
}

You can still wrap this in a higher-level Carousel component in React, but you don’t need a carousel hook or event listeners. This is a huge win for performance and maintainability where we can rely on CSS for scroll behavior.


5. Scroll-State Queries Instead of Scroll Hooks

We’ve all written some version of:

useEffect(() => {
  function onScroll() {
    // update header or \"back to top\" button
  }
  window.addEventListener("scroll", onScroll)
  return () => window.removeEventListener("scroll", onScroll)
}, [])

Scroll-state container queries let you push that logic to CSS.

Mark your scrollable area:

main {
  container-type: scroll-state;
  container-name: page;
}

Then write rules that respond to scroll state:

/* Sticky header style once it sticks */
@container page style(stuck: .site-header) {
  .site-header {
    background: rgba(15, 23, 42, 0.9);
    backdrop-filter: blur(12px);
    box-shadow: 0 8px 16px rgba(15, 23, 42, 0.35);
  }
}

/* Show \"back to top\" only when you can scroll up */
@container page style(scrollable-y: up) {
  .scroll-top {
    opacity: 1;
    pointer-events: auto;
  }
}

In React, you just render <header className="site-header"> and <button className="scroll-top">, and let CSS figure out when they should visually change.


6. stretch for Full-Bleed Layouts Without Math

Have you ever done width: calc(100% - 2rem) to compensate for margins or gaps? stretch is essentially “fill the container, but account for margins”.

Example: card that should fill its parent but still have breathing room:

.dashboard-card {
  width: stretch;
  height: stretch;
  margin: 1rem;
  border-radius: 0.75rem;
  background: #020617;
  color: white;
}

Drop this inside a grid:

.dashboard-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

No more overflow because you forgot the gap/margin interaction. This plays nicely in any framework; your JSX stays clean, your CSS handles the sizing semantics.


7. text-box for Pixel-Perfect Headers

When aligning titles with icons or images in a card, there’s often “mystery” extra space due to font metrics. text-box lets you trim the box to caps, x-height, or alphabetic baselines.

Example: align a heading with an icon in a React card:

function StatCard() {
  return (
    <div className="stat-card">
      <div className="stat-icon">🔥</div>
      <h2 className="stat-title">Active Users</h2>
    </div>
  )
}
.stat-card {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* Trim vertical whitespace to match the icon better */
.stat-title {
  text-box: trim-both cap alphabetic;
  font-size: 1.125rem;
  font-weight: 600;
}

You get better alignment without font-specific hacks or magic margin-top: -2px nudges.


8. sibling-index() for Implicit Ordering Logic

Instead of passing index as a prop everywhere or sprinkling data-index attributes for styling, sibling-index() gives you the position straight in CSS.

Example: React steps component with staggered animation:

export function Steps({items}: {items: string[]}) {
  return (
    <ul className="steps">
      {items.map((item) => (
        <li key={item}>{item}</li>
      ))}
    </ul>
  )
}
.steps li {
  --i: sibling-index();
  background: #1d4ed8;
  color: white;
  padding: 0.5rem 0.75rem;
  margin-block: 0.25rem;

  animation: fade-up 300ms ease-out;
  animation-delay: calc(var(--i) * 60ms);
}

@keyframes fade-up {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}

Reorder the list in React, and the CSS automatically adjusts the delays and widths without touching your JSX.


9. if() – Conditionals Inside CSS Properties

You might be used to writing:

const isMobile = useMediaQuery("(max-width: 640px)")

;<div
  className={clsx("layout", {
    "layout--stacked": isMobile,
  })}
/>

The if() function lets CSS make that decision itself in many cases.

Example: responsive flex-direction with no extra classnames:

.layout {
  display: flex;
  flex-direction: if(width() < 480px, column, row);
}

Or theming via CSS variables:

:root {
  --theme: "light";
}

.app {
  background: if(var(--theme) = "dark", #020617, #f9fafb);
  color: if(var(--theme) = "dark", #e5e7eb, #020617);
}

In React, toggle --theme via a data-theme or class on the root, and keep your CSS as the source of truth for how themes look.


10. Custom Functions as “CSS Hooks”

Custom CSS functions are basically “hooks, but for styles”: reusable logic blocks with parameters and branching.

You can encode layout decisions once and reuse them across components.

Example: narrow-wide() helper for container-aware values:

@function narrow-wide($narrow, $wide) {
  @if (width() < 300px) {
    @return $narrow;
  }
  @return $wide;
}

.card {
  flex-direction: narrow-wide(column, row);
}

.card-title {
  font-size: narrow-wide(1rem, 1.25rem);
}

Now your React <Card /> can be completely dumb:

export function Card(props: React.PropsWithChildren) {
  return <section className="card" {...props} />
}

CSS decides layout based on the container’s width, and you keep logic out of JSX and JS.

Another useful pattern: color helpers:

@function alpha($color, $alpha) {
  $oklch: oklch(from $color l c h);
  @return oklch(l($oklch) c($oklch) h($oklch) / $alpha);
}

.button {
  background: alpha(#0f766e, 0.85);
}

You get a centralized, type-like place for design logic that works across any framework.


Resources