|
| 1 | +# Section Columns Layout |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +`section-layout` is a page-wide metadata key that lets authors selectively group |
| 6 | +top-level sections into shared rows, instead of the usual all-sections-stack |
| 7 | +vertically behavior. A section is full-width by default; adding a `column-span-*` |
| 8 | +value via that section's own **Section Metadata** block opts it into sharing a row |
| 9 | +with its *contiguous* neighbors that carry the same kind of tag, proportioned by |
| 10 | +the span ratio. |
| 11 | + |
| 12 | +This is a page-level layout concern, not a block — no new block, no new |
| 13 | +`EVENT_BLOCKS` entry. It's implemented entirely as an opt-in class toggle |
| 14 | +(`applySectionColumnsLayout()` in `event-libs/v1/utils/decorate.js`) plus CSS |
| 15 | +(`event-libs/v1/libs-styles.css`). |
| 16 | + |
| 17 | +## Authoring |
| 18 | + |
| 19 | +**1. Turn on the layout** — add a **Metadata** block at the bottom of the page: |
| 20 | + |
| 21 | +| Metadata | | |
| 22 | +|---|---| |
| 23 | +| Section Layout | columns | |
| 24 | + |
| 25 | +This applies to the whole page's `<main>`, not any one section. |
| 26 | + |
| 27 | +**2. Split the page into sections** — insert a section break (`---`) between each |
| 28 | +block of content you want as a potential column, same as any other EDS page. |
| 29 | + |
| 30 | +**3. Group sections into a row** — add a **Section Metadata** block inside each |
| 31 | +section you want side-by-side, with a `style` row set to `column-span-1`, |
| 32 | +`column-span-2`, or `column-span-3`: |
| 33 | + |
| 34 | +| Section Metadata | | |
| 35 | +|---|---| |
| 36 | +| style | column-span-2 | |
| 37 | + |
| 38 | +- `column-span-1` — equal share of the row (default weight for a tagged section) |
| 39 | +- `column-span-2` — twice the width of a `column-span-1` neighbor |
| 40 | +- `column-span-3` — three times the width of a `column-span-1` neighbor |
| 41 | + |
| 42 | +`column-span-*` is a **weight**, not a slot number — a section's actual share of |
| 43 | +the row is *its own weight ÷ the sum of every weight in that row*. So: |
| 44 | + |
| 45 | +- `column-span-1` + `column-span-1` → 50% / 50% |
| 46 | +- `column-span-1` + `column-span-2` → 33% / 67% |
| 47 | +- `column-span-3` + `column-span-2` → 60% / 40% |
| 48 | +- `column-span-1` + `column-span-1` + `column-span-1` → even thirds |
| 49 | + |
| 50 | +You can combine this with any other `style` values you already use (background, |
| 51 | +spacing, theme, etc.) by comma-separating them in the same row, exactly as today. |
| 52 | + |
| 53 | +**4. Sections without a `column-span-*` tag stay full-width**, stacked normally. |
| 54 | +This is the default — you only tag the sections you actually want grouped. |
| 55 | + |
| 56 | +**5. Any number of contiguous tagged sections can share one row** — two, three, |
| 57 | +or more. There's no separate "how many columns" setting; a row is just however |
| 58 | +many contiguous `column-span-*` sections you author next to each other. |
| 59 | + |
| 60 | +**6. Grouping only works between *contiguous* sections.** An untagged section |
| 61 | +between two tagged ones breaks them into two separate single-item rows rather |
| 62 | +than merging them — there's nothing to configure here, it falls directly out of |
| 63 | +how the CSS works (see Technical Notes), and it's visible immediately in preview. |
| 64 | + |
| 65 | +**7. Two separate, back-to-back row-groups need a divider between them.** A page |
| 66 | +can have as many independent rows as you want, but the *only* thing that ends a |
| 67 | +row is an untagged (full-width) section. If two different groups sit directly |
| 68 | +next to each other with nothing untagged between them — e.g. you want sections |
| 69 | +2+3 in one row and sections 4+5 in a separate row, immediately after — they'll |
| 70 | +merge into a single four-column row instead, since there's nothing to force a |
| 71 | +break between section 3 and section 4. Insert a plain, untagged section between |
| 72 | +the two groups to force the break (it can be empty/purely structural if you |
| 73 | +don't want it to show as its own visible row). |
| 74 | + |
| 75 | +**8. Blocks inside each section** stack top-to-bottom within their column exactly |
| 76 | +as they do in any normal section — nothing changes there. |
| 77 | + |
| 78 | +**9. Below 900px viewport width**, every section collapses back to plain vertical |
| 79 | +stacking regardless of tagging — there's no authoring for this, it's automatic. |
| 80 | + |
| 81 | +### Example |
| 82 | + |
| 83 | +```html |
| 84 | +<!-- Metadata block at the end of the page --> |
| 85 | +<div> |
| 86 | + <div>Section Layout</div> |
| 87 | + <div>columns</div> |
| 88 | +</div> |
| 89 | +``` |
| 90 | + |
| 91 | +A page with four sections, where the middle two sit side-by-side (1:2 ratio) and |
| 92 | +the outer two stay full-width: |
| 93 | + |
| 94 | +``` |
| 95 | +Section 1 (no column-span tag — full width) |
| 96 | +--- |
| 97 | +Section 2 (Section Metadata: style = column-span-1) |
| 98 | +--- |
| 99 | +Section 3 (Section Metadata: style = column-span-2) |
| 100 | +--- |
| 101 | +Section 4 (no column-span tag — full width) |
| 102 | +``` |
| 103 | + |
| 104 | +Renders (above 900px) as: Section 1 full-width, Sections 2+3 side-by-side split |
| 105 | +1:2, Section 4 full-width — each on its own row. |
| 106 | + |
| 107 | +### Example: multiple independent rows on one page |
| 108 | + |
| 109 | +Two separate 2-up rows, a solo full-width section, and a 3-up row — s1+s2 and |
| 110 | +s3+s4 are *different* groups, so a divider (s2.5) is needed between them: |
| 111 | + |
| 112 | +``` |
| 113 | +Section 1 (Section Metadata: style = column-span-1) |
| 114 | +--- |
| 115 | +Section 2 (Section Metadata: style = column-span-1) |
| 116 | +--- |
| 117 | +Section 2.5 — untagged divider, needed only because s1+s2 and s3+s4 |
| 118 | + are separate groups sitting back-to-back with nothing |
| 119 | + else to force the break between them |
| 120 | +--- |
| 121 | +Section 3 (Section Metadata: style = column-span-1) |
| 122 | +--- |
| 123 | +Section 4 (Section Metadata: style = column-span-1) |
| 124 | +--- |
| 125 | +Section 5 (no column-span tag — full width, "alone in its own row") |
| 126 | +--- |
| 127 | +Section 6 (Section Metadata: style = column-span-1) |
| 128 | +--- |
| 129 | +Section 7 (Section Metadata: style = column-span-1) |
| 130 | +--- |
| 131 | +Section 8 (Section Metadata: style = column-span-1) |
| 132 | +``` |
| 133 | + |
| 134 | +Renders (above 900px) as five rows: [1, 2] split 50/50 → [2.5, full-width] → |
| 135 | +[3, 4] split 50/50 → [5, full-width] → [6, 7, 8] split into even thirds. Section |
| 136 | +5 needed no divider on either side — an untagged section always forces a break |
| 137 | +before *and* after itself, which is exactly what makes it render alone. |
| 138 | + |
| 139 | +## Technical Notes |
| 140 | + |
| 141 | +- The metadata key is `section-layout`, value `columns` (exact string match). |
| 142 | + Read via the existing `getMetadata()` utility — no new metadata-reading code. |
| 143 | +- `applySectionColumnsLayout()` (`event-libs/v1/utils/decorate.js`) is **not** |
| 144 | + called from `decorateEvent()` — `decorateEvent` only runs on pages with an |
| 145 | + `event-id`, but this layout is meant for static/non-event pages too. A |
| 146 | + consuming site's own `decorateArea` must call it directly and unconditionally |
| 147 | + (see `da-events/events/scripts/scripts.js`'s `decorateArea()` for the reference |
| 148 | + integration). It always resolves the real page `<main>` directly and re-reads |
| 149 | + metadata on every call — safe to call repeatedly, since `decorateArea` can |
| 150 | + re-enter multiple times per page load (once per fragment/personalization pass). |
| 151 | +- **No DOM reparenting.** Sections are never moved — every `.section` stays |
| 152 | + exactly where Milo's `loadArea()` puts it, as a direct child of `<main>`. This |
| 153 | + was a deliberate choice: an earlier design considered wrapping selected |
| 154 | + sections in a new element to act as their own flex container, but that breaks |
| 155 | + several load-bearing Milo behaviors that depend on `.section` being `<main>`'s |
| 156 | + direct child — `position: relative` (needed by section-metadata's |
| 157 | + background-image feature), `main > .section > .content` max-width rules used |
| 158 | + by several blocks, `sticky-section.js`'s forced `main.prepend`/`append`, and |
| 159 | + personalization's post-LCP `main > div` containment check. None of that is a |
| 160 | + concern here since the DOM structure never changes. |
| 161 | +- **Grouping mechanism is pure CSS**, via `flex-wrap`, not CSS Grid. Every |
| 162 | + `.section` defaults to `flex: 1 1 100%` (forces it alone onto its own line — |
| 163 | + visually identical to normal stacking). A `column-span-*` class overrides that |
| 164 | + to `flex: N 1 0; min-width: 0`, letting the section shrink from 100% and pack |
| 165 | + onto a shared line with adjacent similarly-tagged siblings. `flex-basis: 0` |
| 166 | + means the entire row width counts as free space, distributed by `flex-grow` |
| 167 | + ratio — this is the whole authoring model in the previous section: a row's |
| 168 | + split is always "my weight ÷ the row's total weight," for any number of |
| 169 | + sections in the row, not just two. Because there's no fixed size involved |
| 170 | + (`min-width: 0` removes the min-content floor too), items also never wrap |
| 171 | + based on width alone — the only thing that forces a line break is an |
| 172 | + untagged `flex: 1 1 100%` sibling, which is why grouping/row-separation is |
| 173 | + entirely driven by that mechanism rather than any explicit "row" concept. |
| 174 | + Flexbox was chosen over CSS Grid specifically because it recalculates |
| 175 | + fill-percentage per line independently — a group's flex-grow ratios always |
| 176 | + sum to fill their shared row completely, regardless of how many sections are |
| 177 | + in the group or what span numbers they use. A fixed-track CSS Grid would |
| 178 | + leave a visible gap whenever a |
| 179 | + group's spans didn't sum to the grid's total column count. |
| 180 | +- The 900px breakpoint matches this repo's own existing stack→side-by-side |
| 181 | + precedent (`event-agenda.css`, `event-partners.css`, `bento-cards.css`), rather |
| 182 | + than Milo's own `section-metadata` intra-section grid breakpoint (1200px), |
| 183 | + since a full-page column is much wider than a sub-section grid column at the |
| 184 | + same viewport. |
| 185 | +- `main.section-columns` sets `align-items: stretch` explicitly (the flex/grid |
| 186 | + default) so sections sharing a row equalize in height — useful for backgrounds |
| 187 | + or borders to line up cleanly across the row. This only stretches each |
| 188 | + section's own box, not its content: `.section` lays out its children as normal |
| 189 | + block flow, not flex/grid, so a shorter section's actual content stays at its |
| 190 | + natural height inside the taller box rather than being force-stretched too. |
0 commit comments