Profile picture
Nicolas @necolas
, 21 tweets, 6 min read Read on Twitter
A brief analysis and comparison of the CSS for Twitter's PWA vs Twitter's legacy desktop website. The difference is dramatic and I'll touch on some reasons why.
Legacy site *downloads* ~630 KB CSS per theme and writing direction.

6,769 rules
9,252 selectors
16.7k declarations
3,370 unique declarations
44 media queries
36 unique colors
50 unique background colors
46 unique font sizes
39 unique z-indices

cssstats.com/stats?url=http…
PWA *incrementally generates* ~30 KB CSS that handles all themes and writing directions.

735 rules
740 selectors
757 declarations
730 unique declarations
0 media queries
11 unique colors
32 unique background colors
15 unique font sizes
7 unique z-indices

cssstats.com/stats?link=htt…
The legacy site's CSS is what happens when hundreds of people directly write CSS over many years. Specificity wars, redundancy, a house of cards that can't be fixed. The result is extremely inefficient and error-prone styling that punishes users and developers.
The PWA's CSS is generated on-demand by a JS framework that manages styles and outputs "atomic CSS". The framework can enforce strict constraints and perform optimisations, which is why the CSS is so much smaller and safer. Style conflicts and unbounded CSS growth are avoided.
How does the PWA do responsive design with 0 media queries? Modern responsive design is about conditionally rendering entire component trees, and making components adapt to their own dimensions. You need to use 'ResizeObserver' for that. Media queries in CSS aren't good enough.
In fact, putting state in CSS (:hover, @media, etc) is as much of a problem for dynamic web apps as putting state in the DOM. Removing state from style definitions simplifies coordinating changes to trees & styles, and opens the door to more native-feeling interactions.
The Twitter PWA is a good example of how a huge, highly dynamic app benefits from a simpler "styles in JavaScript" paradigm (powered by a subset of CSS) that is significantly more effective and reliable than working directly with CSS or CSS-in-JS.
There are tradeoffs and new possibilities from moving the styles into JavaScript. Roughly speaking, this:

// *.css
.name { prop-name: value; … }
// *.html
<div class="name">

Is now written as:

// *.js
let name = { propName: value, … }
<div className={name} />
And renders to:

<div class="jzrps nibae lupp mihax">

The impact on over-the-wire size of more HTML classes per element is negligible, whether in HTML or JS; it's massively offset by CSS byte savings and style safety.

Previous exploration nicolasgallagher.com/about-html-sem…
The interesting tradeoff is from moving the style *rules* into JS. Simple style rules as JS objects (not strings!) can even compress better than their CSS equivalents! Styles are collocated with their renderer and code-split along the same seams as the JS. Simplification!
It's important for web apps to be able to prioritise, parallelize, and incrementally download complete *components*. You can't even render without the styles, so they need to be there as soon as the JS is ready.
Once the styles are in JS it's also possible to perform further build-time transforms to influence over-the-wire size and runtime work. But there are tradeoffs here too.
Living products are in a constant state of change. The Twitter PWA is deployed every day. If the styles for hundreds of components are extracted to a shared stylesheet, a single style change in any component could require all the CSS to be downloaded again. Not good.
This is one example where even incrementally downloading more code over a longer time period (i.e., amortized cost) may result in a better UX than downloading less code but doing it all upfront, especially if the upfront cost must be paid every time.
It's possible to push less code and do less work, but produce a worse UX. Bundle size metrics don't tell the whole story. How code is loaded, run, and invalidated (over multiple sessions) is also a critical part of perceived performance.
I haven't even mentioned the impact styles in JavaScript can have on app build times, inclusive design, dynamic UIs, native-feeling interactions, component-sharing, testing, etc. Styles in JavaScript make several hard things easy. It's are not for free but neither is CSS.
One last reflection. It's really important to be able to run experiments in your own app rather than relying solely on data others have shared. I remember when we ran the experiment to replace m.twitter.com's logged-out experience with the PWA

We all assumed that metrics would be terrible, especially on slower networks. Moving people from an app that was rendered server-side to one rendered client-side? From hardly any JS to almost everything is JS? Who would have guessed the results would be favourable?!
Yes initial load time increased significantly. But return visits were faster (thanks to SW) and load times between screens went down (thanks to data fetching) so people stayed longer and looked at more screens. The overall PWA experience was better than the SSR experience.
Similarly, the PWA's migration from CSS modules to React Native's flavour of styles in JavaScript was initiated after several months of experimentation produced neutral metrics. Experiments on your product are a great way to test ideas and assumptions

Missing some Tweet in this thread?
You can try to force a refresh.

Like this thread? Get email updates or save it to PDF!

Subscribe to Nicolas
Profile picture

Get real-time email alerts when new unrolls are available from this author!

This content may be removed anytime!

Twitter may remove this content at anytime, convert it as a PDF, save and print for later use!

Try unrolling a thread yourself!

how to unroll video

1) Follow Thread Reader App on Twitter so you can easily mention us!

2) Go to a Twitter thread (series of Tweets by the same owner) and mention us with a keyword "unroll" @threadreaderapp unroll

You can practice here first or read more on our help page!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just three indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member and get exclusive features!

Premium member ($30.00/year)

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!