CSS-indelingen en responsief ontwerp

  1. 1 Basisprincipes
    1. Welke vormgevingsmodus moet ik gebruiken?
    2. Werkprincipes
  2. 2 Flexbox
  3. 3 Raster en subraster
    1. Codevoorbeeld: grid en subgrid
  4. 4 Containerquery's
    1. Codevoorbeeld: vloeiende typografie met containerquery-eenheden
  5. 5 Native overlays, ankerpositionering en stapelcontexten
  6. 6 Overloop bijhouden en stabiliteit van de indeling
  7. 7 Mechanica van de viewport en distributie van tracks
  8. 8 rasterbanen (ook wel metselwerk genoemd)

1 Fundamentals

Gebruik waar mogelijk de lay-outengine van de browser voor betere prestaties. Gebruik intrinsieke grootte, logische eigenschappen en aspect-ratio voordat je hardgecodeerde dimensies of ingewikkelde mediaquery's gebruikt.

1.1 Welke lay-outmodus moet ik gebruiken?

Doorloop het beslissingsschema van boven naar beneden en stop bij de eerste overeenkomst. Indelingen kunnen in elkaar worden genest en elke beslissing is gebaseerd op de use case voor die container.

  1. Is het een eenvoudige rij OF kolom met items? Gebruik flexbox: 1D, content-first, content wordt verdeeld over één as.
  2. Moet een genest element worden uitgelijnd met de tracks van het bovenliggende raster? Gebruik subgrid: 2D, relatiegericht, neemt bovenliggende tracks over zodat kleinkinderen kunnen worden uitgelijnd met broers en zussen.
  3. Is het een complexe pagina- of componentstructuur met rijen EN kolommen? Gebruik grid: 2D, layout-first, je definieert het skelet en de content vult het.
  4. Is de content een lange tekst die moet worden opgesplitst in evenwichtige kolommen? Gebruik meerdere kolommen: 1D-stroom, krantenstijl.
  5. Zijn er items van verschillende hoogtes die strak moeten worden ingepakt? Gebruik vandaag nog grid met grid-auto-flow: dense. Gebruik native masonry (ook wel 'grid lanes' genoemd) alleen als dit wordt geleverd in je Baseline-doel (zie §8).
  6. Moet een element boven de pagina zweven en ruimtelijk gekoppeld blijven aan een trigger, zelfs over DOM-grenzen of stapelcontexten heen? Gebruik ankerpositionering: anchor-name op de trigger, position-anchor op de overlay (zie §5).

1.2 Werkprincipes

  • Gebruik logische eigenschappen (inline-size, block-size, margin-inline, padding-block, inset-inline-start) voor de afmetingen en afstand van de indeling. Ga naar CSS: Modern Architecture voor een volledige beschrijving.
  • Pas het mentale model content-first versus layout-first toe: flexbox als items de flow bepalen, grid als je eerst het skelet definieert.
  • Gebruik de place-*-afkortingen (place-content, place-items, place-self) om beide assen in één declaratie uit te lijnen.
  • Gebruik intrinsieke grootte (min-content, max-content, fit-content()) en flexibele tracks (fr, minmax()) in plaats van vaste width/height: minder mediaquery's, veerkrachtigere indelingen.
  • Gebruik overflow: hidden NIET om indelingproblemen te maskeren. Los ze op bij de bron. Bepaalde vervangen elementen zoals <canvas>, <img>, <svg>, <video>, <iframe> gebruiken bijvoorbeeld standaard display: inline, wat in bepaalde lay-outs een gat kan toevoegen. In plaats van de ruimte te verkleinen met overflow, verwijder je deze door een block display-outside-waarde toe te passen (bijvoorbeeld display: block, grid, flex, enzovoort).
  • Gebruik aspect-ratio om ruimte te reserveren voor media en indelingsverschuivingen te voorkomen voordat componenten worden geladen.
.sidebar       { inline-size: max-content; }    /* Size to longest unbreakable token. */
.main-content  { inline-size: fit-content; }    /* Grow to available space, no further. */
.media         { aspect-ratio: 16 / 9; inline-size: 100%; block-size: auto; }
body.centered  { display: grid; place-content: center; min-block-size: 100dvb; }

Ga naar Berekenen met intrinsieke grootten voor calc-size() en intrinsieke groottebepaling op basis van beperkingen.

2 Flexbox

Eendimensionale indeling: items lopen langs één hoofdas met uitlijning op de dwarsas. Gebruik dit voor navigatiebalken, werkbalken, itemrijen en elke verdeling met één rij of kolom.

Doen:

  • Stel een context in met display: flex en stel de hoofdas in met flex-direction (row is de standaardinstelling).
  • Gebruik flex-wrap: wrap als er een mogelijkheid is dat de tekst niet past. nowrap zonder overflow: auto/hidden loopt over op smalle viewports.
  • Laag flex-wrap: balance na flex-wrap: wrap op teruggelopen rijen van compacte items (zoals tag clouds, filterpillen, knoppengroepen of navigatielinks) om items gelijkmatig over regels te verdelen en te voorkomen dat er één item op de laatste rij staat. Browsers zonder ondersteuning vallen terug op wrap.
  • Gebruik de verkorte notatie flex <grow> <shrink> <basis> (bijv. flex: 1 1 250px) voor items in plaats van flex-grow/flex-shrink/flex-basis afzonderlijk in te stellen.
  • Gebruik gap (of de volledige row-gap/column-gap) voor de afstand tussen items in plaats van onderliggende marges.
  • Voeg het voorvoegsel safe toe aan de positionele uitlijning (bijv. align-items: safe center) zodat content waarop kan worden gefocust niet wordt afgekapt als de container smaller is dan de content.
  • Duw één item naar het uiteinde van de hoofdas met margin-inline-start: auto (of margin-block-start: auto). Dat is de standaard ontsnappingsroute.
  • Overschrijf de uitlijning op de dwarsas per item met align-self.
  • Gebruik align-items om alle items op de kruisas te centreren. Gebruik margin: auto voor één item om het onafhankelijk op beide assen te centreren. Gebruik align-content alleen als de container wordt teruggebroken en extra ruimte tussen de rijen heeft.
  • Stel min-inline-size: 0 (of min-width: 0) in voor flex-items die lange, niet-afbreekbare content bevatten (URL's, code, lange tekenreeksen). Flex-items worden standaard niet kleiner dan hun contentgrootte, waardoor overloop ontstaat.

Wat u niet moet doen:

  • Gebruik justify-self niet voor flex-items. Dit werkt alleen voor raster-, blok- en absoluut gepositioneerde lay-outs. Gebruik in plaats daarvan automatische marges.
  • Gebruik order of flex-direction: *-reverse niet om interactieve content opnieuw te ordenen. Ze wijzigen alleen de visuele volgorde. De DOM-volgorde bepaalt nog steeds de opeenvolgende focus, dus de tabstroom van het toetsenbord komt niet overeen met wat de gebruiker ziet.
  • Verwar space-around (halve tussenruimte aan de uiteinden) niet met space-evenly (gelijke tussenruimtes voor, tussen en na).
  • Vergeet niet dat de as wordt omgedraaid: bij flex-direction: column wordt justify-content uitgelijnd op de blokas en align-items op de inline-as, het tegenovergestelde van de standaardinstelling.
  • Maak de container en de onderliggende elementen niet zo groot dat ze elkaar vullen. Dit is een veelvoorkomende oorzaak van overloop en onverwachte resultaten. Geef één zijde een definitieve grootte.
  • Stel niet zowel flex-basis als width/inline-size in voor hetzelfde item. flex-basis heeft voorrang in een flex-context en width wordt genegeerd. Gebruik flex-basis (of de afkorting flex) als de enige bron van waarheid voor de grootte van flex-items.
.card-grid        { display: flex; flex-flow: row wrap; gap: 1rem; }
.card-item        { flex: 1 1 250px; }                  /* grow, shrink, basis */
.card-item-action { margin-inline-start: auto; }        /* Push to main-axis end. */
.toolbar          { display: flex; align-items: safe center; }
.tag-list         { display: flex; flex-wrap: wrap; flex-wrap: balance; gap: 0.5rem; }

3 Raster en subraster

2-dimensionale indeling: definieer rijen EN kolommen expliciet of laat de engine ze afleiden. Met subgrid neemt een genest raster de tracks van de bovenliggende elementen over, zodat kleinkinderen worden uitgelijnd met broers en zussen.

Rasterfuncties kiezen:

  • Weet je precies hoeveel kolommen je nodig hebt?
    • Ja: gebruik expliciete tracks (grid-template-columns: 200px 1fr, repeat(3, 1fr), enzovoort)
    • Hebben verschillende kolommen verschillende formaten nodig (zijbalk + hoofdgedeelte, koptekst die alles omvat)? → gebruik grid-template-areas voor benoemde, leesbare regio's
    • Zijn alle kolommen uniform of puur gepositioneerd op regelnummer? → gebruik repeat(N, ...) of benoemde lijnen
    • Nee (responsief, onbekend aantal items): gebruik repeat(auto-fit, minmax(min, 1fr))
    • Moeten items op de laatste rij worden uitgerekt om de resterende ruimte te vullen? → auto-fit
    • Moeten lege tracks op de laatste rij hun minimale grootte behouden (zodat spookslots in kolommen behouden blijven)? → auto-fill
  • Moet je een item op een specifieke locatie plaatsen?
    • Ja: gebruik grid-column: <start> / <end> of grid-area: <name>
    • Nee (deze beslaat meerdere tracks, de positie van de flow maakt niet uit): gebruik grid-column: span <n>
  • Moeten onderliggende elementen de trackgrootten van het bovenliggende raster overnemen (uitlijning met onregelmatige randen tussen broers en zussen)?
    • Ja: gebruik een subraster op de betreffende as
    • Is het aantal kinderen per cel variabel? → subgrid slechts één as; gebruik grid-auto-rows/grid-auto-columns voor de andere
    • Is het aantal kinderen vastgesteld? → subgrid op beide assen is prima
    • Nee: standaardraster, geen subraster nodig

Doen:

  • Stel een context vast met display: grid.
  • Gebruik grid-template-areas voor complexe lay-outs op paginaniveau. Gebiedsnamen zijn zelfdocumenterend en de declaratie kan worden uitgelijnd in rijen en kolommen voor een snelle leesbaarheid.
  • Gebruik repeat(auto-fit, minmax(200px, 1fr)) voor responsieve kaartrasters die gevulde tracks uitrekken om de rij te vullen, of auto-fill om lege herhaalde tracks op hun minimale grootte te houden.
  • Gebruik fr voor proportionele trackdistributie en minmax(min, max) voor flexibele maar begrensde tracks.
  • Plaats items met grid-column: span <n> om de grootte over tracks te bepalen, grid-column: <start> / <end> om ze op specifieke regels te plaatsen of grid-area: <name> voor benoemde regio's.
  • Gebruik subgrid (grid-template-columns: subgrid of grid-template-rows: subgrid) om het probleem van de 'rafelige rand' in kaartlijsten op te lossen. Interne elementen zoals titels, metadata en CTA's worden dan uitgelijnd tussen broers en zussen.
  • Koppel een subgrid-declaratie aan een voorgaande expliciete grid-template-rows/-columns-declaratie als een fallback met dezelfde cascade voor oudere browsers.

Wat u niet moet doen:

  • Verwacht niet dat de trackgrootte van auto-fit/auto-fill afkomstig is van de itemcontent. Deze is afkomstig van het argument repeat() size.
  • Gebruik grid-auto-flow: dense niet voor interactieve content. De items worden efficiënt ingepakt, maar visueel opnieuw geordend, waardoor de tabvolgorde van het toetsenbord in de DOM-volgorde wordt verbroken.
  • Pas geen subraster toe op beide assen als het aantal onderliggende elementen variabel is. Extra's komen in de laatste track terecht. Gebruik in plaats daarvan grid-auto-rows/grid-auto-columns voor de impliciete as.
  • Verwar justify-items/align-items (lijnt itemcontent uit binnen de track) niet met justify-content/align-content (lijnt de rastertracks uit binnen de container). Als je de verkeerde gebruikt, heeft dit geen effect.
  • Gebruik repeat(auto-fit/auto-fill, ...) niet zonder een bepaalde inline-size voor de container. In display: inline-grid of een flex-item zonder grootte heeft de container geen breedte om te verdelen, waardoor het aantal tracks onvoorspelbaar wordt.

3.1 Codevoorbeeld: grid en subgrid

Pagina-shell: <main class="page-layout"> bevat <header>, <aside>, een <section class="card-grid"> met <div class="card"> onderliggende elementen en <footer>.

/* Align grid-template-areas in rows and columns for readability. */
.page-layout {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-areas:
    "header  header  header"
    "sidebar main    main"
    "footer  footer  footer";
  gap: 1.5rem;
}

header  { grid-area: header; }
aside   { grid-area: sidebar; }
footer  { grid-area: footer; }

.card-grid {
  grid-area: main;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  grid-template-rows: auto 1fr; /* title block, body block */
  gap: 1rem;
}

.card {
  grid-row: span 2;
  display: grid;
  /* Same-cascade fallback: ignored when subgrid is supported. */
  grid-template-rows: auto 1fr;
  grid-template-rows: subgrid;
}

4 Containerquery's

Voer een query uit voor de grootte (of berekende stijl) van een bovenliggende container in plaats van het kijkvenster. Mentaal model: containerquery's = componentcontext; mediaquery's = algemene pagina-indeling en gebruikersvoorkeuren (prefers-color-scheme, prefers-reduced-motion).

Doen:

  • Stel een containmentcontext in met container-type: inline-size (alleen breedtequery's) of container-type: size (beide assen) op een wrapper voordat de onderliggende elementen kunnen worden opgevraagd.
  • Geef containers een naam met container-name (of de afkorting container: container: card / inline-size) als geneste contexten kunnen botsen.
  • Neem containerquery-eenheden op bij de berekening van dynamische typen en tussenruimte: cqi/cqb (logische inline/blok), cqw/cqh (fysiek), cqmin/cqmax.
  • Geef de container een definitieve block-size wanneer container-type: size wordt gebruikt. Zonder deze worden afstammelingen samengevouwen omdat de container door de formaatbeperking de content negeert.

Wat u niet moet doen:

  • Gebruik block-size niet als container-type-waarde. Deze is niet geldig. Gebruik size voor beide assen.
  • Verwacht niet dat de intrinsieke grootte van onderliggende elementen de container beïnvloedt nadat je container-type hebt gedeclareerd. De container wordt berekend alsof deze geen onderliggende elementen heeft zodra de containment actief is.
  • Vertrouw niet op containerquery-eenheden in afstammelingen van een niet-gekwalificeerde voorouder. Ze vallen terug naar de kleine viewport (svw/svh).

4.1 Codevoorbeeld: vloeiende typografie met containerquery-eenheden

.card-wrapper {
  container: card / inline-size; /* shorthand for container-name / container-type */
}

@container card (inline-size > 400px) {
  .content {
    display: flex;
    gap: 2rem;
  }
}

.title {
  /* Fluid type bound to the container width, not the viewport. */
  font-size: clamp(1rem, 4cqi, 2rem);
}

Ga naar Size Aware Styling en Fluid Scaling voor componentgestuurde responsieve stijlpatronen.

5 Native overlays, ankerpositionering en stapelcontexten

Wanneer u elke overlay-primitief gebruikt:

  • Gebruik popover voor tijdelijke, niet-modale UI (fly-outs, toasts, tooltips). Deze bevindt zich in de bovenste laag, dus je hebt geen z-index-beheer nodig.
  • Gebruik <dialog> met .showModal() voor modale interacties die focusvastlegging en een inerte achtergrond vereisen.
  • Combineer popover en .showModal() niet in hetzelfde element. Dit zijn elkaar uitsluitende runtimestatussen.

Ankerpositionering (ruimtelijke indeling van overlays):

  • Gebruik position-area (of anchor() voor insets) en anchor-size() om een overlay te positioneren en de grootte ervan te bepalen ten opzichte van de trigger.
  • Gebruik position-try-fallbacks: flip-block (of flip-inline) om de browser de positie te laten aanpassen als de overlay de viewport overschrijdt.
  • Combineer geen fysieke en logische zoekwoorden in één position-area-waarde. Kies één coördinatensysteem.
  • Voer een functiedetectie uit met @supports (anchor-name: --x) en zorg voor een absolute-position-fallback.

Ga naar Declarative Dialog and Popover Control en Position Aware Tooltips voor volledige implementatiegegevens, polyfill-strategieën en de referentie voor de waarde popover. Ga naar Viewport-Aware Dropdowns en Anchor Positioning Tab Underline voor ankerpositionering die wordt toegepast op dropdownmenu's, flyouts en tabbladindicatoren.

6 Overlooptracking en lay-outstabiliteit

Beheer lay-outverschuivingen, schuifbalken en knippen op een voorspelbare manier.

Doen:

  • Gebruik overflow: auto zodat schuifbalken alleen verschijnen als de content daadwerkelijk overloopt.
  • Gebruik overflow: clip om content te knippen zonder een scrollcontainer te maken. Kies voor overloop met overflow-clip-margin.
  • Gebruik scrollbar-gutter: stable om ruimte te reserveren voor scrollbalken en indelingsverschuivingen te voorkomen als de content groeit.
  • Gebruik overscroll-behavior: contain (of none) voor geneste scrollgebieden (zoals zijbalken, modale vensters en lijsten) om te voorkomen dat scrollketens naar de pagina worden overgedragen, of overscroll-behavior: chain voor tussenliggende scrollwrappers (zoals swipe-to-reveal-tracks) als overmatig scrollen moet worden doorgegeven aan een bovenliggende scroller zonder een lokale rubberband-bounce te activeren.
  • Gebruik de triade -webkit-line-clamp + display: -webkit-box + -webkit-box-orient: vertical voor afkapping van meerdere regels. Ondanks het voorvoegsel is dit patroon volledig gespecificeerd en niet beëindigd. Declareer de afkorting line-clamp zonder voorvoegsel ernaast. Browsers die deze nog niet ondersteunen, negeren de property zonder problemen. Wat u niet moet doen:

  • Gebruik geen overflow: scroll als auto voldoende is. scroll dwingt scrollbalken af, zelfs als er niets is om doorheen te scrollen.

  • Gebruik overflow: hidden niet als je alleen wilt knippen. hidden maakt een scrollcontainer die programmatisch kan worden gescrold.

.scrollable-list {
  max-block-size: 400px;
  overflow-y: auto;
  scrollbar-gutter: stable;       /* Reserve scrollbar space. */
  overscroll-behavior: contain;   /* No scroll chaining into the page. */
}

.snippet {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  line-clamp: 3;                  /* Ignored where unsupported. */
  overflow: clip;
}

Ga naar Overflow Clipping Control voor meer informatie over overflow: clip en overflow-clip-margin. Ga naar Swipen om te tonen voor geneste scroll-overdracht met overscroll-behavior: chain in de UI voor swipen om te tonen. Ga naar De kleur of dikte van een scrollbalk aanpassen, Donkere modus en Scrollbalk aanpassen aan voorkeuren voor hoog contrast voor de kleur, grootte en thema's van de scrollbalk.

7 Mechanica van de viewport en distributie van tracks

  • Gebruik dvh/dvw voor containers met een mobiele indeling die rekening moeten houden met verschuivingen in de browser-UI (samenvouwen/uitvouwen van de URL-balk).
  • Gebruik 100vw niet voor een indeling op volledige breedte. De breedte van de scrollbalk wordt dan genegeerd en er ontstaat een horizontale overloop. Gebruik in plaats daarvan 100%, 100dvw of 100svw.

Ga naar Responsief ontwerp voor het toegangspunt voor responsief ontwerp (dynamische viewport-eenheden, ruimte reserveren voor media, vloeiende typografie).

8 rasterbanen (ook wel metselwerk genoemd)

De specificatie is in ontwikkeling onder de naam 'grid lanes' (bijv. display: grid-lanes), terwijl eerdere experimentele prototypes grid-template-rows: masonry gebruikten.

Doen:

  • Gebruik vandaag nog een raster met grid-auto-flow: dense voor een compacte indeling en accepteer dat de DOM-volgorde misschien niet overeenkomt met de visuele volgorde.
  • Gebruik meerdere kolommen (columns: 3; column-gap: 1rem) voor een metselwerkachtige flow met veel content als items documentfragmenten zijn in plaats van kaarten met een gelijk gewicht.
  • Behandel grid-template-rows: masonry alleen als een progressive enhancement: voer een functieherkenning uit met @supports.

Wat u niet moet doen:

  • Verzend grid-template-rows: masonry niet als harde vereiste totdat je Baseline-doel is bijgewerkt.
.gallery       { columns: 3 200px; column-gap: 1rem; }
.gallery > *   { break-inside: avoid; margin-block-end: 1rem; }

@supports (grid-template-rows: masonry) {
  .gallery {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    grid-template-rows: masonry;
    gap: 1rem;
    columns: unset;
  }
}