If you can do it with CSS do it with CSS

I read Twitter with Tweetdeck. And I use the excellent Better Tweetdeck to improve my Tweetdeck experience. And I had an idea.

You see, emoji, much as they’re the way we communicate now, they’re actually quite hard to read. And Slack does this rather neat thing where if you respond to a message with an emoji, it displays that emoji bigger than normal text so you can see it clearly. And some people just write tweets which are, like, two emoji and that’s it, and it would be really handy if they were large enough to read.

So I thought, here’s an idea; how about, if there’s a tweet which is just emoji, then display those emoji larger so they’re easier to see? Only if there are, say, four or less; you don’t want those people who write a whole huge tweet as emoji to get enlarged. Just the ones where someone responds with two little pictures and that’s it; let’s make that nice and visible, like Slack does.

This is clearly a thing for Better Tweetdeck to do. (They already provide a config option to make emoji a little bigger, which I appreciate.) So… how do we do this?

Well, one obvious way is to do it with JavaScript. Every time we read a new tweet, look to see whether it contains nothing but emoji, and if it does and there are less than four of them, add inline styles to make them larger. Job done.

But… that’s not very efficient, is it? You have to do that every time a new tweet appears, in any column, and that happens a lot. What would actually be better is to write some CSS which does this, and add that CSS one time, when you load up, and then you’re done. Have the browser do the heavy lifting, not us. It is a principle with me that if you can do a thing with CSS, then you should do it with CSS. JavaScript is there for things that CSS can’t do. Don’t use JavaScript, which makes you do the work, when you can use CSS and make the browser do it instead. The browser is better than you at it.

So… what you want to do is this. In pure CSS, if there’s a tweet (which in Tweetdeck is a <p> element) which contains <img class="emoji"> and nothing else, and there are four or fewer of these img.emoji elements, then make those images larger.

In pure CSS. No JavaScript. This is harder than it looks. Go and try to work it out, if you don’t believe me.

Well, the key insight here is that if you are an element, and you are :last-child(X), and you are also last-of-type(X), then there can’t be any elements after you which are not the same as you. So, if an img.emoji is the first of its type, and also the first element, and it’s the third last of its type, and also the third last element, then we know that it is element 1 of three identical elements. So an img:nth-child(1) which is also an img:nth-of-type(1) and which is also an img:nth-last-of-type(3) and also an img:nth-last-child(3) must be the first <img> in a group of three <img> elements. So that solves our problem! All we need is a selector which matches an img which is:

  • img 1 in a group of 1 image, or
  • img 1 in a group of 2 images, or
  • img 2 in a group of 2 images, or
  • img 1 in a group of 3 images, or
  • img 2 in a group of 3 images, or
  • img 3 in a group of 3 images, or
  • img 1 in a group of 4 images, or
  • …etc

…and that’s pretty easy, although long, to express as a CSS selector. So, to resize all img.emoji elements where (a) there are only img.emoji elements in this tweet and no text, and (b) there are four or fewer img.emoji in the tweet, we need a selector like this:

p > .emoji:nth-child(1):nth-of-type(1):nth-last-child(1):nth-last-of-type(1), /* 1 of 1 */
p > .emoji:nth-child(1):nth-of-type(1):nth-last-child(2):nth-last-of-type(2), /* 1 of 2 */
p > .emoji:nth-child(2):nth-of-type(2):nth-last-child(1):nth-last-of-type(1), /* 2 of 2 */
p > .emoji:nth-child(1):nth-of-type(1):nth-last-child(3):nth-last-of-type(3), /* 1 of 3 */
p > .emoji:nth-child(2):nth-of-type(2):nth-last-child(2):nth-last-of-type(2), /* 2 of 3 */
p > .emoji:nth-child(3):nth-of-type(3):nth-last-child(1):nth-last-of-type(1), /* 3 of 3 */
p > .emoji:nth-child(1):nth-of-type(1):nth-last-child(4):nth-last-of-type(4), /* 1 of 4 */
p > .emoji:nth-child(2):nth-of-type(2):nth-last-child(3):nth-last-of-type(3), /* 2 of 4 */
p > .emoji:nth-child(3):nth-of-type(3):nth-last-child(2):nth-last-of-type(2), /* 3 of 4 */
p > .emoji:nth-child(4):nth-of-type(4):nth-last-child(1):nth-last-of-type(1) {/* 4 of 4 */
    styles here
}

It looks long and cryptic and mystic, but actually it’s not that complicated at all. And, importantly, this is all the work you have to do. Add that CSS, and then any new tweets that come along which match our criteria get automatically styled to match. You don’t have to inspect every tweet and tweak it. The browser does the work, which is what the browser is designed for. If you can do a thing with CSS, then do it with CSS. Job done.

I’m quite proud of this. There’s an increasing, and depressing, movement to add more JavaScript to web pages, to write more code client side, to deal with huge JS downloads by improving compression rather than by just doing less JavaScript. I, myself, I’m in favour of having CSS do the things it can do, even if you have to be creative to solve that problem. Falling back to JavaScript to do styling is a failure. Use CSS where you can; being clever in how you do that CSS is part of the fun. You don’t need JS for this, really you don’t. CSS actually is awesome.

My pull request at Better Tweetdeck is, at time of writing, still pending. Proof that this technique works is in jsbin. Fingers crossed my PR gets accepted, and we can solve another problem with pure CSS.

Purity is great. And if you’re thinking, CSS can’t solve real problems… check out Bence Szabó‘s amazing pure CSS stacking game. I was open mouthed with awe. If you aren’t, maybe you should spend some time going back over how the web works, and then you will be too.

More in the discussion (powered by webmentions)

  • Gamer Geek responded at twitter.com Stuart Langridge: If you can do it with CSS do it with CSS kryogenix.org/days/2018/08/0…
  • Russell Dickenson responded at twitter.com
  • Birmingham.IO responded at twitter.com Stuart Langridge: If you can do it with CSS do it with CSS by @sil buff.ly/2OqbFWD #PlanetBirmingham
  • Garrett LeSage responded at twitter.com I solved a problem in @BetterTDeck with CSS and didn't use any JS at all because that's clearly best. If you CAN solve a problem with pure
  • Bence Szabó responded at twitter.com
  • Bence Szabó responded at twitter.com Thanks for the shoutout! Interesting problem, reminds me of this article: lea.verou.me/2011/01/stylin… I see your case is slightly different, but stil…
  • Garrett LeSage responded at twitter.com
  • Stuart Langridge responded at twitter.com if it can be shortened I'm interested in how :) I don't think it can; @LeaVerou's approach is indeed similar, but mine requires that all the children…
  • Lea Verou responded at twitter.com What about img.emoji:nth-child(1):nth-last-child(-n+4):nth-last-of-type(-n+4), img.emoji:nth-child(1):nth-last-child(-n+4):nth-last-of-type(-n+4) ~ im…
  • Lea Verou responded at twitter.com Btw "If you CAN solve a problem with pure CSS, then that's the way you SHOULD solve it." is wrong. CSS is Turing complete, so you can implement any pr…
  • Lea Verou responded at twitter.com That said, I do agree that for the case you describe, CSS is the best way to do it. But I disagree with the general aphorism.
  • Alastair Campbell responded at twitter.com There are a few things that aren’t best done with just CSS. For example, if you display new content (e.g. popover) with interactive elements (e.g. lin…
  • Alastair Campbell responded at twitter.com Or anything where the dom needs to be re-structrured, like converting data-tables to lists for responsive sites.
  • Bence Szabó responded at twitter.com Nice, Thank You! 🙂
  • Stuart Langridge responded at twitter.com Interesting! I'll test that out; nice pointer.
  • Stuart Langridge responded at twitter.com Mm... I take your point, but I think there are waaaaay more people doing things in JS that should be done with styles than there are people implementi…
  • Stuart Langridge responded at twitter.com Fair comment!
  • hermaproditus responded at twitter.com
  • Mary Pieroszkiewicz responded at twitter.com
  • Robert Ivan responded at twitter.com I wanna see a CSS calculator now
  • Lea Verou responded at twitter.com Still, that doesn't mean you have to do *everything* with JS. You can only do the focus management with JS, and everything else with CSS+HTML.
  • Lea Verou responded at twitter.com Googling "Pure CSS calculator" will work wonders towards curing that affliction.
  • Bence Szabó responded at twitter.com experiments.hertzen.com/css3calculator/
  • Alastair Campbell responded at twitter.com Yep, I’m sure we’re all on board with the ‘right tool for the job’ thing, it’s just figuring out what the right tool for different jobs is :-)
  • Robert Ivan responded at twitter.com Ok I’m over it 😂
  • Robert Ivan responded at twitter.com That doesn’t work on my iphone
  • SkipZero responded at twitter.com
  • qheolet responded at twitter.com
  • Catherine_db responded at twitter.com
  • Erik Vorhes responded at twitter.com
  • John Coenen responded at twitter.com
  • Gamer Geek responded at twitter.com Stuart Langridge: If you can do it with CSS do it with CSS kryogenix.org/days/2018/08/0…