A display a CSS legtöbbet használt tulajdonsága, és valószínűleg az is, amit a legkevésbé értünk. Tudjuk, hogy a flex flexboxot csinál, az inline-block valahogy „egyszerre mindkettő”, a block pedig új sorba tesz — de hogy ezek miért pont így viselkednek, azt a legtöbben sosem tanultuk meg.

Pedig van mögötte egy egyszerű, következetes rendszer. Minden doboznak két display típusa van: egy kifelé és egy befelé. Amikor ezt megérted, egy csomó látszólag független rejtély egyszerre a helyére kerül — a margóösszecsúszástól a clearfixig.

Ebben az útmutatóban végigmegyünk ezen a modellen, kész receptekkel: mi a külső és a belső típus, mi az a block formatting context, mire jó a flow-root, és mit írtál valójában, valahányszor inline-block-ot gépeltél.

Minden doboznak két display típusa van

Kezdjük a lényeggel. Amikor ezt írod:

.card {
    display: flex;
}

akkor a böngésző valójában ezt érti belőle:

.card {
    display: block flex;
}

Nem elírás, és nem is új funkció: a display mindig is két dolgot állított be egyszerre. Csak évtizedekig egyetlen kulcsszóba volt sűrítve mind a kettő, és emiatt a második észrevétlen maradt.

  • a külső típus (block vagy inline) azt írja le, hogyan viselkedik maga a doboz a szülője elrendezésében,
  • a belső típus (flow, flow-root, flex, grid, table, ruby) azt, hogy a doboz milyen szabályok szerint rendezi el a gyerekeit.

A két értékes szintaxis nem ad új képességet — csak kimondja, ami eddig is történt. Épp ezért olyan jó tanulóeszköz: nem megjegyezni kell, hanem elolvasni.

A display nem egy beállítás, hanem kettő: mi vagyok kívülről, és mi történik belül.

A megfeleltetési táblázat

Ez a cikk legfontosabb táblázata. Minden megszokott display érték pontosan egy külső + belső párosnak felel meg:

Amit írsz Amit jelent Kívül / belül
block block flow blokkszintű / normál folyam
inline inline flow sorszintű / normál folyam
inline-block inline flow-root sorszintű / saját BFC
flow-root block flow-root blokkszintű / saját BFC
flex block flex blokkszintű / flex
inline-flex inline flex sorszintű / flex
grid block grid blokkszintű / grid
inline-grid inline grid sorszintű / grid
table block table blokkszintű / táblázat
inline-table inline table sorszintű / táblázat
list-item block flow list-item blokkszintű / folyam + jelölő

Nézd meg egy percig ezt a listát, és látszik a rendszer: az inline- előtag mindig csak a külső típust cseréli le, a belső elrendezés változatlan marad. A flex és az inline-flex belül pontosan ugyanaz a flexbox — csak az egyik külön sorba kerül, a másik a szövegfolyamban marad.

Ha csak az egyiket írod ki: a hiányzó felet a böngésző kitölti. Ha csak belső típust adsz meg (display: flex), a külső block lesz. Ha csak külsőt (display: block), a belső flow. Pontosan ezért egyezik a régi és az új írásmód eredménye.

A külső típus: hogyan viselkedik kifelé

A külső típusnak két értéke van a gyakorlatban: block és inline. Ez a doboz „társasági viselkedése” — az, hogy mit kezd vele a szülője.

  • block — a doboz külön sorba kerül, és alapból kitölti a rendelkezésre álló szélességet. Fölötte és alatta törés van.
  • inline — a doboz beleáll a szövegfolyamba, a sorok között tördelődhet, és csak annyi helyet foglal, amennyit a tartalma kíván.

A fontos, hogy a külső típus semmit nem mond arról, mi történik a dobozon belül. Egy inline doboz belsejében is lehet teljes értékű grid elrendezés — attól még a szöveg közepén fog ülni.

Van egy harmadik, run-in nevű külső érték is a specifikációban, de a gyakorlatban nem implementálta senki. Nyugodtan felejtsd el.

A belső típus: hogyan rendezi el a gyerekeit

A belső típus a doboz „belügye”: milyen elrendezési szabályok szerint pakolja el a gyerekeit. Hat értéke van:

  • flow — a normál folyam: blokkok egymás alatt, sorszintű tartalom sorokba tördelve. Ez az alapértelmezés.
  • flow-root — ugyanaz a normál folyam, de a doboz új block formatting contextet nyit. Erről mindjárt részletesen.
  • flex — a gyerekek flexitemekké válnak, egy tengely mentén rendeződnek.
  • grid — a gyerekek griditemekké válnak, sorokba és oszlopokba.
  • table — táblázatos elrendezés, HTML <table> nélkül is.
  • ruby — kelet-ázsiai kiejtésjelölés elrendezése; ritkán találkozol vele.

Itt van a modell legnagyobb haszna. Amikor egy elemre ráírod, hogy display: grid, nem az elem viselkedését állítod át, hanem a gyerekeiét. Az elem maga ugyanolyan blokkszintű doboz marad, mint volt. Ha egyszer ezt észreveszed, sokkal kevesebb meglepetés ér.

Ugyanez a gondolat viszi tovább a modern elrendezési eszközöket is: ha a komponenseidet nem a képernyőhöz, hanem a saját helyükhöz akarod igazítani, arról szól a container query-kről szóló cikkünk.

Miért számít ez a gyakorlatban?

Nézzünk három klasszikus félreértést, amit ez a modell egy mondatban megold.

„Flexbe tettem, mégis külön sorban van”

Mert a display: flex a belső típust állítja flexre. A külső típus közben block maradt, tehát a konténer továbbra is külön sorba kerül és kitölti a szélességet. Ha azt akarod, hogy a flexkonténer a szövegfolyamban maradjon, a külső típust kell cserélned:

.chip {
    display: inline-flex;   /* = inline flex */
}

„Az inline elemem nem veszi fel a padding-ot”

Egy inline flow doboz függőleges paddingja és margója nem befolyásolja a sor magasságát — ezért lóg rá a szomszédos sorokra. Amire szükséged van, az egy sorszintű doboz, ami belül viszont rendes blokk-környezet: vagyis inline flow-root, ismertebb nevén inline-block.

„Miért nem csúszik össze a margó a flexben?”

Mert a margóösszecsúszás a normál folyam (flow) szabálya. A flex- és griditemek nem a normál folyamban vannak, így rájuk ez a szabály nem vonatkozik. Nem kivétel — egyszerűen más elrendezési modell.

flow-root: a hiányzó darab

A flow-root az egyetlen érték a családban, amit sokan sosem tanultak meg — pedig a leghasznosabb. A neve pontosan leírja, mit csinál: normál folyam, aminek ez a doboz a gyökere.

.wrapper {
    display: flow-root;   /* = block flow-root */
}

Kívülről teljesen hétköznapi blokkszintű doboz. Belül viszont új, önálló elrendezési világot nyit, amiből a dolgok nem szivárognak ki, és amibe kívülről nem szivárognak be. Ez a „világ” a block formatting context.

Ezt nyugodtan használd élesben. A flow-root támogatottsága ~96% (Chrome 58, Firefox 53, Safari 13, Edge 79) — vagyis évek óta biztonságos. Ne keverd össze a két értékes szintaxis támogatottságával, ami ennél alacsonyabb.

Mi az a block formatting context?

A block formatting context (röviden BFC) egy elszigetelt elrendezési terület. Aki létrehoz egyet, arra három dolog lesz igaz:

  1. Bekeríti a benne lévő lebegő elemeket. A float nem tud kilógni belőle, tehát a szülő magassága tartalmazni fogja.
  2. A margói nem csúsznak össze a gyerekei margóival. Amit belül megadsz, belül is marad.
  3. Nem lóg rá a kívülről jövő floatokra. Ahelyett, hogy alá folyna, mellé áll.

Új BFC-t többféleképpen lehet nyitni. A gyakorlatban ezekkel találkozol:

  • display: flow-root — a szándékos, mellékhatás nélküli módszer,
  • display: inline-block (vagyis inline flow-root),
  • overflow, ha nem visible — a régi overflow: hidden trükk,
  • lebegő (float) és abszolút pozicionált elemek,
  • flex- és griditemek,
  • táblázatcellák,
  • contain: layout, content vagy paint,
  • és maga a gyökérelem, a <html>.

Sokáig az overflow: hidden volt a bevett módszer, de annak van egy kellemetlen mellékhatása: tényleg levágja a kilógó tartalmat. Egy fókuszgyűrű, egy tooltip vagy egy negatív margós elem simán áldozatul eshet neki. A flow-root pontosan azért született, hogy a BFC-t mellékhatás nélkül lehessen kérni.

Ha eddig overflow: hidden-t írtál, de nem akartál semmit levágni, akkor valójában flow-root-ot akartál.

Margóösszecsúszás

A margóösszecsúszás (margin collapsing) az a szabály, ami miatt két egymás melletti függőleges margó nem összeadódik, hanem a nagyobbik nyer. Ez a normál folyam sajátja, és három helyen üti fel a fejét:

  • szomszédos testvérek között — az egyik alsó és a másik felső margója összeolvad,
  • szülő és első gyerek felső margója között — a gyerek margója „kiszökik” a szülőből,
  • szülő és utolsó gyerek alsó margója között — ugyanez lefelé.

A második eset okozza a leggyakoribb rejtélyt: azt írod, hogy a kártyán belüli címsornak legyen felül margója, erre a margó megjelenik a kártya fölött, a kártya háttere pedig nem nyúlik odáig. Nem hiba — a margó a szabály szerint kiszökött.

Négyféleképpen állíthatod meg:

  1. display: flow-root a szülőn — a legtisztább,
  2. padding vagy border a szülő és a gyerek közé — egy hajszálnyi is elég,
  3. a szülő legyen flex- vagy gridkonténer — az itemek margói sosem csúsznak össze,
  4. a gyerek legyen abszolút pozicionált vagy lebegő.

A második ezért működik: ha van közte akár 1px padding, a két margó már nem érintkezik, tehát nincs mit összecsúsztatni. Sokan használják, de a flow-root őszintébb, mert nem változtatja meg a doboz méretét.

Receptek

Négy visszatérő helyzet, ahol ez a modell azonnal kifizetődik.

1. Float bekerítése clearfix nélkül

Ha egy konténerben csak lebegő elemek vannak, a konténer magassága nulla lesz — a floatok kilógnak belőle. Erre évekig ez volt a válasz:

/* A régi clearfix */
.wrapper::after {
    content: "";
    display: table;
    clear: both;
}

Ma egyetlen sor, pszeudoelem nélkül:

.wrapper {
    display: flow-root;
}

Ugyanaz az eredmény, csak épp kimondja a szándékot: „ez a doboz kerítse be a saját tartalmát”.

2. A kártya fölötti titokzatos rés megszüntetése

A klasszikus eset: a kártya háttere nem indul ott, ahol várnád, mert az első gyerek felső margója kiszökött.

.card {
    background: var(--surface);
    border-radius: 12px;
    display: flow-root;   /* a gyerek margója bent marad */
}

.card h3 {
    margin-top: 1.5rem;   /* mostantól a kártyán belül mér */
}

A flow-root előnye a paddinghoz képest, hogy nem nyomja beljebb a tartalmat — a belső margókat tudod továbbra is szabadon állítani.

3. Szöveg, ami nem folyik a kép köré

Alapból a szöveg körbeöleli a lebegő képet. Ha inkább egy tiszta, kéthasábos hatást akarsz, a szövegblokkból csinálj BFC-t — az nem lóg rá a floatra, hanem mellé áll:

.figure {
    float: left;
    width: 12rem;
    margin-right: 1.5rem;
}

.body {
    display: flow-root;   /* saját oszlop, nem folyik körbe */
}

4. Címke, ami elbírja a paddinget

Egy sorban lévő, de dobozként viselkedő elem — badge, gomb, címke — pontosan az inline flow-root esete:

.badge {
    display: inline-block;   /* = inline flow-root */
    padding: 0.2em 0.6em;
    border-radius: 999px;
    background: rgba(0,0,0,0.06);
}

Sima inline-nal a függőleges padding nem tolná arrébb a sorokat, hanem ráfolyna a szomszédos sorokra. A flow-root belső típus az, ami valódi dobozzá teszi.

inline-block: amit valójában írtál

Az inline-block talán a CSS legfélrevezetőbb nevű értéke. A név azt sugallja, hogy „egyszerre sorszintű és blokkszintű” — ami önmagában értelmetlen, hiszen a kettő egymást kizárja.

A két értékes forma egy pillanat alatt tisztázza:

display: inline flow-root;

Kívül sorszintű, belül saját block formatting context. Ez magyarázza meg az összes olyan viselkedést, amit külön-külön szoktunk bemagolni:

  • miért marad a szövegfolyamban → mert kívül inline,
  • miért veszi fel a szélességet és a magasságot → mert belül rendes blokk-környezet,
  • miért keríti be a benne lévő floatokat → mert BFC,
  • miért nem csúszik össze a margója a gyerekeiével → szintén mert BFC.

Négy „szabály” helyett egyetlen mondat. Ez a két értékes szintaxis igazi haszna, akkor is, ha soha nem gépeled be.

list-item: a három értékes furcsaság

A list-item kilóg a rendszerből, mert nem külső vagy belső típus, hanem egy harmadik, független összetevő: azt mondja meg, hogy a doboz kapjon-e ::marker jelölőt. Ezért a teljes alakja három szóból áll:

display: block flow list-item;   /* = display: list-item */

És mivel független komponens, kombinálható is. Egy sorszintű listaelem például így néz ki:

display: inline flow list-item;

A gyakorlatban ritkán van rá szükség, de jól mutatja, hogy a rendszer következetes: a jelölő megléte és az elrendezési mód két külön kérdés.

Amire nincs két értékes forma

Nem minden display érték illeszkedik a modellbe — és ez nem hiányosság, hanem logikus. Ezek ugyanis nem elrendezési módok.

Dobozgenerálás: none és contents

A display: none azt mondja, hogy az elem egyáltalán ne hozzon létre dobozt. Ahol nincs doboz, ott értelmetlen kérdés, hogy mi a külső vagy a belső típusa.

A display: contents ennél érdekesebb: az elem saját doboza tűnik el, a gyerekei viszont megmaradnak, és úgy viselkednek, mintha közvetlenül a nagyszülő gyerekei lennének. Ez remek eszköz, ha egy felesleges wrapper miatt nem tudsz gridet vagy flexet használni:

.grid { display: grid; grid-template-columns: repeat(3, 1fr); }

/* A wrapper doboza eltűnik, a gyerekei a grid itemjei lesznek */
.grid > .wrapper { display: contents; }

Óvatosan a contents-szel. Sokáig komoly hozzáférhetőségi hibája volt: az érintett elem szemantikája eltűnt a hozzáférhetőségi fából, tehát egy <ul> megszűnt listaként létezni a képernyőolvasók számára. A modern motorokban ez nagyrészt javult, de szemantikus elemeken — lista, táblázat, gomb — továbbra is érdemes óvatosnak lenni és képernyőolvasóval ellenőrizni. Sima <div> wrapperen viszont nyugodtan használható.

Táblázatok belső értékei

A table-row, table-cell, table-header-group és társaik szintén egy értékesek maradnak. Ezek ugyanis nem önálló dobozok a szokásos értelemben, hanem egy táblázat belső szerkezeti szerepei — csak egy táblázatos elrendezésen belül van értelmük.

Használjam élesben a két értékes szintaxist?

Őszinte válasz: egyelőre inkább ne — de nem azért, mert rossz.

A két írásmód viselkedése pontosan azonos, tehát semmit nem nyersz a hosszabbal. Ráadásul a támogatottsága ~93%, ami azt jelenti, hogy fallback sort is ki kellene írnod:

.badge {
    display: inline-block;      /* fallback */
    display: inline flow-root;  /* ugyanaz, csak hosszabban */
}

Két sor, nulla haszon. Ez az a pont, ahol ez a téma alapvetően különbözik például a dinamikus viewport egységektől: azok egy valódi hibát oldottak meg, ez viszont ugyanazt írja le másképp.

Ahol viszont mégis megéri használni:

  • tanításban és code review-ban — egy kommentben odaírt /* inline flow-root */ többet magyaráz, mint egy bekezdés,
  • gondolkodásban — amikor egy elrendezés nem úgy viselkedik, ahogy vártad, kérdezd meg magadtól: mi a külső típusa, és mi a belső?
  • dokumentációban — a design system leírásában pontosabb, mint a régi nevek.

Ahogy a támogatottság 98% fölé kúszik, ez valószínűleg meg fog fordulni: az inline flow-root önmagát magyarázza, az inline-block viszont örökre egy félrevezető név marad.

Böngészőtámogatás

Itt fontos két külön dolgot szétválasztani, mert nagyon eltér a helyzetük.

Mi Mióta Lefedettség
flow-root Chrome 58, Firefox 53, Safari 13, Edge 79 ~96% — használd
két értékes szintaxis Chrome 115, Edge 115, Firefox 70, Safari 15 ~93% — még várj vele

Vagyis a gondolat teljes egészében használható már ma — a flow-root, a BFC és a külső/belső modell mind bevethető. Csak a szintaxis az, ami még nem érte utol.

Gyors döntési segédlet

Ha elakadsz egy elrendezésnél, ez a lista általában kirántja belőle:

  • a gyerekek elrendezését akarod megváltoztatni? → belső típus: flex, grid, flow-root,
  • azt, hogy maga a doboz hogyan áll a testvérei között? → külső típus: block vagy inline,
  • lebegő elem lóg ki a konténerből? → display: flow-root,
  • margó szökik ki a szülőből? → display: flow-root a szülőn,
  • overflow: hidden-t írnál, de nem akarsz levágni semmit? → flow-root,
  • sorban maradna, de padding kell rá? → inline-block,
  • flexkonténer kell, ami a szövegben marad? → inline-flex,
  • felesleges wrapper akadályozza a gridet? → display: contents, hozzáférhetőségi ellenőrzéssel,
  • élesbe menő kódban egyelőre az egy értékes formákat írd.

Gyakori kérdések

Mi az a két értékes display szintaxis?

A display valójában mindig két dolgot állított be: a doboz külső típusát (hogyan viselkedik a testvérei között) és a belső típusát (hogyan rendezi el a gyerekeit). A két értékes szintaxis ezt teszi láthatóvá: a display: flex pontosan ugyanaz, mint a display: block flex, az inline-block pedig az inline flow-root rövidítése.

Mi a különbség a külső és a belső display típus között?

A külső típus (block vagy inline) azt írja le, hogyan viselkedik maga a doboz a szülője elrendezésében: külön sorba kerül-e, vagy a szövegfolyamban marad. A belső típus (flow, flow-root, flex, grid, table, ruby) azt, hogy a doboz milyen elrendezési szabályok szerint pakolja el a saját gyerekeit. A kettő független egymástól.

Mi az a flow-root és mire jó?

A display: flow-root egy blokkszintű dobozt hoz létre, amely új block formatting contextet nyit a tartalmának. Gyakorlatilag ez a modern clearfix: bekeríti a benne lévő lebegő elemeket, és megakadályozza, hogy a gyerekei margója kiszökjön belőle. Böngészőtámogatottsága kiváló (~96%), nyugodtan használható élesben.

Mi a különbség az inline-block és az inline flow-root között?

Semmi — ugyanaz a két írásmód. Az inline-block a régi, egy értékes neve annak, amit a két értékes szintaxis inline flow-rootként ír le: kívül sorszintű doboz, belül saját block formatting context. A hosszabb forma csak pontosabban megmondja, mi történik valójában.

Hogyan állíthatom meg a margóösszecsúszást?

A legtisztább módszer, ha a szülőn új block formatting contextet nyitsz: display: flow-root. Ezen kívül megakadályozza az összecsúszást a szülő és a gyerek közé tett padding vagy border, illetve az is, ha a szülő flex- vagy gridkonténer — a flex- és griditemek margói soha nem csúsznak össze.

Használhatom már élesben a két értékes display-t?

Technikailag igen, de jelenleg nincs sok értelme. A támogatottsága nagyjából 93% (Chrome és Edge 115, Firefox 70, Safari 15 óta), tehát fallback sort is ki kellene írni mellé — az viszont hosszabb lenne, mint maga az egy értékes forma, miközben a viselkedés pontosan ugyanaz. Érdemesebb gondolkodni benne, és egy értékes formákat írni.

Miért nincs két értékes formája a display: none-nak?

Mert a none nem elrendezési mód, hanem dobozgenerálási beállítás: azt mondja meg, hogy az elem egyáltalán ne hozzon létre dobozt. Ahol nincs doboz, ott nincs se külső, se belső elrendezési típus. Ugyanez igaz a display: contents értékre és a táblázat belső értékeire, például a table-cellre.

Biztonságos a display: contents használata?

Elrendezési szempontból hasznos, mert egy felesleges wrapper dobozát tünteti el úgy, hogy a gyerekei közvetlenül a nagyszülő grid- vagy flexelrendezésébe kerülnek. Sokáig viszont komoly hozzáférhetőségi hibája volt: az érintett elem szemantikája eltűnt a hozzáférhetőségi fából. A modern motorokban ez javult, de szemantikus elemeken — lista, táblázat, gomb — továbbra is óvatosan érdemes bánni vele, és képernyőolvasóval tesztelni.