← Back to Blog

Component-First CSS: Container Queries, :has(), and View Transitions in 2026

Component-First CSS: Container Queries, :has(), and View Transitions in 2026

For years, building responsive, interactive interfaces meant reaching for JavaScript libraries and media-query gymnastics. That era is ending. The browser platform has quietly absorbed most of what we used to bolt on, and at TCCB Solutions we've shifted our default toolkit accordingly. In this post we'll walk through three native CSS capabilities we now reach for first — container queries, the :has() selector, and the View Transitions API — and show how they make our front-ends simpler and more resilient.

Stop styling for the viewport. Style for the container.

The biggest shift in how we think about responsive design is moving away from the viewport. A card component doesn't care how wide the screen is — it cares how much room it has been given. With container queries, a component can adapt to its own context, which means the same card works in a sidebar, a three-column grid, or a full-width hero without a single bespoke breakpoint.

The pattern is two steps: declare a containment context on the parent, then query it from the child.

.card-grid {
  container-type: inline-size;
  container-name: grid;
}

.card {
  display: grid;
  gap: 1rem;
}

@container grid (min-width: 28rem) {
  .card {
    grid-template-columns: 8rem 1fr;
  }
}

The win here is genuine reusability. We ship a component once, drop it anywhere, and it lays itself out correctly based on available space. No more props that say "render the compact variant" — the CSS decides.

Parent-aware styling with :has()

The :has() selector — often called the "parent selector" — lets an element style itself based on its descendants or its siblings. This closes a gap that forced us into JavaScript for years. A few things we now do entirely in CSS:

/* Wrapper turns red only when it holds an invalid input */
.field:has(input:invalid:not(:placeholder-shown)) {
  border-color: #e11d48;
}

/* Give the card a media layout only if there's a figure */
.card:has(figure) {
  grid-template-rows: auto 1fr;
}

Combined with container queries, :has() lets us express conditional UI as declarative style rather than imperative logic. Less JavaScript means fewer re-render bugs, less hydration weight, and behaviour that survives even when scripts fail to load.

Smooth transitions without a framework

Animated transitions between states or pages used to be the exclusive domain of heavy SPA frameworks. The View Transitions API now gives us that polish natively — including for multi-page sites, which is where a lot of our client work lives.

For same-document state changes, the API is a single wrapper around the DOM update:

function updateView(renderFn) {
  if (!document.startViewTransition) {
    renderFn();
    return;
  }
  document.startViewTransition(renderFn);
}

For traditional multi-page navigation, we can opt in entirely from CSS, no script required:

@view-transition {
  navigation: auto;
}

::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.25s;
}

Notice the feature detection in the JavaScript example. That's deliberate: where the API isn't supported, the update simply happens instantly. The interface still works — it just isn't animated.

Progressive enhancement is still the rule

None of this changes our core discipline. We layer these features on top of a baseline that works without them. @container and @view-transition rules are ignored by browsers that don't understand them, and :has() degrades gracefully when paired with sensible defaults. We pair this with the @supports rule for anything load-bearing:

@supports (container-type: inline-size) {
  /* enhanced layout here */
}

The practical takeaway is that you can adopt all three of these today. They ship in every current major browser, they reduce the amount of JavaScript you have to write and maintain, and they make components that are easier to reason about. We've found that leaning on the platform — rather than reaching reflexively for a dependency — produces interfaces that are faster, more accessible, and cheaper to maintain over their lifetime.

If you're weighing a rebuild or want a second opinion on modernising an existing front-end, we'd love to talk it through. Get in touch with our team and let's figure out what makes sense for your project.