phucnht

Modern HTML & CSS in 2026: what I no longer need JavaScript for

Sunday, August 2, 2026 · 2 min read

Modern HTML & CSS in 2026

For years, my default for anything interactive was a React or Vue project with a pile of packages on top. A lot of that code was there to patch gaps in HTML and CSS. In 2026 a good number of those gaps are closed, and I keep deleting JavaScript from my own projects because the browser can do the job now. This post is my list so far.

View transitions

Single-page apps were partly an answer to full page loads that flashed a white screen. If I wanted a smooth fade between two views, I needed a client-side router and an animation library.

The View Transitions API moves that into the browser. In a single-page app, document.startViewTransition() animates between two DOM states. In a multi-page site, one CSS rule gives same-origin navigations a crossfade:

@view-transition {
	navigation: auto;
}

If an element has the same view-transition-name on both pages, the browser moves it from one position to the other. That’s how post titles on this blog slide from the list into the article header. Cross-document support still differs between browsers, so check MDN before you depend on it. A browser without support loads the page the old way, which is fine.

:has() and container queries

Styling a parent based on its children used to need a JavaScript listener, because CSS selectors could only look down the tree. :has() lifts that limit. Container queries let a component respond to the width of its container instead of the whole viewport.

/* The card responds to its own width, not the viewport */
.card {
	container-type: inline-size;
}

/* Cards that contain an image get a different surface */
.card:has(img) {
	background: var(--surface-subtle);
	border: 1px solid var(--border-highlight);
}

@container (width > 30rem) {
	.card-body {
		display: grid;
		grid-template-columns: 1fr 2fr;
	}
}

I can drop the same card into a narrow sidebar or a wide grid and the CSS picks the layout, with no script measuring widths.

Update, 2026-10-20

Since writing this I ripped GSAP and bits-ui out of this site. The mobile menu is now an HTML popover, so the browser handles Escape, the outside click, and the backdrop. The contact form uses a native <select>, styled with appearance: base-select in browsers that support it. The page intro and the scroll reveals on the blog list are CSS keyframes and animation-timeline: view(). Two fewer dependencies to update, and less JavaScript for your phone to download.