Proč jsme zvolili atomické CSS

Při tvorbě nové verze Fakturoidu jsme zvolili atomická CSS. Dost možná máte proti našemu zápisu class=“flex mb-3 p-3 b b-secondary bg-secondary-lighter br-2 bs-1″ výhrady. Nevadí. My našemu přístupu věříme a pokusíme se ho obhájit. Pojďme na to.
Text vyšel původně na blogu Fakturoidu.
Původní verze naší aplikace (Fakturoid) spatřila světlo světa v roce 2009, kdy metodologie pro organizaci CSS prakticky neexistovaly. Postupné rozšiřování funkčnosti a interakcí znamenalo psaní CSS, které dříve či později muselo vyústit v neudržitelný stav a technický dluh.
Příprava nové verze započala dva roky zpět, a byla ideální dobou zamyslet se (nejen) nad CSS architekturou.
✍️ Prvotní požadavky
- Malá velikost výsledného CSS a minimální navyšování při rozšiřování funkčnosti. Objemné CSS stojí uživatele čas i peníze.
- Sdílení vybraných CSS pro desktop aplikaci a mobilní web, resp. hybridní mobilní aplikaci. Prvotní úvahy nepočítaly s finálním responzivním designem, ve hře byly i separátní mobilní verze.
- Rychlé prototypování nových obrazovek i menších zásahů do UI. Znáte to, i drobné vylepšení microcopy vám může zamíchat s rozhraním.
- Nízká specificita selektorů.
- Technikálie: SASS, generovaná dokumentace, autoprefixování vlastností, apod.
Monolitické frameworky typu Bootstrap 4 (tehdy vyšla první alfaverze, vzpomínáte si ještě?) nebo Foundation jsme brzo zamítli. Ekosystém okolo Bootstrapu byl lákavý, ale s průběžnými návrhy nového rozhraní jsme si jen potvrdili, že bychom využili jen část a valná většina rozhraní by skončila v „custom“ kódu.
⚛️ Atomické, ale jak moc?
Na jaře 2016 vydal Adam Morse (autor knihovny Tachyons) článek CSS and Scalability o úskalích sémantických CSS (nejen BEMu). Najednou bylo mnohem jasnější, že nechceme psát dokolečka nové sémantické komponenty nebo se rozhodovat, zda nová část rozhraní bude novou komponentou, či modifikací stávající. Odpadnou otázky: Jak vlastně pojmenovat Blok, Element, Modifikátor? Jak škálovatelné bude CSS po pár letech vývoje?
Najednou Atomické CSS (chcete-li Funkcionální nebo Utility) začalo pro naše potřeby dávat smysl. Otázkou bylo, v jakém rozsahu.
V té době jsme začali navrhovat první obrazovky nové verze (jen pro informaci, aplikace jich má bez admin sekcí kolem 150) a krystalizoval nám vizuální jazyk rozhraní. Souběžně s tím jsme se pustili do vlastní atomické knihovny, která doplňovala formulářové minikomponenty. Věděli jsme, že klasickým component-first přístupem chceme obsloužit hlavně nejmenší formulářové prvky – button
, input
typy, select
y, ale i custom přepínače (přestylované nativní checkbox
y nebo radio
buttony). Tehdejší atomické knihovny nebyly kompletní (Tachyons, Basscss), případně byly striktně atomické a nelíbila se nám syntaxe (Atomic CSS). Nezbývalo nám, než si připravit vlastní.
Náš přístup k Atomickému CSS
Atomický přístup (zjednodušeně řečeno) znamená nahrazení „sématických“ tříd sadou tříd, které reprezentují samotné vlastnosti. Utilita zpravidla nese jedinou CSS vlastnost (tím je interně oddělujeme od Helper tříd jako .clearfix
). Flexbox container se tak nastaví pomocí .flex
, vlastnost display: block;
reprezentuje třída .d-b
, apod.
Příklad
Takto vypadá jedna z našich pomocných hlášek:
V tradiční B.E.M. metodologii by mohla být zapsaná takto:
<div class="helpbox helpbox--highlighted">
<div class="helpbox__image-column">
<img class="helpbox__image" src="robot-tip.png" alt"">
</div>
<div class="helpbox__feature-column">
<p>
Umím automaticky označovat faktury jako zaplacené podle plateb, které dorazí na váš účet.
</p>
<p class="helpbox__upsell">
<a href="/blank/subscription">Přejděte na placený tarif</a> a budete moci využít párování plateb.
</p>
</div>
</div>
a s robotí atomickou knihovnou…
<div class="flex mb-3 p-3 b b-secondary bg-secondary-lighter br-2 bs-1">
<div class="wf-80 md-wf-100 flex-none self-center">
<img src="robot-tip.png" alt="">
</div>
<div class="md-pl-3 small">
<p>
Umím automaticky označovat faktury jako zaplacené podle plateb, které dorazí na váš účet.
</p>
<p class="mb-0">
<a href="/blank/subscription">Přejděte na placený tarif</a> a budete moci využít párování plateb.
</p>
</div>
</div>
Cože? 😧 To můžu rovnou psát inline styly! 😤
Na první pohled se může zdát, že zápis přes style
by vyšel nastejno. Ale pozor, inline styly:
- mají vysokou specificitu,
- nepodporují podmíněná pravidla jako
@media
,@supports
,@keyframe
,@font-face
, apod., - nezapíšete s nimi pseudotřídy (
:
) a pseudoelementy (::
), - nekešují se,
- jsou zbytečně „ukecané“ (rozhodně více než zápisy většiny atomických knihoven),
- s inline styly lehce narušíte konzistenci. Atomická CSS vás jednoduše omezí barvami pro text nebo škálou pro odsazení.
🤔 Hmm, ale takhle mi nabobtná přenášený HTML kód
Ano, ale zanedbatelně. Snapshot obrazovky s výpisem faktur má 187 kB, GZIPovaná 17 kB. Pokud odstraníme CSS třídy, snížíme velikost na 143 kB (GZIP 14 kB). Výsledný rozdíl = 3 kB. To není tak strašné, ne? A přidáme-li klasické component-first třídy, které budou mít kompresní poměr jistě horší, rozdíl se ještě sníží.
Proč ne osvědčené metodologie jako BEM?
BEM metodologie vnesla do CSS řád, skvěle řeší namespacing a kód je v šabloně bezpochyby čitelnější. Problém nastává u komplexnějších bloků na složitém projektu. Jednotlivé komponenty samy o sobě většinou dávají smysl, ale poskládané v různých kombinacích, na různých obrazovkách nebo s odlišným copy obvykle jako celek netvoří ideální výsledek.
V rodícím se designu jsme samozřejmě nacházeli opakující se návrhové vzory, ale příliš často se lišily, byť jen drobnostmi. Kombinace komponent vyžadovala často jiná odsazení, oddělující prvky nebo potřebu komponentu podbarvit. Přidejte k tomu variace na jednotlivých breakpointech a na složitějším projektu, psaném v čistém BEM zápisu, dříve nebo později skončíte v CSS pekle. Nakonec tvoříte další komponenty nebo jejich modifikace, které zřídkakdy znovu použijete. Zkrátka stav, kterému se zvlášť na „živém“ projektu, jako je Fakturoid, chcete vyhnout. A to jsme ani nenakousli problematiku pojmenování tříd. Ale to je na jiné povídání. Zpět k naší CSS architektuře…
🤖 Pod pokličkou robotí aplikace
Jak bylo zmíněno, nepoužíváme striktně pouze atomická CSS. Odhadem utility pokrývají zhruba 95 % kódu v šablonách (Erb Views). O architektuře CSS asi napoví adresářová struktura:
/vendor
(normalize.css)/base
(resetování, výchozí typografie, tabulky, apod.)/mixins
(SASSové mixiny)/layout
(styly pro layouty obrazovek)/forms
(formulářové prvky psané čistě BEM zápisem)/components
(vše ostatní, co nepokryjeme utilitami)/utility
(sada utilit pro většinu základních CSS vlastností)_variables.scss
(SASS proměnné)style.scss
(hlavní soubor pro import SCSS souborů)
Responsivní design používá 3 breakpointy, pro které jsou připraveny utility. Třídy na breakpointech jsou prefixované na začátku názvu .md-*
, .lg-*
. Mohlo by se zdát, že 3 breakpointy jsou málo, ale zatím si vystačíme. Podle potřeby píšeme pro komponenty tweakpointy pro odlišné chování mimo hlavní „zlomy“.
⭐️ Zajímavosti
- BEM komponenty mícháme (s čistým svědomím) s utilitami. BEM komponenta má třeba jen definovaný obrázek na pozadí a v HTML je doplněná o ostatní vlastnosti utilitami. Díky tomu je komponenta lépe znovupoužitelná.
- Utility mají nejnižší možnou specificitu, importují se na konec hlavního souboru a slouží jako modifikační třídy.
- Škála pro spacingové utility (
margin
,padding
) má 5 stupňů. Každé odsazení v aplikaci může nabývat pouze jednu z těchto hodnot. - Barevnost je definována přes 5 „prioritních“ proměnných (
$primary
,$secondary
, …), z nichž každá má navíc odstupňované odstíny$primary-light
,$primary-lighter
,$primary-dark
,$primary-darker
. Vedle toho máme 5 stupňů na škále od bílé do černé. Maximálně počet použitelných barev v rozhraní aplikace je 30 (pokud nepočítáme barvy štítků a barvy vzhledů faktur). - Nejběžnější barevné kombinace (pozadí vs. popředí textu) mají dostatečný kontrast barev (AAA nebo AA). Doporučujeme nástroj Contrast Grid.
🏁 Půl roku po spuštění nového Fakturoidu
Fakturoid je (opravdu) živý organismus. Denně dostává menší či větší vylepšení nebo opravy. Mimo jiné i díky tomu, že kódování a prototypování přímo v kódu se znatelně zrychlilo. Jeho frontend tvůrci nemusí přepínat neustále kontext mezi CSS a HTML.
Ukázka prototypování pomocí atomické knihovny Tailwind
Po ustálení knihovny píšeme nové CSS opravdu výjimečně. Zároveň odpadla nutnost přemýšlet nad pojmenováním tříd.
There are only two hard things in Computer Science: cache invalidation and naming things.
— Phil Karlton
Nepamatujeme, že bychom řešili jediný specificity problém.
Two CSS properties walk into a bar.
A barstool in a completely different bar falls over.
— Thomas Fuchs 😽 (@thomasfuchs) 28. července 2014
Ve srovnání s původní (neresponzivní) verzí jsme snížili velikost CSS na méně než polovinu (44 kB → 19 kB GZIP). A očekáváme, že s rozšiřováním funkčnosti by neměla narůstat.
Robot generuje styleguide přes Hologram. Díky převaze utilit by asi bylo lepší ji nazývat dokumentační knihovnou. Všechny utility mají ukázky, dokumentace modifikací a definovaných breakpointů.
Opakované části většího kódu řešíme pomocí partial šablon. Na úrovni složitějších formulářových komponent (např. vícepolohové přepínače) nám zase pomáhají railsové helpery, které dokáží nagenerovat prvek s definovanou sadou utilit i potřebnými HTML atributy. Obojí šetří čas a přispívá ke konzistenci. Přesto se objevuje pár částí, které se v kódu opakují často a nejspíš si časem zaslouží vytáhnout do komponent.
Nakonec je fér zmínit i nevýhodu v podobě horší skenovatelnosti šablon – prostě se v nic teď o trochu hůř orientuje. To je ale daň, se kterou jsme počítali.
Je to strašne hlúpe takto písať CSS.
Je možném rozepsat proč? Je totiž strašně hloupé psát komentáře jen o svých pocitech, ze kterých si ostatní nic moc nevezmou. Jaké jsou hlavní důvody toho, proč považujete tento způsob za hloupý?
5 dôvodov:
– jde proti tomu na čo bol CSS určený a jak mal byť používaný…
– neprehladný, zaplevelený, nechutný HTML kód.
– ťažké úpravy (trebárs CSS Grid Areas v HTML classoch? asi ne no…)
– opakovanie
– omedzujúca funkcionalita… stejne CSS budete musieť otvárať, kedže už vidím jak by ste 255^3 farieb nacpali do classov a cez 20 tisíc^2 rozmerov veľkostí v rôznych jednotkách.
Vaše argumenty nijak nereflektují kontext a potřeby, které autor řešil, ač je v článku popsal. Moudrost od hlouposti lze rozeznat spíše hodnocením souladu volby s vytknutými cíli a potřebami. Nikoli podle volby samotné
👍👍 Děkuji za rozepsání! (Poděkování neznamená souhlas.)
Vít Heřman to sepsal lépe než já, takže jen doplním…
1) Atomické CSS třídy nejsou inline styly, nedefinují atributy na HTML. CSS vlastnosti jsou stále definované v separátním souboru. Jen lze třídy častěji znovu použít. Furt je tam ale zmiňovaná separace obsahu od vzhledu.
2) Tady souhlas, nicméně dá se na to zvyknout a v článku je to zmíněno. Furt si stojím za tím, že výhody v našem případě převládají. Mimochodem „zaplevelené“ CSS Vám nevadí?
3) Ano, CSS Grid (narozdíl od Flexboxu) si zatím nedokážu představit abstrahovat do single-purpose tříd. CSS Fakturoidu ale nejsou striktně atomická, můžeme je psát komponentově (nebo chcete-li sémanticky). CSS Grid jsme zatím nepotřebovali a v době vzniku nebyl ani zásadně podporován. Časem uvidíme, zda bude potřeba a jak s tím naložíme.
Možná by stálo za to prozradit, na jakém typu projektů pracujete (prezentační weby / aplikace). Jakou CSS metodologii (nebo architekturu) upřednostňujete. Nebo jaký CSS framework používáte. Našel bych mnoho příkladů, kde (zvlášť striktně) atomické styly nedávají smysl. Třeba jsou vaše projekty právě tohoto typu.
Já si zatím pro sebe brainstormuji tyhle tři zápisy:
Známe dobře z historie, že ano?
Všechny tři mají stejnou linku, která jasně odrazuje. Těžko si můžeme pomoct, abychom i v tom posledním neviděli ten první, středověký. Na druhou stranu je v každém kroku pokrok. Každý z těch kroků = nová generace. S lepšími možnostmi. Hledám pro sebe, jak moc mám ještě nenávidět ten poslední zápis. A hlavně kdy ano a kdy ne.
Většiny výtek, které napsal Mlocik, jsem si byl už vědom. Nezaskočily by mě, snad jim předejdu. Takže pro mě nemají smysl. Myslím, že nikdo snad nebude vytvářet třídy pro všechny barvy, ale bude mít předem danou paletu barev – třeba v designovém manuálu/styleguide, který si tým sepíše – pro tu třídy vytvoří a pak už je sází atd., že jo? Podobně další výtky.
Tady by asi pro příští text o atomickém CSS chtělo víc zdůraznit nějaká pravidla, která pomůžou se těm slepým uličkám vyvarovat, a která každý zřejmě nevidí. Možná něco, co ty Martine a další děláte podvědomě, ale do těch atomických návodů se to nedostane.
Myslím si, že hlavní přínos atomických CSS na Fakturoidu není ani tak o zápisu, ale o tom, že byl v podstatě na míru napsán takový malý a asi nepříliš obsáhlý DSL nad CSS, který narozdíl od sémantického přístupu nebude po stabilizaci vyžadovat téměř žádnou údržbu. Krom toho pokrývá jasně a přesně specifika projektu, reálně zjednodušuje stylování přesně takovým způsobem, který autor potřebuje. Toto nedokáže zajistit ani univerzální CSS framework. Třeba Bootstrap je vynikající a poměrně obstojně konfigurovatelný, ale vždy s vědomím, že bude lepší své potřeby přizpůsobovat Bootstrapu, než-li obráceně (chci-li ho opravdu využít).
Jednoduše byla použitá správná věc pro daný úkol. Obhajoba obstála :-)
Zda obhajoba obstojí, to ukáže čas. Ale já nesoudím Fakturoid, zamýšlím se čistě obecně nad správným použitím technologií.
Tahle formulace se mi líbí:
O to bych se při úvahách o atomickém CSS opíral.
Základ knihovny vykrystalizoval někdy před rokem a půl. Od té doby se rozrůstá opravdu minimálně a vznikly desítky nových obrazovek aplikace. Nevím jak se povedla obhajoba, ale zatím se nám s mixem atomických tříd a komponent pracuje skvěle :)
Jen s tím DSL to IMHO není úplně pravda… Osobně jsem si na stejném stacku/architektuře (ano v mírně očesané podobě) vyzkoušel i několik soukromých projektů, menších/středních prezentační webů, a taktéž bez problémů. Jen jsem si jinak nakonfiguroval barevnosti, spacing škálu, typografii, apod. Výhodu atomických knihoven vidím mimo jiné v přenositelnosti na jiný projekt (ne každý). Píšu stejné názvy tříd, které znám téměř nazpaměť. Třída
.d-b
, bez ohledu na projekt, vždy nastavídisplay: block;
a zároveň jsem si jistý, že nic nerozbije.Nepochybuji, že stejný stack (s jinou konfigurací a opět trochu očesaný) použijeme i pro nový robotí web.
To ale přesně vyhovuje definici DSL. Ono to omezení neznamená nutně na jeden projekt. Doménou může být klidně více (nejen!) vašich projektů. Důležité je, že to má limitovat přímé využití CSS. Koukal jsem na Fakturoid a je pravda, že .d-b jen mapuje na stejný výraz CSS. Ale celkově to nic nemění na tom, že jste si vytvořili vlastní výrazový aparát (jazyk) jako výrazně redukovanou podmnožinu CSS.
Koneckonců Bootstrap je také kombinace DSL a komponent. Jeho utility třídy nejsou ničím jiným, než DSL.
Neberte to ale dogmaticky. Spíš jsem toto označení použil proto, abych vynesl strukturální aspekt tohoto řešení a trochu potlačil povrchnější úvahy nad hezkostí/ošklivostí zápisu.
Ja sa designu (teda ani CSS) moc nevenujem, pracujem najmä na web aplikáciách zväčša v Angulari, jinak Electron/Cordova aplikace, jinak jazyky JS (i node.js), Scala a Go. Zatial když som ptreboval psát CSS mi stačil objektový prístup s pravidlo „#kde .jak“ a zatím jak CSS znám (možno až tak moc jak Vy asi ne), tak mi to prijde ako celkom v pohode.
Ono to celkem v pohodě může být. Jde ale o další požadavky jako když třeba chcete maximalizovat re-using CSS tříd, potřebujete se v týmu orientovat v CSS třídách a CSS stylech, zabránit mrtvému CSS zejména na sdílených projektech, atd. Ad-hoc řešení, které dělal kolega pak je často peklo největší. Taky jsem hlavně programátor, byť s přesahem do kódování designu, ale důsledné OOCSS (a příbuzné metodiky) je zatím nejlepším vynálezem jak CSS dostat pod kontrolu. A ty atomické CSS ještě trochu posouvají hranice jinam a minimálně má smysl to prověřit.
Z vlastní (cca 15leté) zkušenosti s CSS vím, že přístup
#kde .jak
u projektu, který žije a permanentně se delší dobu rozvíjí, je opravdu cesta špatným směrem. Ostatně CSS původní robotí verze byly jasným důkazem.Tenhle přístup se mi strašně líbí. Nemusím nic psát do css a přitom se mi web rodí pod rukama.
Jedu amatérsky Bootstrap už několik let a jsem rád, že se ty utils classy stávají populárnější.
Díky za shrnutí a vysvětlení proč a výhod a nevýhod!
Nevím… mě to připadá jako by někdo začal propagovat spagetti kód a globální proměnné. Resp. nepochopil jsem důvody, proč to dělat. Jasně, pokud mám na všechno „sémantické“ šablony, např. alert_success, alert_danger, tak to může fungovat, ale jinak jsou jasné nevýhody: nekonzistence (každý to dělá jinak), nízká míra deskriptivnosti, obtížná změna designu, refaktoring musí změnit veškeré html, není schopné se přizpůsobit zobrazení v odlišných prostředích…
Tvrdit, že 3kB není mnoho v době omezených mobilních dat je nesmysl. Je to jen hra se statistikou, čtenář se podívá a řekne si, no opravdu 3kB nejsou takový rozdíl. Řekněme si kolikrát si můžeme takovou stránku bez „cache“ stáhnout při limitu 1GB (nezáleží na tom jaký je, vždy se to jen vynásobí nějakou pěknou konstantou). 1GB / 17kB ~ 60000 (bude to něco málo pod nebo přes, záleží zda počítáme GiB nebo GB). Pro tu menší verzi je to 1GB / 13kB ~ 80000 (viz nahoře). No a nyní si spočítejme o kolik takových stránek si můžu za měsíc prohlédnout více? No je to 17/13 ~ 1,308, což znamená, něco okolo 30% více stránek s menší verzí. Pro uživatele, který si ve 3. týdnu vyčerpal data to znamená, že by mu při nižší variantě mohli vydržet skoro celý další týden. A to už není zas tak zanedbatelné.
existuje pojem service-worker a pojem PWA.
To je teoretická úvaha, avšak zcela nevhodně aplikovaná. Předpokládáte, že uživatel využije celou svou mobilní kapacitu pro Fakturoid. Pro realistickou představu byste měl ale uvažovat realistický vzorek, třeba průměrný počet zobrazených stránek za měsíc nebo počítat s nějakým rozumným rozložením. Pak dojdete ke zcela opačnému závěru o té zanedbatelnosti :-)
Autor udělal tu chybu, že napsal „rozdíl = 3 kB“. Kdyby napsal „rozdíl = 2%“, tak by neinspiroval k zavádějícím počtům se spoustou neověřených předpokladů. Mám spoustu výhrad k a tomickým css, ale velikost to opravdu není.
BTW 60000 za měsíc? Tedy 2000 denně? To imho není úplně běžný use-case. Zato to 1 GB vcelku ano.
Tak samozřejmě je takový způsob zápisu CSS v podstatě prasárna, která jde proti duchu CSS. Podobně to má ale třeba i onen zmiňovaný Bootstrap, a bohužel to dnes nejde dělat o moc lépe. Co v CSS chybí, je možnost říci, že nějaká třída má zdědit vlastnosti z několika dalších tříd najednou. Tj. aby bylo možné v HTML zapsat něco ve stylu:
A v CSS se pak řeklo, jak má taková tabulka vypadat odkazem na mnoho dalších tříd, za kterými je schováno konkrétní další vzhled a chování:
Bohužel čisté CSS tohle neumí a CSS preprocessory to umí jen v omezené míře.
Podobně to dělám v LESS a funguje to dle očekávání. Zatím jediný zádrhel, na který jsem přišel, je to, že to může být matoucí, když se to ladí v prohlížeči. Není z toho moc poznat, co je kde definované.
a nenapadlo Vás podať návrh do W3C?
Můžete naznačit, v čem jsou podle Vás preprocesory v tomto omezené? Podle mne spolehlivě tu úroveň abstrakce zajistí.
Ale jinak jak píšete, lépe to moc nejde. A já bych jen dodal, že ani nemůže z principu jít. Buď redukujete duplicity, kladete důraz na čistou sémantiku a skončíte se složitou a obtížně udržovatelnou taxonomií nebo uděláte jednoduchou taxonomii, která má mnoho výhod, avšak může působit trochu jako prasárna.
Nejsmutnější je, že jsou vůbec potřeba. Tím, že to není nativní funkce v CSS, preprocesory musí u mnoha deklarací přidávat další selektory a výsledné CSS pak může dost nakynout.
Jj, o tom žádná, že by bylo nejlepší, kdyby tohle umělo nativní CSS. Mne jen zajímalo „to něco“ , co podle Vás nejde udělat ani s preprocesory (a bez ohledu na výsledné CSS). Jednoduše mám pocit, že mixiny tento problém řeší, a to zcela. A třeba se mýlím a jen něco nevidím :-)
Ještě mě napadá další nevýhoda těch preprocesorů, že kdybych chtěl udělat .trida_elementu {.trida_s_barvou}, tak kdybych chtěl mít více variant souborů s .trida_s_barvou a ty podle potřeby proměňovat, tak mi nezbývá než sestavovat to pro každou variantu zvlášť nebo to sestavovat dynamicky v reálném čase. Takže kdyby to CSS uměly nativně, byl by to velký důvod k oslavě.
Tohle třeba umí https://tailwindcss.com/docs/what-is-tailwind/. ;)
Tu špatnou čitelnost by řešilo pojmenování tříd bez zkratek – místo
.d-b
psát.display-block
. Je to sice delší, ale bude rozdíl při kompresi podstatný?Není možné se podělit o kód atomického css? :) Rád bych získal inspiraci a z minifkovaného souboru se to čte celkem blbě.