Fakturoidí CSS
Článek doplňuje nedávný podcast Vzhůru Dolů s Martinem Držkou.
Původní verze Fakturoidu 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, která započala dva roky zpět, tak 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 sami 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 nebý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 jedinný specificity problém.
Two CSS properties walk into a bar.
— Thomas Fuchs 😽 (@thomasfuchs) 28. července 2014
A barstool in a completely different bar falls over.
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.