You've almost certainly noticed this: scrolling through a feed in a native mobile app is usually smooth. But open a content-heavy webpage in a browser, and you'll often see content jumping, layout flickering, blank areas appearing before being filled in. This isn't a network speed issue. A large part of the reason is that browsers are an order of magnitude slower than native platforms at computing how much space each piece of content occupies after layout.
Pretext recently went viral in the tech community, and it does exactly this: predicting how much space text will occupy inside a container. A reader named Ma Gong asked a very good question after reading about it: shouldn't this be the most basic functionality? Why, in 2026, do we still need a third-party library to solve this?
I followed this question down the rabbit hole and found that it touches something far deeper than frontend technology choices. In 2012, Facebook paid the price of rewriting their entire mobile stack because they didn't understand the trade-off behind it. This isn't just a concern for frontend engineers. It's a judgment call about system design philosophy: when you build an abstraction, what should you hide, and what should you leave exposed?
On Every Other Platform, It Really Is One Line of Code
Let's first verify Ma Gong's intuition. On iOS, if you want to know how tall a piece of text will be when laid out at a given width, you call sizeThatFits. One line of code, returns instantly, doesn't even require putting the text into any view hierarchy. Android uses StaticLayout, Qt uses QFontMetrics, Flutter uses TextPainter. Same pattern everywhere: the layout engine is an independent computation module. You give it input, it gives you output, no global operations triggered.
This is why native apps scroll through long lists so smoothly: the system can pre-calculate the height of each content item, knowing precisely which items are about to enter the screen and how much space to reserve for them.
The web can't do this. In a browser, to find out how tall a piece of content is after layout, you have to actually insert it into the page, then trigger a Reflow, which means the browser recalculates the position and size of all affected elements. This calculation is synchronous and blocking, and its scope isn't limited to just the element you asked about. It can cascade through the entire layout tree. In a content-heavy feed page, when the window size changes, every item needs to be recalculated, and each recalculation ripples across the whole page. This is why web-based feeds, chat interfaces, and e-commerce listings typically feel rougher than their native counterparts during scrolling and window resizing.
Ma Gong is right: this really should be the most basic functionality. And on iOS, Android, Qt, and Flutter, it truly is the most basic functionality. The web is the only major UI platform that can't do this.
But web engineers couldn't possibly have overlooked this for thirty years. There must be a trade-off here. And understanding this trade-off matters a lot.
CSS's Choice Wasn't Stupid
There are two ways to build a user interface.
One is to tell the system where to put everything. Early iOS development worked this way: you manually calculated coordinates and dimensions for each element. The system executed exactly what you specified, and you had precise understanding of every step. In this model, "how tall is this after layout" is naturally available as intermediate data, because you need that number to decide where the next element goes.
The other is to describe what you want and let the system figure out the rest. CSS works this way. You write display: flex; flex-wrap: wrap; gap: 16px, and whether the screen is 320px or 1920px wide, the browser decides how many items per row and how to distribute space. You don't control the process. You describe intent.
The latter has a higher ceiling. LaTeX is an even more extreme example. Its line-breaking algorithm treats an entire paragraph as an optimization problem, considering all possible break points and selecting the one that produces the most uniform spacing across the whole paragraph. It might make earlier lines slightly looser to avoid an ugly gap in a later line. You can't achieve this effect with line-by-line manual typesetting, because when you're setting line three, you don't know what will happen at line seven. Only a system with a global view can make that kind of optimization.
CSS's responsive layout follows the same logic. A developer experienced with WPF and XAML commented on Hacker News that building adaptive layouts with CSS Flexbox/Grid is more efficient in terms of development time than many native desktop frameworks, because you're describing intent rather than writing implementation.
But what's the cost? The cost is that you can't ask the system "what did you calculate?" In CSS's world, an element's final size depends on all the elements around it. Floats, positioning, inline formatting contexts, margin collapse: every rule reinforces the same fact. Local results are solved globally. There is no context-free independent answer. CSS's very first proposal in 1994 established this architecture: information flows one way. Developers declare rules, the browser executes layout, but the browser doesn't report intermediate results back.
For document typesetting, this makes perfect sense. You don't need to know a paragraph's exact height. You just declare styles, and the browser handles presentation. LaTeX users experience the same thing. You don't control which page a figure appears on. You tell TeX your preference, and it decides the optimal placement.
The cost of not understanding this trade-off can run into hundreds of millions of dollars. In 2012, Facebook built its entire mobile experience on HTML5. Both the iOS and Android apps were essentially wrappers around WebViews. The rationale for choosing HTML5 was intuitive: write once, run everywhere; push updates from the server without requiring users to download new versions. But ultimately, performance couldn't keep up. Facebook engineers documented the issues in a postmortem: inconsistent scroll frame rates, UI thread stutter, device resource exhaustion causing crashes. Global DOM Reflow was one of the core bottlenecks: every content update could trigger recalculation of the entire layout tree, and News Feed's long lists with heavy images pushed it to the breaking point.
Facebook ultimately spent nine months rewriting the native iOS app from scratch. Launch time dropped from roughly 10 seconds to roughly 4 seconds. News Feed loading speed doubled. Zuckerberg said at TechCrunch Disrupt:
The biggest mistake that we made as a company was betting too much on HTML5 as opposed to native.
The subsequent chain of events is equally telling. The HTML5 failure directly led to React (2013). Once React's declarative approach was validated, it led to React Native (2015). React Native's layout engine Yoga was explicitly designed to optimize layout queries. Facebook's engineering blog noted that Yoga ensures text views are "measured as few times as possible, ideally just once," and moves layout computation to a separate thread, completely sidestepping the mutual blocking between JavaScript and layout in DOM Reflow.
Every step in this chain was an escape from CSS's layout architecture constraints. Understanding why CSS made this trade-off, and under what conditions it breaks down, is a judgment call worth hundreds of millions of dollars.
But This Trade-off Isn't Inevitable
At this point, the story sounds like a binary choice: either pick "the system decides for you" and get better layout quality at the cost of invisible intermediate results, or pick "you control everything" and get precise queryability at the cost of losing global optimization.
But in fact, this trade-off can be broken.
SwiftUI (Apple, 2019) and Jetpack Compose (Google, 2021) are both declarative UI frameworks. Writing responsive layouts in SwiftUI is conceptually very similar to writing Flexbox in CSS: describe intent, let the system decide layout. But neither of them has CSS's problem.
The underlying reason is architectural layering. Native platforms are designed with a completely independent layout engine at the bottom layer (Core Text on iOS, StaticLayout on Android), and declarative frameworks on top. The declarative framework calls the underlying engine to perform layout, but application code can also pierce through the declarative abstraction at any time and query the underlying engine directly. Jetpack Compose even provides an official TextMeasurer API that returns complete layout information, including dimensions, line count, and character positions, without triggering any actual rendering.
This proves something important: declarative and observable are not mutually exclusive. You can let the system perform global optimization while maintaining an independent channel for querying intermediate results. The key is that the layout engine must be designed as an independent module, not welded into the global layout pipeline.
CSS's problem isn't that it chose to be declarative. The problem is that when building its abstraction, it also sealed the layout engine inside the global layout process without leaving an independent query interface. (Caveat: canvas.measureText() only handles single-line text and doesn't support line wrapping. The line-wrapping logic is locked inside the layout engine and has never been exposed as a standalone interface.)
When CSS was designed in 1994, the web was an academic document exchange system. Nobody foresaw it becoming an application platform. In a document context, you genuinely don't need to independently query layout results, so nobody thought to design the layout engine as a separately callable module. That decision was perfectly reasonable at the time. But thirty years later, it became a massive piece of technical debt.
The W3C is well aware of this problem. CSS Houdini's Font Metrics API was designed to address it, but as of 2026, it remains at the proposal stage with no browser implementation. Pretext filled this gap with 4,000 lines of userland code. It rebuilt on the web a capability that iOS and Android have long provided as infrastructure. Its very existence is evidence that CSS is missing an abstraction layer.
This Lesson Extends Far Beyond Frontend
Looking back, both CSS and native platforms chose declarative layout, but native platforms preserved the layout engine as an independent, queryable layer. CSS did not. A layering decision made in the 1990s, seemingly inconsequential at the time, led thirty years later to fundamentally different developer experiences, fundamentally different performance characteristics, and a company's strategic mistake costing hundreds of millions of dollars.
This pattern exists well beyond frontend.
Traditional API design follows the same philosophy as CSS: hide complexity, shield users from needing to see intermediate state. Catch underlying errors and throw an abstract high-level exception. Wrap implementation details behind clean interfaces. This works fine when the user is human. Humans have limited cognitive bandwidth, and good abstractions help them focus.
But when the system's user becomes code or AI that needs to make decisions, this protective abstraction becomes an obstacle. AI's effectiveness depends on a try-observe-correct loop. A vague "operation failed, please try again later" directly breaks this loop, which is the same problem as CSS not telling you layout results. What AI needs is fine-grained raw feedback, granular control interfaces, and sufficiently detailed intermediate state. The thirty-year struggle of frontend layout and the problems AI engineering faces today share the same root: the same design philosophy hitting a wall in different eras.
The solutions are also structurally similar. Pretext built an independent queryable channel alongside CSS's declarative rendering, not replacing the browser's layout but letting you observe layout results. Agentic loops do the same thing for AI workflows: they don't prescribe what AI should do at each step, but they let it observe the results of its own actions and autonomously decide what to do next. Neither tears down the abstraction. Both open observation windows on top of it.
Ma Gong said "many frontend problems exist because of lacking proper architecture." The direction is right, but it can be made more precise: the problem isn't that CSS chose to be declarative. Declarative genuinely has a higher ceiling. The problem is that CSS, when building its abstraction, also hid things that shouldn't have been hidden. Good abstractions let you choose which layer to work at. Bad abstractions glue all layers together and leave you no choice.
Next time you design a system or build an abstraction layer, it's worth asking yourself: are you sealing intermediate state that your users will someday need to observe into a black box? That decision might look clean and elegant today. But CSS has spent thirty years proving that it can be a very long-dated, very high-interest piece of technical debt.
Comments