פריסות CSS ועיצוב רספונסיבי

  1. ‫1 יסודות
    1. באיזה מצב פריסה כדאי להשתמש?
    2. עקרונות העבודה
  2. ‫2 Flexbox
  3. ‫3 רשת ורשת משנה
    1. קוד לדוגמה: רשת ורשת משנה
  4. ‫4 שאילתות מאגר
    1. דוגמת קוד: טיפוגרפיה גמישה באמצעות יחידות של שאילתות קונטיינר
  5. ‫5 שכבות-על מותאמות, מיקום עוגן והקשרים של סדר הערמה
  6. ‫6 מעקב אחרי חריגה ויציבות הפריסה
  7. ‫7 מכניקה של אזור התצוגה והפצת טראקים
  8. 8 שורות ועמודות (נקרא גם "בנייה")

‫1 Fundamentals

כדי לשפר את הביצועים, כדאי להסתמך על מנוע הפריסה של הדפדפן כשאפשר. השתמשו בגודל פנימי, במאפיינים לוגיים וב-aspect-ratio לפני שתשתמשו במאפיינים בתוך הקוד או בשאילתות מדיה מסובכות.

‫1.1 באיזה מצב פריסה להשתמש?

עוברים על ההנחיות מלמעלה למטה ועוצרים בהתאמה הראשונה. חשוב לזכור שאפשר להטמיע פריסות זו בתוך זו, וכל החלטה מבוססת על תרחיש השימוש של אותו מאגר.

  1. האם מדובר בשורה או בעמודה פשוטה של פריטים? משתמשים ב-flexbox – חד-ממדי, מתמקד בתוכן, התוכן מופץ לאורך ציר אחד.
  2. האם רכיב בתצוגת עץ צריך להתיישר עם המסלולים של רשת הסבא שלו? שימוש ב-subgrid – דו-ממדי, מתמקד בקשרים, יורש את המסלולים של ההורה כדי שהנכדים יוכלו להתיישר עם האחים.
  3. האם מדובר בדף או במבנה רכיבים מורכב עם שורות וגם עמודות? רשת – דו-ממדית, קודם כל פריסה, אתם מגדירים את השלד והתוכן ממלא אותו.
  4. האם התוכן הוא טקסט ארוך שצריך לפצל לעמודות מאוזנות? פריסה בכמה עמודות – זרימה חד-ממדית, בסגנון עיתון.
  5. האם יש פריטים בגבהים שונים שצריך לארוז בצפיפות? השתמשו ב-grid עם grid-auto-flow: dense היום; השתמשו ב-masonry מקורי (המכונה גם "grid lanes") רק כשהוא נשלח ביעד הבסיסי שלכם (ראו §8).
  6. האם רכיב צריך לצוף מעל הדף ולהישאר מקושר מרחבית לטריגר, גם מעבר לגבולות ה-DOM או להקשרים של סדר הערימה? משתמשים במיקום עוגן — anchor-name בטריגר, position-anchor בשכבת-העל (ראו סעיף 5).

‫1.2 עקרונות עבודה

  • השתמשו במאפיינים לוגיים (inline-size,‏ block-size,‏ margin-inline,‏ padding-block,‏ inset-inline-start) לממדי הפריסה ולמרווחים – מידע נוסף זמין במאמר CSS: Modern Architecture.
  • השתמשו במודל המנטלי של "תוכן קודם" לעומת "פריסה קודמת": flexbox כשהפריטים קובעים את הזרימה, ו-grid כשמגדירים קודם את השלד.
  • משתמשים בקיצורי הדרך של place-* (place-content,‏ place-items,‏ place-self) כדי ליישר את שני הצירים בהצהרה אחת.
  • השתמשו בגודל פנימי (min-content,‏ max-content,‏ fit-content()) ובמסלולים גמישים (fr,‏ minmax()) לפני width/height קבועים – פחות שאילתות מדיה, פריסות גמישות יותר.
  • אל תשתמשו ב-overflow: hidden כדי להסתיר בעיות בפריסה – תקנו אותן במקור. לדוגמה, רכיבים מסוימים שהוחלפו כמו <canvas>,‏ <img>,‏ <svg>,‏ <video>,‏ <iframe> משתמשים ב-display: inline כברירת מחדל, מה שיכול להוסיף רווח בפריסות מסוימות. במקום לחתוך את הרווח באמצעות overflow, צריך להסיר אותו על ידי החלת ערך block display-outside (למשל display: block,‏ grid,‏ flex וכו').
  • אפשר להשתמש ב-aspect-ratio כדי לשמור מקום למדיה ולמנוע שינויי פריסה לפני שהנכסים נטענים.
.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; }

למידע על calc-size() ועל גודל פנימי שמותאם לאילוצים, אפשר לעיין במאמר חישוב באמצעות גדלים פנימיים.

‫2 Flexbox

פריסה חד-ממדית – הפריטים זורמים לאורך ציר ראשי אחד עם יישור בציר הנגדי. הוא מתאים לסרגלי ניווט, לסרגלי כלים, לשורות של פריטים ולכל פריסה של שורה או עמודה אחת.

מומלץ:

  • מגדירים הקשר באמצעות display: flex ומגדירים את הציר הראשי באמצעות flex-direction (ברירת המחדל היא row).
  • השתמשו ב-flex-wrap: wrap בכל פעם שיש אפשרות לחריגה – nowrap בלי overflow: auto/hidden יחרוג באזורי תצוגה צרים.
  • שכבה flex-wrap: balance אחרי flex-wrap: wrap בשורות עטופות של פריטים קומפקטיים (כמו ענני תגים, כפתורי סינון, קבוצות של לחצנים או קישורי ניווט) כדי לפזר את הפריטים באופן שווה בין השורות ולמנוע פריט בודד בשורה האחרונה. דפדפנים ללא תמיכה חוזרים ל-wrap.
  • משתמשים בקיצור flex <grow> <shrink> <basis> (למשל, flex: 1 1 250px) בפריטים במקום להגדיר את flex-grow/flex-shrink/flex-basis בנפרד.
  • כדי ליצור רווח בין פריטים, השתמשו ב-gap (או ב-row-gap/column-gap) במקום בשוליים של רכיבי הצאצא.
  • כדאי להוסיף את הקידומת safe (למשל, align-items: safe center) ליישור המיקום כדי שתוכן שאפשר להתמקד בו לא ייחתך כשהמאגר צר יותר מהתוכן שלו.
  • דוחפים פריט אחד לקצה הרחוק של הציר הראשי באמצעות margin-inline-start: auto (או margin-block-start: auto) – זהו פתח המילוט הרגיל.
  • אפשר לשנות את היישור של הציר הנגדי לכל פריט באמצעות align-self.
  • משתמשים ב-align-items כדי למרכז את כל הפריטים בציר המשני; משתמשים ב-margin: auto בפריט בודד כדי למרכז אותו בשני הצירים באופן עצמאי; משתמשים ב-align-content רק כשהמאגר עוטף ויש לו מקום נוסף בין השורות.
  • מגדירים min-inline-size: 0 (או min-width: 0) בפריטי flexbox שמכילים תוכן ארוך שלא ניתן לפיצול (כתובות URL, קוד, מחרוזות ארוכות) – פריטי flexbox לא יתכווצו מתחת לגודל התוכן שלהם כברירת מחדל, מה שיגרום לחריגה.

מה לא לעשות:

  • אל תשתמשו ב-justify-self בפריטי flex – הוא פועל רק בפריסות של רשת, בלוק ומיקום מוחלט. במקום זאת, צריך להשתמש בשוליים אוטומטיים.
  • אל תשתמשו ב-order או ב-flex-direction: *-reverse כדי לשנות את הסדר של תוכן אינטראקטיבי. הם משנים רק את הסדר החזותי. סדר ה-DOM עדיין קובע את רצף המיקוד, כך שסדר המעבר בין הטאבים במקלדת לא יתאים למה שהמשתמש רואה.
  • אל תתבלבלו בין space-around (חצי מרווח בקצוות) לבין space-evenly (מרווחים שווים לפני, בין ואחרי).
  • אל תשכחו את היפוך הצירים: כש-flex-direction: column, justify-content מתיישר בציר הבלוק ו-align-items מתיישר בציר השורה – ההפך מברירת המחדל.
  • אל תגדירו את הגודל של הקונטיינר ושל הצאצאים שלו כך שהם ימלאו זה את זה – זו סיבה נפוצה לחריגה מהגבולות ולתוצאות מפתיעות. מגדירים גודל מסוים לאחד מהצדדים.
  • אל תגדירו גם את flex-basis וגם את width/inline-size באותו פריט – flex-basis מקבל עדיפות בהקשר של flex ו-width לא נכלל. השתמשו ב-flex-basis (או בקיצור flex) כמקור האמת היחיד לקביעת הגודל של פריטי flex.
.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 רשת ורשת משנה

פריסה דו-ממדית – מגדירים שורות ועמודות באופן מפורש, או מאפשרים למנוע להפיק אותן. רשת משנה מאפשרת לרשת בת לרשת את המסלולים של הרשת הראשית, כך שהנכדים מיושרים בין האחים.

בחירת תכונות של הרשת:

  • אתם יודעים בדיוק כמה עמודות אתם צריכים?
    • כן – צריך להשתמש בטראקים מפורשים (grid-template-columns: 200px 1fr, repeat(3, 1fr) וכו')
    • האם עמודות שונות צריכות להיות בגדלים שונים (סרגל צד + תוכן ראשי, כותרת שמשתרעת על פני כל העמודות)? ← משתמשים ב-grid-template-areas לאזורים קריאים עם שם
    • האם כל העמודות אחידות או שהן ממוקמות רק לפי מספר שורה? ← משתמשים ב-repeat(N, ...) או בקווים עם שם
    • לא (רספונסיבי, מספר פריטים לא ידוע) – משתמשים ב-repeat(auto-fit, minmax(min, 1fr))
    • האם הפריטים בשורה האחרונה צריכים להימתח כדי למלא את המקום שנותר? ← auto-fit
    • האם מסלולים ריקים בשורה האחרונה צריכים לשמור על הגודל המינימלי שלהם (כדי לשמור על משבצות ריקות בעמודה)? ← auto-fill
  • צריכים להציב פריט במיקום ספציפי?
    • כן – משתמשים ב-grid-column: <start> / <end> או ב-grid-area: <name>
    • לא (רק משתרע על פני כמה טראקים, מיקום הזרימה לא משנה) – משתמשים ב-grid-column: span <n>
  • האם רכיבי הצאצא צריכים לרשת את גודלי המסלולים של רשת ההורה (יישור קצוות משוננים בין אחים)?
    • כן – משתמשים ברשת משנה בציר הרלוונטי
    • האם מספר הילדים בכל תא משתנה? ← רשת משנה בציר אחד בלבד; משתמשים ב-grid-auto-rows/grid-auto-columns לציר השני
    • האם מספר הילדים קבוע? → רשת משנה בשני הצירים היא בסדר
    • לא – רשת רגילה, אין צורך ברשת משנה

מומלץ:

  • הגדרת הקשר באמצעות display: grid.
  • כדאי להשתמש ב-grid-template-areas לפריסות מורכבות ברמת הדף – שמות האזורים מתעדים את עצמם ואפשר ליישר את ההצהרה בשורות ובעמודות כדי שיהיה קל לקרוא אותה.
  • אפשר להשתמש ב-repeat(auto-fit, minmax(200px, 1fr)) לרשתות של כרטיסים רספונסיביים שמותחות את המסלולים המלאים כדי למלא את השורה, או ב-auto-fill כדי לשמור על המסלולים החוזרים הריקים בגודל המינימלי שלהם.
  • משתמשים ב-fr כדי להגדיר חלוקה יחסית של הטראקים, וב-minmax(min, max) כדי להגדיר טראקים גמישים אבל מוגבלים.
  • אפשר למקם פריטים באמצעות grid-column: span <n> כדי לשנות את הגודל שלהם לאורך טראקים, באמצעות grid-column: <start> / <end> כדי למקם אותם בשורות ספציפיות, או באמצעות grid-area: <name> כדי למקם אותם באזורים עם שם.
  • כדי לפתור את בעיית "הקצוות הלא אחידים" ברשימות של כרטיסים, אפשר להשתמש ברשת משנה (grid-template-columns: subgrid או grid-template-rows: subgrid) – רכיבים פנימיים כמו כותרות, מטא-נתונים וקריאות לפעולה מסתדרים בשורה אחת עם רכיבים מקבילים.
  • כדי שהאתר יפעל כמו שצריך בדפדפנים ישנים, כדאי להוסיף הצהרה על רשת משנה עם הצהרה מפורשת קודמת על grid-template-rows/-columns כחלופה לאותו סגנון מדורג.

מה לא לעשות:

  • אל תצפו שגודל הטראק auto-fit/auto-fill יגיע מתוכן הפריט – הוא מגיע מהארגומנט של הגודל repeat().
  • אל תשתמשו ב-grid-auto-flow: dense בתוכן אינטראקטיבי. הוא אורז פריטים ביעילות אבל משנה את הסדר שלהם באופן חזותי, ומשבש את זרימת הכרטיסיות במקלדת בסדר DOM.
  • אל תפעילו את subgrid בשני הצירים כשמספר הצאצאים משתנה. התוספות מגיעות למסלול האחרון. במקום זאת, צריך להשתמש ב-grid-auto-rows/grid-auto-columns לציר המשתמע.
  • חשוב להבדיל בין justify-items/align-items (מיישר את תוכן הפריט בתוך המסלול שלו) לבין justify-content/align-content (מיישר את מסלולי הרשת בתוך המאגר). אם משתמשים בערך הלא נכון, לא תהיה לכך השפעה.
  • אל תשתמשו ב-repeat(auto-fit/auto-fill, ...) בלי inline-size מוגדר בקונטיינר – בתוך display: inline-grid או פריט גמיש ללא גודל, לקונטיינר אין רוחב לחלוקה, ולכן מספר הטראקים לא צפוי.

‫3.1 קוד לדוגמה: רשת ורשת משנה

מעטפת הדף: <main class="page-layout"> מכיל את <header>, <aside>, <section class="card-grid"> עם <div class="card"> צאצאים ו-<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 שאילתות בנוגע למאגר

שליחת שאילתה לגבי הגודל (או הסגנון המחושב) של מאגר אב במקום של אזור התצוגה. מודל מנטלי: שאילתות קונטיינר = הקשר של רכיב; שאילתות מדיה = פריסת דף גלובלית והעדפות משתמש (prefers-color-scheme, prefers-reduced-motion).

מומלץ:

  • כדי שיהיה אפשר לשלוח שאילתות לצאצאים של רכיב wrapper, צריך להגדיר הקשר של הכלה באמצעות container-type: inline-size (שאילתות רוחב בלבד) או container-type: size (שני הצירים) ברכיב ה-wrapper.
  • צריך לתת שם למאגרים באמצעות container-name (או הקיצור של container: container: card / inline-size) כשיש סיכוי להתנגשות בין הקשרים בתצוגת עץ.
  • הכללת יחידות של שאילתות קונטיינר בחישוב של סוג וריווח נוזליים: cqi/cqb (בלוק/אינליין לוגי), cqw/cqh (פיזי), cqmin/cqmax.
  • צריך לתת לקונטיינר block-size מוגדר בכל פעם שמשתמשים ב-container-type: size. בלי זה, הצאצאים קורסים כי הכלה של גודל מאלצת את הקונטיינר להתעלם מהתוכן שלו.

מה לא לעשות:

  • אל תשתמשו ב-block-size בתור ערך של container-type – הוא לא תקין. משתמשים ב-size לשני הצירים.
  • אל תצפו שהגודל הפנימי של הצאצאים ישפיע על הקונטיינר אחרי שמצהירים על container-type. אחרי שההכלה מופעלת, המאגר מחושב כאילו אין לו צאצאים.
  • אל תסתמכו על יחידות של שאילתות מאגר בתוך צאצאים של אב קדמון שלא עומד בדרישות. הן חוזרות לאזור התצוגה הקטן (svw/svh).

‫4.1 דוגמת קוד: טיפוגרפיה גמישה באמצעות יחידות של שאילתות קונטיינר

.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);
}

למידע על תבניות עיצוב רספונסיביות שמבוססות על רכיבים, אפשר לעיין במאמרים עיצוב שמותאם לגודל והתאמה גמישה לעומס.

‫5 שכבות-על מקוריות, מיקום נקודות עיגון והקשרי סידור

מתי כדאי להשתמש בכל אחד מהפרימיטיבים של שכבות-העל:

  • משתמשים ב-popover לממשק משתמש זמני, לא מודאלי (תפריטים נשלפים, הודעות קצרות, הסברים על כלים) – נמצא בשכבה העליונה, אין צורך בניהול z-index.
  • משתמשים ב-<dialog> עם .showModal() לאינטראקציות מודאליות שדורשות לכידת מיקוד ורקע אינרטי.
  • אל תשלבו את popover ו-.showModal() באותו רכיב – אלה מצבי זמן ריצה שמוציאים זה את זה.

מיקום העוגן (פריסה מרחבית של שכבות-על):

  • כדי למקם שכבת-על ביחס לטריגר שלה ולשנות את הגודל שלה, משתמשים ב-position-area (או ב-anchor() בתוספות) וב-anchor-size().
  • אפשר להשתמש ב-position-try-fallbacks: flip-block (או ב-flip-inline) כדי לאפשר לדפדפן לשנות את המיקום כשהשכבה חורגת מאזור התצוגה.
  • אל תערבבו מילות מפתח פיזיות ולוגיות בערך position-area אחד – בחרו מערכת קואורדינטות אחת.
  • מזהים את התכונה באמצעות @supports (anchor-name: --x) ומספקים חלופה של מיקום מוחלט.

כדי לקבל פרטים מלאים על ההטמעה, אסטרטגיות של polyfill ומידע על הערך של popover, אפשר לעיין במאמרים Declarative Dialog and Popover Control ו-Position Aware Tooltips. כדי לראות דוגמאות למיקום של עוגנים בתפריטים נפתחים, בתפריטים צפים ובאינדיקטורים של כרטיסיות, אפשר לעיין במאמרים תפריטים נפתחים שמותאמים לאזור התצוגה וקו תחתון בכרטיסייה למיקום של עוגנים.

‫6 מעקב אחרי חריגה ויציבות הפריסה

לנהל שינויי פריסה, פסי גלילה וחיתוך באופן צפוי.

מומלץ:

  • משתמשים ב-overflow: auto כדי שפסי הגלילה יופיעו רק כשהתוכן באמת חורג מהגבולות.
  • אפשר להשתמש ב-overflow: clip כדי לחתוך תוכן בלי ליצור מאגר גלילה, ולהפעיל גלישה באמצעות overflow-clip-margin.
  • אפשר להשתמש ב-scrollbar-gutter: stable כדי לשמור מקום לפסי גלילה ולמנוע שינויי פריסה כשהתוכן גדל.
  • משתמשים ב-overscroll-behavior: contain (או ב-none) באזורי גלילה בתצוגת עץ (כמו סרגלי צד, מודאלים ורשימות) כדי למנוע משרשראות גלילה להופיע בדף, או ב-overscroll-behavior: chain במעטפות גלילה ביניים (כמו מסלולי החלקה לחשיפה) כשגלילה עודפת צריכה להתפשט לגלילה קודמת בלי להפעיל הקפצה מקומית של גומייה.
  • משתמשים בשילוב של -webkit-line-clamp + display: -webkit-box + -webkit-box-orient: vertical כדי לחתוך טקסט בכמה שורות. למרות הקידומת, התבנית הזו מוגדרת במלואה ולא הוצאה משימוש. צריך להצהיר על קיצור הדרך line-clamp ללא קידומת לצד הקיצור עם הקידומת. דפדפנים שעדיין לא תומכים בו יתעלמו מהמאפיין בלי לגרום נזק. מה לא לעשות:

  • אל תשתמשו ב-overflow: scroll אם אפשר להשתמש ב-auto – scroll מאלץ פסי גלילה גם כשאין מה לגלול.

  • אל תשתמשו ב-overflow: hidden אם אתם רק רוצים לחתוך – hidden יוצר מאגר גלילה שאפשר לגלול בו באופן פרוגרמטי.

.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;
}

למידע מעמיק על overflow: clip ו-overflow-clip-margin, אפשר לעיין במאמר Overflow Clipping Control. כדי להעביר גלילה בתצוגה בתצוגת עץ עם overscroll-behavior: chain בממשק משתמש של החלקה לחשיפה, ראו החלקה לחשיפה. כדי לשנות את הצבע, הגודל והעיצוב של סרגל הגלילה, אפשר לעיין במאמרים התאמה אישית של הצבע או העובי של סרגל הגלילה, מצב כהה והתאמת סרגל הגלילה להעדפות של ניגודיות גבוהה.

‫7 מכניקה של אזור התצוגה והתפלגות המסלולים

  • צריך להשתמש ב-dvh/dvw למאגרי פריסה לנייד שצריכים להביא בחשבון את השינוי בממשק המשתמש של הדפדפן (כיווץ/הרחבה של סרגל כתובות ה-URL).
  • אל תשתמשו ב-100vw לפריסה ברוחב מלא – הוא מתעלם מרוחב סרגל הגלילה וגורם לגלישה אופקית. במקומה צריך להשתמש ב-100%, ב-100dvw או ב-100svw.

לנקודת הכניסה של העיצוב הרספונסיבי (יחידות דינמיות של אזור התצוגה, שמירת מקום למדיה, טיפוגרפיה גמישה), ראו עיצוב רספונסיבי.

‫8 שורות ועמודות ברשת (פריסה בלבנים)

המפרט נמצא בפיתוח תחת השם "grid lanes" (למשל, display: grid-lanes), ואילו אבות טיפוס ניסיוניים קודמים השתמשו ב-grid-template-rows: masonry.

מומלץ:

  • כדי להציג את הפריטים בצפיפות, אפשר להשתמש ב-grid עם grid-auto-flow: dense, אבל צריך לקחת בחשבון שסדר ה-DOM לא בהכרח יהיה זהה לסדר הוויזואלי.
  • כדאי להשתמש בפריסה מרובת עמודות (columns: 3; column-gap: 1rem) לזרימה דמוית פסיפס עם הרבה תוכן, כשהפריטים הם קטעי מסמכים ולא כרטיסים בעלי משקל שווה.
  • צריך להתייחס ל-grid-template-rows: masonry כשיפור הדרגתי בלבד – זיהוי תכונות באמצעות @supports.

מה לא לעשות:

  • אל תשיקו את grid-template-rows: masonry כדרישה מחייבת עד שהיעד של Baseline יתעדכן.
.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;
  }
}