
CSS item-pack: balance – vyvážené rozložení flex položek
Vlastnost item-pack: balance rozděluje flex položky rovnoměrně mezi řádky, podobně jako text-wrap: balance vyvažuje řádky textu.
Vlastnost item-pack: balance je součástí nového návrhu Item Flow, který sjednocuje ovládání rozmisťování položek ve flexboxu a CSS gridu. Funguje podobně jako text-wrap: balance pro text – jen místo slov vyvažuje flex položky mezi řádky.
Problém s posledním řádkem
Flexbox při zalamování (flex-wrap: wrap) umisťuje položky na řádek, dokud se vejdou. Když se další položka nevejde, přesune se na nový řádek. To často vede k tomu, že na posledním řádku skončí jen jedna nebo dvě osamocené položky:
/* 7 položek v kontejneru o šířce 600px */
.container {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.item {
width: 150px;
}
Výsledek bez balance:
┌──────┐ ┌──────┐ ┌──────┐
│ 1 │ │ 2 │ │ 3 │
└──────┘ └──────┘ └──────┘
┌──────┐ ┌──────┐ ┌──────┐
│ 4 │ │ 5 │ │ 6 │
└──────┘ └──────┘ └──────┘
┌──────┐
│ 7 │ ← osamocená položka
└──────┘
Tento problém je obzvlášť viditelný u navigací, seznamů ikon, galerie tagů nebo karet. Poslední řádek vypadá neúplně a neesteticky.
Řešení: item-pack: balance
Vlastnost item-pack: balance říká prohlížeči, aby položky rozdělil rovnoměrně mezi řádky. Místo naplnění řádků na maximum a ponechání zbytku na konci se prohlížeč snaží dosáhnout co nejrovnoměrnějšího rozložení:
.container {
display: flex;
item-wrap: wrap;
item-pack: balance;
gap: 1rem;
}
Výsledek s balance:
┌──────┐ ┌──────┐ ┌──────┐
│ 1 │ │ 2 │ │ 3 │
└──────┘ └──────┘ └──────┘
┌──────┐ ┌──────┐
│ 4 │ │ 5 │
└──────┘ └──────┘
┌──────┐ ┌──────┐
│ 6 │ │ 7 │
└──────┘ └──────┘
Prohlížeč automaticky přerozdělí položky tak, aby žádný řádek nevypadal příliš prázdně. Přesná strategie závisí na implementaci – cílem je minimalisovat rozdíl v obsazenosti jednotlivých řádků.
Analogie s text-wrap: balance
Princip je stejný jako u text-wrap: balance, kterou prohlížeče již podporují. Tato vlastnost vyrovnává délky řádků textu, aby se zabránilo situaci, kdy poslední řádek obsahuje jen jedno krátké slovo:
/* text-wrap: balance pro text */
h2 {
text-wrap: balance;
}
/* item-pack: balance pro flex položky */
.tags {
display: flex;
item-wrap: wrap;
item-pack: balance;
}
Zatímco text-wrap: balance pracuje se slovy v textovém bloku, item-pack: balance pracuje s elementy ve flex kontejneru. Princip je ale totožný – rovnoměrné rozdělení obsahu mezi řádky.
Součást návrhu Item Flow
item-pack je jednou z vlastností nového návrhu Item Flow, který přichází od WebKitu. Cílem je sjednotit flex-flow a grid-auto-flow do jednoho systému. Návrh zavádí tyto vlastnosti:
| Vlastnost | Popis | Hodnoty |
|---|---|---|
item-direction | Směr toku položek | row, column, row-reverse, column-reverse |
item-wrap | Zalamování položek | nowrap, wrap, wrap-reverse |
item-pack | Způsob vyplňování | normal, dense, balance |
item-slack | Tolerance přetečení | délková hodnota (např. 10px) |
item-flow | Zkrácený zápis | kombinace výše uvedených |
Zkrácený zápis item-flow by měl sloužit jako alias pro flex-flow i grid-auto-flow:
/* Flexbox s balance */
.flex-container {
display: flex;
item-flow: row wrap balance;
}
/* Grid s masonry */
.grid-container {
display: grid;
grid-template-columns: repeat(3, 1fr);
item-flow: row collapse;
}
Hodnoty item-pack
Vlastnost item-pack přijímá několik hodnot, které lze i kombinovat:
normal- Výchozí chování. Flex i grid se chovají jako dosud.
dense- Husté vyplňování – prohlížeč se snaží zaplnit mezery menšími položkami, i za cenu změny pořadí. Obdoba současného
grid-auto-flow: dense, nově dostupná i pro flexbox. balance- Rovnoměrné rozložení položek mezi řádky. Prohlížeč se snaží o co nejmenší rozdíl v obsazenosti jednotlivých řádků.
collapse- V CSS gridu aktivuje masonry (zděný) layout – položky se skládají do sloupců bez pevných řádků.
Hodnoty dense a balance nebo dense a collapse lze kombinovat:
/* Masonry layout s hustým vyplňováním */
.gallery {
display: grid;
grid-template-columns: repeat(3, 1fr);
item-pack: dense collapse;
}
Praktické využití
Navigace a seznamy tagů
.tags {
display: flex;
item-wrap: wrap;
item-pack: balance;
gap: .5rem;
}
.tag {
padding: .25rem .75rem;
background: #e2e8f0;
border-radius: 1rem;
}
Tagy se rovnoměrně rozloží mezi řádky místo hromadění na prvních řádcích.
Seznam ikon
.icon-grid {
display: flex;
item-wrap: wrap;
item-pack: balance;
gap: 1rem;
justify-content: center;
}
.icon {
width: 48px;
height: 48px;
}
Dnes se pro vyvážení ikon používá workaround s display: inline-block a text-wrap: balance na rodiči. S item-pack: balance to půjde přímo ve flexboxu, včetně podpory pro gap.
Galerie karet
.cards {
display: flex;
item-wrap: wrap;
item-pack: balance;
gap: 1.5rem;
}
.card {
flex: 1 1 300px;
max-width: 400px;
}
Co lze používat dnes
item-pack: balance zatím žádný prohlížeč nepodporuje. Návrh Item Flow je ve fázi diskuse v CSS Working Group. Názvy vlastností i hodnot se mohou ještě změnit.
Existuje ale několik workaroundů, jak balance efekt dosáhnout už dnes.
CSS grid s round() a :has()
Nejelegantnější čistě CSS řešení využívá funkce round() a selektor :has() k výpočtu optimálního počtu sloupců. Místo naplnění řádků na maximum a ponechání osamocené položky na konci se počet sloupců sníží tak, aby se položky rozložily rovnoměrně:
.balanced-grid {
--n: 1;
--min-w: 12rem;
--gap: 1rem;
--max-cols: max(1, round(down,
(100cqw + var(--gap)) / (var(--min-w) + var(--gap))
));
--rows: round(up, var(--n) / var(--max-cols));
--cols: max(1, round(up, var(--n) / var(--rows)));
display: grid;
gap: var(--gap);
grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
}
/* Počítání položek přes :has() */
.balanced-grid:has(> :nth-child(2)) { --n: 2; }
.balanced-grid:has(> :nth-child(3)) { --n: 3; }
.balanced-grid:has(> :nth-child(4)) { --n: 4; }
.balanced-grid:has(> :nth-child(5)) { --n: 5; }
.balanced-grid:has(> :nth-child(6)) { --n: 6; }
.balanced-grid:has(> :nth-child(7)) { --n: 7; }
.balanced-grid:has(> :nth-child(8)) { --n: 8; }
.balanced-grid:has(> :nth-child(9)) { --n: 9; }
.balanced-grid:has(> :nth-child(10)) { --n: 10; }
.balanced-grid:has(> :nth-child(11)) { --n: 11; }
.balanced-grid:has(> :nth-child(12)) { --n: 12; }
Jak to funguje:
- Počítání položek – selektor
:has(> :nth-child(n))zjistí počet přímých potomků a nastaví proměnnou--n. Pokud kontejner má 7 dětí, pravidlo:has(> :nth-child(7))se aktivuje a nastaví--n: 7. - Maximální počet sloupců –
--max-colsse spočítá z šířky kontejneru (100cqw) a minimální šířky položky (--min-w). Funkceround(down, ...)zaokrouhlí dolů – kolik sloupců se vejde. - Počet řádků –
--rowsjeround(up, n / max-cols), tedy kolik řádků by bylo při maximálním počtu sloupců. - Vyvážený počet sloupců –
--colsjeround(up, n / rows). Toto je klíčový krok: místo maximálního počtu sloupců se vypočítá optimální počet tak, aby se položky rozložily rovnoměrně.
Příklad s 5 položkami a maximálně 4 sloupci: bez balance by výsledek byl 4 + 1 (čtyři v prvním řádku, jedna osamocená). S tímto výpočtem: rows = ceil(5/4) = 2, cols = ceil(5/2) = 3 – výsledek je 3 + 2, tedy rovnoměrné rozložení.
Použití 100cqw (container query width) vyžaduje, aby rodič byl kontejner (container-type: inline-size). Alternativně lze použít 100% nebo 100vw.
Nevýhoda tohoto přístupu je nutnost ručně zapsat pravidla :has() pro každý možný počet položek. V praxi stačí pokrýt rozumný rozsah (např. do 12 nebo 15). U reaktivních JS frameworků bude pravděpodobně jednodušší nastavit proměnnou --n přímo v JavaScriptu a pravidla s :has() vynechat.
text-wrap: balance pro inline elementy
Jednodušší workaround pro inline elementy (tagy, ikony) využívá text-wrap: balance na rodiči:
.tags-wrapper {
text-wrap: balance;
text-align: center;
}
.tag {
display: inline-block;
padding: .25rem .75rem;
margin: .25rem;
background: #e2e8f0;
border-radius: 1rem;
}
Tento přístup funguje, protože text-wrap: balance vyvažuje jakýkoliv inline obsah – nejen text, ale i inline-block elementy. Nevýhoda je, že neumožňuje použít gap (nutnost řešit mezery přes margin) a chybí další výhody flexboxu a gridu.
JavaScript řešení
Pro složitější případy, kde nestačí CSS, existují JavaScript knihovny:
- Pro masonry layout – Masonry
- Vlastní logika – přepočet počtu sloupců v JavaScriptu podle stejného vzorce jako CSS řešení výše
Souvislost s CSS Masonry
Návrh Item Flow vznikl primárně kvůli řešení masonry layoutu v CSS. Místo zavedení zcela nového display: masonry se pracovní skupina rozhodla rozšířit existující grid o nové možnosti řízení toku položek.
V tomto kontextu je item-pack: collapse klíčová hodnota – říká gridu, aby položky skládal do sloupců bez pevných řádků (masonry efekt). Hodnota balance je pak přirozeným doplňkem pro flexbox.
Firefox už experimentálně podporuje masonry přes grid-template-rows: masonry, ale tento zápis se pravděpodobně změní na systém založený na item-flow.
Odkazy
Co si myslíte o tomto článku?
Diskuse
Související články
CSS Container Queries – responsivní komponenty
Jak pomocí @container, container-type a container-name vytvářet komponenty, které se přizpůsobí velikosti svého rodiče místo viewportu.
CSS Anchor Positioning – posicování elementů relativně k jiným
Jak pomocí anchor-name, position-anchor a funkce anchor() posicovat elementy relativně k jiným bez JavaScriptu.
CSS Stacking Context – jak funguje vrstvení elementů a z-index
Jak stacking context v CSS ovlivňuje z-index, co vytváří nový kontext vrstvení a jak řešit problémy s překrýváním elementů.
Jeden sloupec fixní, druhý proměnlivý
Jak vytvořit dvousloupcové rozvržení, kde je jeden sloupec s pevnou šířkou a druhý se přizpůsobuje šířce okna.