Should CSS be a constraint system instead?
Source: Hacker News
CSS is hard. The layout rules are quite complex and hard to pick up just from examples. Something like “centering a “” is, like, famously a problem. Or remember the 2000s when you’d read A List Apart for all sorts of crazy ways to achieve the “Holy Grail” layout, specifically a header, a main body and sidebar of equal heights, and a footer? Given that it’s such a mess, should we maybe just throw it out and start over with a totally different system?
I do feel like I can speak on this with some authority. In grad school I wrote the first formal specification of the CSS 2.2 spec; the formal spec passed the (relevant fragment of) the conformance test. So I know the existing algorithm in quite a lot of detail, though of course I’m going beyond that expertise when I talk about what designers do or about other systems.
Constraints
One commonly‑proposed replacement for CSS is a constraint system. In a constraint system you can just directly say:
(obj.top + obj.bottom) / 2 == (obj.parent.top + obj.parent.bottom) / 2
This line constrains the vertical midpoint of obj to be the vertical midpoint of its parent; in other words, it constrains it to be vertically centered.
And in fact this idea has been explored quite a bit. In CSS, there’s the well‑known Constraint Cascading Style Sheets paper. The idea in that paper is that the web‑page author writes constraints, somewhat like the one above, and then the browser runs a constraint solver which computes positions and sizes for each object that satisfy the constraints. This is much like normal CSS, where the author writes rules and the browser runs a layout algorithm that computes sizes and positions. Naturally in both cases you actually compute a lot more than just sizes and positions: fonts, colors, and so on. But layout is taken to be the “hard part” of the problem, and I don’t really disagree with that.
In fact the authors (especially Alan Borning) have a long history with constraint solvers, and in particular are associated with the Cassowary incremental constraint solver, where “incremental” means it can not only solve the constraints quickly but also re‑solve them extra‑quickly if the page changes a small amount, like in response to JavaScript or user action. Real browsers do that too. And they’ve been quite successful. Most notably, iOS provides constraint‑based layout using a re‑implementation of Cassowary, and I hear it’s quite popular.
What’s wrong with constraints
All that said, I don’t think a constraint system would actually be better for the web. With rule‑based systems like current CSS, the challenge is that the rules are really complex and it’s hard to predict what the layout will be because actually executing the rules in your head is nearly impossible. But with constraint‑based systems, the layout might be literally under‑ or *over‑*determined, in the sense that there might be more than one, or less than one, layout that satisfies your rules.
In fact, writing layout constraints for UIs that aren’t either under‑ or over‑determined is basically impossible and I’m not sure anyone has ever done it. Even the simple “vertical centering” constraint above doesn’t fix the size or position of either box. It doesn’t say that the outer box (obj.parent) should be the minimum possible size to contain its child (obj), or that they should both be on‑screen, or whatever. Maybe you’d write other constraints to achieve that, but the more constraints you write, the greater the chance those constraints will conflict, leaving you over‑determined.
There are a few ways to handle this:
- Under‑determined layouts – add implicit rules (e.g., boxes must stay on‑screen) or optimization criteria (pick the layout that uses the least space).
- Over‑determined layouts – assign weights to each constraint and optimize for breaking the fewest.
This isn’t a crazy way to build a system—it’s basically how LaTeX works—but fiddling with the weights, the exact form of implicit rules, and the optimization criteria becomes, in practice, the actual determiner of how layouts look. Simple implicit rules/criteria/weights lead to bizarre, horrible edge cases, so if you want things to look good you’ll need really complicated ones, and then you’re back to the actual layout being hard to predict from the style sheet.
LaTeX is not widely beloved for its predictable layout. While constraint layout is popular on iOS, it’s notable that iOS only supports a small number of screen/window shapes. People still complain that constraint‑based layout is fussy, brittle, and unpredictable, with debugging (especially of under‑ and over‑constrained layouts) considered tedious and annoying. It’s also considered verbose; given that there are conventions and patterns in UI design, it would be nice to express those patterns modularly, but constraint‑based systems—especially when you start adding implicit rules or optimization criteria—are explicitly not modular.
I do applaud Apple for shipping it, and I’m happy people are using it and it solves their problems. LaTeX solves mine! Newer students seem really excited about Typst, which I have not tried yet. But I do think designers in constraint‑based systems suffer exactly the kinds of problems theory would predict.
What’s the underlying problem?
So why is this so hard? Why do we get all these weird edge cases whenever we do layout? I think the issue is that layout actually is quite hard.
Here, let me give you an example. Here’s a line from my formal semantics of CSS 2.2, the specification of text-align: center:
[(is-text-align/center textalign)
(= (- (left-outer f) (left-content b))
(- (right-content b) (right-outer l)))]
This says that if a container b has text-align: center specified, then its left gap (the x position of the left outer edge of its first child f, minus the left content edge of the container b) equals its right gap (similar, using right edges, the last child l, and the subtraction being reversed). It’s a constraint!
But if you go a few lines up (see here), you’ll see that before actually applying this constraint we first check if the container is even big enough to contain the content, and if not, we left‑align it no matter what.
What? Really? Yep. It’s a quirky part of the CSS semantics (see section 9.4.2 of CSS 2.1). The controlling standard on this exact quirk is now CSS Text Level 3, which documents the behavior clearly: if text is centered inside a box too small to contain it, we don’t want it spilling out the left edge (it might go off‑screen, where the user cannot scroll); left‑aligning ensures it only spills out on the right.
That’s a funky quirk, but you may have never noticed it, and if you did, this edge case was probably better than what the layout would have been otherwise. In other words, building this edge case into the definition of text-align was a smart choice by the CSS designers, embedding hard‑earned design wisdom into intuitive rules that people mostly use without issue.