https://pavpanchekha.com/blog/why-css-bad.html
Pavel Panchekha
By Pavel Panchekha
03 December 2025
* Blog
* WBE
* Herbie
* Github
* Papers/CV
Share under CC-BY-SA.
Should CSS be Constraints?
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 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 web page author
writes rules and then the browser runs a layout algorithm that
computes sizes and positions.^1 [^1 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
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, layouts that
satisfy 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 onscreen, 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's a bunch of things you could do at this point. For
under-determined layouts, you could add "implicit rules", like saying
that boxes should always be onscreen. Or "optimization criteria",
like saying that if multiple layouts are available you should pick
the one that takes up the least space. And for over-determined
layouts, you could do the reverse, like assigning "weights" to each
constraint and then optimizing 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, the
optimization criteria becomes, in practice, the actual determiner of
how layouts look. And it turns out that 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.
In fact, LaTeX is not widely beloved for its predictable layout. And
while constraint layout is, I believe, popular on iOS, it's notable
that iOS quite famously allows only a small number of screen / window
shapes. And also people still complain about the constraint-based
layout being fussy, brittle, and unpredictable, with debugging
(especially debugging under- and over-constrained layouts) considered
tedious and annoying. Plus, people say it's verbose; given that there
actually are conventions and patterns to how people design UIs, it
would be nice to actually express those patterns and be modular with
regard to them, and constraint-based systems, especially once you
start adding implicit rules or optimization criteria, are quite
explicitly not modular.
I do applaud Apple for shipping and I'm happy people are using it and
it solves their problems. LaTeX solves mine!^2 [^2 Newer students
seem really excited about Typst, which I have not tried but.] 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 pop
up 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 is saying that if a container b has text-align: center
specified, then its left gap (the x position of 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 then if
you go a few lines up 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 tricky little quirk of the CSS semantics,
section 9.4.2 of CSS 2.1.^3 [^3 Actually, I think the controlling
standard on this exact quirk is now CSS Text Level 3 which has a
quite clear paragraph documenting this behavior.] 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 also, you may have never noticed it and if
you did this edge case probably was better than what the layout would
have been. Meaning, actually, 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. (text-align is not considered one of the
bad scary parts of CSS.) And on the contrary, in a "clean"
constraint-based system, web page designers would probably not
bothered to manually add this as a constraint, and probably in quite
a few cases that would result in worse, not better layout.
Generalizing a bit, the challenge is that we're never just "deciding
what our page looks like". We are always designing a layout that is
responsive to parameters like screen size, zoom level, details of
font rendering (Windows and macOS render identical fonts slightly
differently), operating system details, and even higher-level changes
in our application like new content, new features, translations to
other languages,^4 [^4 German words are very long, Chinese ones are
very short.] device oddities like notches, and so on.
Designing a layout from scratch that looks good in any possible one
of those contexts is basically impossible--it's hard enough to do both
desktop and mobile!--and so designers are, by necessity, going to rely
on implicit knowledge encoded somewhere on what to do in edge cases.
There's going to be a huge amount of this implicit knowledge, and
whether it's encoded in rules or weights or optimization criteria
it's going to be opaque to designers and surprising at least
sometimes.
For example, in CSS you can also justify text, stretching spaces
between words so that all lines in a paragraph (except the last!)
have the same right edge. But, famously, if the line width is too
narrow and the line contains too few words that are too long, then
the spaces between them get stretched comically far apart and it
looks terrible. You can do better by enabling hyphenation (which
might turn 2 really long words in a line into 3 or more moderate size
word chunks) or letter-spacing (which might also stretch the spaces
between letters slightly) but those are themselves unpredictable and
language-dependent and still sometimes look ugly.
So where did all these implicit rules about justification come from?
Well, text justification comes from a long Western tradition.^5 [^5
That post claims Trajan's column, built 113 AD, as an early example.]
So what happened in that long Western tradition, before CSS and
computers, when this problem came up? Well, in the olden days, if you
were a newspaper columnist and your column was ugly when justified^6
[^6 Most newspapers justified their text and also ran it in lots of
narrow columns, so it was especially a problem with newspapers.] then
your editor might just rephrase your text into smaller words. That
technology might be slowly becoming possible but is clearly outside
the bounds of what CSS would do. Generalizing, these implicit rules
often draw from traditions where these edge cases simply never came
up! So it's no surprise that there might be no ideal workable of
implicit rules with no edge cases.
So what can we do instead?
I think what we can do, though, rather than trying a ground-up
re-conception of the problem, is to improve the situation by
providing more-intuitive rule systems with more-predictable
less-esoteric rules. For example, when designing CSS layouts, you can
use negative margins and floats and clear: both like we did in the A
List Apart days. Or you can use flex-box and grid. Both are workable
but you can guess which of these I teach to students!
To analogize a bit... JavaScript has all sorts of bizarre mis-behaviors
and semantic oddities like for ... in loops or the with statement or
totally insane semantics and syntax^7 [^7 Look for "Direct and
indirect eval"; eval() is a special syntactic form separate
from function application but there's also a function named eval that
can be applied with normal function application, and they behave
differently.] One solution to this problem is to forsake JavaScript
forever, to use Lisp or Haskell or Rust or something. But another is,
like, Typescript and ESLint, which together make it really easy to
avoid all the bad features of JavaScript and use good versions
instead. C has a similar situation, where strcpy has really simple
but also bad semantics. One option is forsaking C and rewriting in
Rust. Another is using strlcpy.
In CSS specifically, a lot of the problems in CSS layout are problems
more precisely in "Flow layout", the default layout mode which is
optimized around layout out text in a standard Western style. What's
fine for text isn't good for applications, and doing UI design using
text formatting tools like floats and clear was never going to be
simple and intuitive. By contrast, newer layout modes like Flex-box
and Grid are maybe a bit more complex off the bat, but once you grasp
the mental model are quite intuitive with far fewer sharp edges.
So I think in fact the solution is what the CSS committee has been
doing, standardizing new and more intuitive layout modes optimized
for the specific types of layouts being poorly served by what
currently exists. With just a bit of effort, those new layout modes
can be intuitive, with few edge cases, while still building in a lot
of implicit knowledge around, for example, how to respond to
container size changes or handle content that is too big or too
small.
Thanks to my PhD student Andrew Riachi, whose work on his own website
inspired this blog post.
Footnotes:
^1
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.
^2
Newer students seem really excited about Typst, which I have not
tried but.
^3
Actually, I think the controlling standard on this exact quirk is now
CSS Text Level 3 which has a quite clear paragraph documenting this
behavior.
^4
German words are very long, Chinese ones are very short.
^5
That post claims Trajan's column, built 113 AD, as an early example.
^6
Most newspapers justified their text and also ran it in lots of
narrow columns, so it was especially a problem with newspapers.
^7
Look for "Direct and indirect eval"; eval() is a special
syntactic form separate from function application but there's also a
function named eval that can be applied with normal function
application, and they behave differently.