|
| 1 | +import { Source } from '@storybook/addon-docs/blocks'; |
| 2 | + |
| 3 | +# Grid |
| 4 | + |
| 5 | +The `grid` module implements the [Guardian Grid](https://theguardian.design/2a1e5182b/p/41be19-grids) using [CSS grid](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout/Basic_concepts_of_grid_layout). The columns described in the Guardian grid definition are implemented as <CSSGridColumn />s. In some Guardian designs we also have a concept of three layout columns: <LeftColumn />, <CentreColumn />, and <RightColumn />. As these are often used in designs on dotcom and apps, this module provides an API for positioning content in these columns as well. |
| 6 | + |
| 7 | +<Diagram> |
| 8 | + <text x="45" y="265" fill="red" style={textStyles}>Left column</text> |
| 9 | + <text x="460" y="265" fill="green" style={textStyles}>Centre column</text> |
| 10 | + <text x="1035" y="265" fill="blue" style={textStyles}>Right column</text> |
| 11 | +</Diagram> |
| 12 | + |
| 13 | +export const textStyles = { |
| 14 | + fontSize: 30, |
| 15 | + fontFamily: 'sans-serif', |
| 16 | + fontWeight: 'bold', |
| 17 | +}; |
| 18 | + |
| 19 | +Note that some of these layout columns only exist at certain breakpoints. All breakpoints have the <CentreColumn />, but the <RightColumn /> only appears from the "desktop" breakpoint (980px), and the <LeftColumn /> only appears from the "leftCol" breakpoint (1140px). You can use the [media queries API](https://guardian.github.io/storybooks/?path=/docs/source_foundations-media-queries--docs) from `@guardian/source` to specify which columns to use at different breakpoints. |
| 20 | + |
| 21 | +The following code provides an example of the API in use, while later sections on this page will give more details about the features demonstrated here. Note that a mixture of different Emotion patterns, such as [object styles](https://emotion.sh/docs/css-prop#object-styles) and [string styles](https://emotion.sh/docs/css-prop#string-styles), are included for demonstration purposes. |
| 22 | + |
| 23 | +<Source language="tsx" code={` |
| 24 | +import { css } from '@emotion/react'; |
| 25 | +import { from } from '@guardian/source/foundations'; |
| 26 | +import { grid } from '../grid'; |
| 27 | +
|
| 28 | +const MyComponent = () => ( |
| 29 | + <article css={css(grid.container)}> |
| 30 | + <h1 css={css(grid.column.centre)}> |
| 31 | + Headline |
| 32 | + </h1> |
| 33 | + <p css={{ |
| 34 | + '&': css(grid.column.centre), |
| 35 | + [from.leftCol]: css(grid.column.left), |
| 36 | + }}> |
| 37 | + Byline |
| 38 | + </p> |
| 39 | + <ol css={css\` |
| 40 | + \${grid.column.right} |
| 41 | + display: none; |
| 42 | +
|
| 43 | + \${from.desktop} { |
| 44 | + display: block; |
| 45 | + } |
| 46 | + \`}> |
| 47 | + Most viewed |
| 48 | + </ol> |
| 49 | + </article> |
| 50 | +); |
| 51 | +`} /> |
| 52 | + |
| 53 | +## Grid Container |
| 54 | + |
| 55 | +A CSS grid layout consists of an element that's designated as a grid container, and grid items, which are its direct children in the DOM. The `grid` module provides two variants of a grid container, both of which set up the Guardian grid layout. |
| 56 | + |
| 57 | +The first is `grid.container`, which defines a grid that covers the entire viewport, and allows you to position content all the way from the left edge to the right. This is useful for elements like the [immersive main media](https://www.theguardian.com/society/2025/jul/08/the-life-swap-dream-or-a-marketing-gimmick-the-italian-towns-selling-houses-for-1), which needs to span the entire viewport. |
| 58 | + |
| 59 | +The second is `grid.paddedContainer`, which works similarly to `grid.container`, except that it only allows you to position content within the main Guardian grid area, and automatically generates margins on either side. There are many designs where nothing appears in those margins, and it's therefore convenient to be able to position content without having to worry about them. |
| 60 | + |
| 61 | +In practice, which one of these two containers you choose will affect `grid.column.all` in the [Column API](#column-api), and usages of the `'grid-start'` and `'grid-end'` lines in the [Line API](#line-api). |
| 62 | + |
| 63 | +## Column API |
| 64 | + |
| 65 | +The column API is used to interact with the three layout columns. `grid.column.left` will position an element in the <LeftColumn />, `grid.column.centre` will position an element in the <CentreColumn />, and `grid.column.right` will position an element in the <RightColumn />. |
| 66 | + |
| 67 | +There's also `grid.column.all`, which will position an item such that it spans all the way from the start of the grid to the end. As discussed in [Grid Container](#grid-container), if you're within a `grid.container` then this item will always span the entire width of the viewport, whereas with `grid.paddedContainer` it will only span the area inside the margins. |
| 68 | + |
| 69 | +<Source language="tsx" code={` |
| 70 | +import { css } from '@emotion/react'; |
| 71 | +import { grid } from '../grid'; |
| 72 | +
|
| 73 | +const ImmersiveHeader = () => ( |
| 74 | + <header css={css(grid.container)}> |
| 75 | + <figure css={css(grid.column.all)}> |
| 76 | + Main media |
| 77 | + </figure> |
| 78 | + </header> |
| 79 | +); |
| 80 | +
|
| 81 | +const FrontContainer = () => ( |
| 82 | + <section css={css(grid.paddedContainer)}> |
| 83 | + <article css={css(grid.column.all)}> |
| 84 | + First card |
| 85 | + </article> |
| 86 | + </section> |
| 87 | +); |
| 88 | +`} /> |
| 89 | + |
| 90 | +## Line API |
| 91 | + |
| 92 | +While the [Column API](#column-api) covers many use cases, it is intentionally limited. In cases where designs call for more flexibility, such as crossing multiple layout columns, or ignoring them completely, a lower-level API is available for positioning elements across an arbitrary number of <CSSGridColumn />s. |
| 93 | + |
| 94 | +A CSS grid layout automatically generates [lines](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout/Grid_layout_using_line-based_placement) with numerical indices between each grid column. The [grid containers](#grid-container) in this module also define a set of named grid lines based around the layout columns mentioned above. These are, from left to right: `grid-start`, `left-column-start`, `left-column-end`, `centre-column-start`, `centre-column-end`, `right-column-start`, `right-column-end`, and `grid-end`. |
| 95 | + |
| 96 | +<Diagram viewBox="0 -100 1300 700"> |
| 97 | + <text x="0" y="-70" fill="#d97700" style={lineTextStyles}>grid-start</text> |
| 98 | + <line x1="2" y1="-50" x2="2" y2="550" stroke-width="4" stroke="#d97700" /> |
| 99 | + <text x="30" y="-20" fill="red" style={lineTextStyles}>left-column-start</text> |
| 100 | + <line x1="20" y1="-50" x2="20" y2="550" stroke-width="4" stroke="red" /> |
| 101 | + <text x="65" y="535" fill="red" style={lineTextStyles}>left-column-end</text> |
| 102 | + <line x1="240" y1="-50" x2="240" y2="550" stroke-width="4" stroke="red" /> |
| 103 | + <text x="270" y="-20" fill="green" style={lineTextStyles}>centre-column-start</text> |
| 104 | + <line x1="260" y1="-50" x2="260" y2="550" stroke-width="4" stroke="green" /> |
| 105 | + <text x="670" y="535" fill="green" style={lineTextStyles}>centre-column-end</text> |
| 106 | + <line x1="880" y1="-50" x2="880" y2="550" stroke-width="4" stroke="green" /> |
| 107 | + <text x="990" y="-20" fill="blue" style={lineTextStyles}>right-column-start</text> |
| 108 | + <line x1="980" y1="-50" x2="980" y2="550" stroke-width="4" stroke="blue" /> |
| 109 | + <text x="1090" y="535" fill="blue" style={lineTextStyles}>right-column-end</text> |
| 110 | + <line x1="1280" y1="-50" x2="1280" y2="550" stroke-width="4" stroke="blue" /> |
| 111 | + <line x1="1298" y1="-50" x2="1298" y2="550" stroke-width="4" stroke="#d97700" /> |
| 112 | + <text x="1210" y="580" fill="#d97700" style={lineTextStyles}>grid-end</text> |
| 113 | +</Diagram> |
| 114 | + |
| 115 | +export const lineTextStyles = { |
| 116 | + fontSize: 24, |
| 117 | + fontFamily: 'sans-serif', |
| 118 | +}; |
| 119 | + |
| 120 | +Note that, as mentioned above, not all of the layout columns exist at all breakpoints, and therefore not all of these named lines do either. You can use the [media queries API](https://guardian.github.io/storybooks/?path=/docs/source_foundations-media-queries--docs) from `@guardian/source` to specify which lines to use at different breakpoints. |
| 121 | + |
| 122 | +To position an element between two grid lines, the `grid.between` function is available. It takes the line name or number at which the element should start, and the line name or number at which it should end. Alternatively, to have an element start at a particular line and span a certain number of <CSSGridColumn />s, the `grid.span` function is available. It takes the line name or number at which the element should start, and a number of columns to span. |
| 123 | + |
| 124 | +<Source language="tsx" code={` |
| 125 | +import { css } from '@emotion/react'; |
| 126 | +import { grid } from '../grid'; |
| 127 | +
|
| 128 | +const ShowcaseHeader = () => ( |
| 129 | + <header css={css(grid.paddedContainer)}> |
| 130 | + <figure css={css( |
| 131 | + grid.between('centre-column-start', 'right-column-end') |
| 132 | + )}> |
| 133 | + Main media |
| 134 | + </figure> |
| 135 | + </header> |
| 136 | +); |
| 137 | +
|
| 138 | +const GalleryHeader = () => ( |
| 139 | + <header css={css(grid.container)}> |
| 140 | + <p css={css(grid.span('centre-column-start', 6))}> |
| 141 | + Standfirst |
| 142 | + </p> |
| 143 | + </header> |
| 144 | +); |
| 145 | +`} /> |
| 146 | + |
| 147 | +The functions in this part of the API correspond to features of the [`grid-column` CSS property](https://developer.mozilla.org/en-US/docs/Web/CSS/grid-column). While that property could be used directly, these functions allow for type-safety and auto-completion of the named grid lines. |
| 148 | + |
| 149 | +{ /* Components used throughout this file */ } |
| 150 | + |
| 151 | +export const CSSGridColumn = () => ( |
| 152 | + <span style={{ backgroundColor: '#ddd', paddingLeft: 4, paddingRight: 4 }}> |
| 153 | + CSS grid column |
| 154 | + </span> |
| 155 | +); |
| 156 | + |
| 157 | +export const LeftColumn = () => ( |
| 158 | + <span style={{ color: 'red' }}>left column</span> |
| 159 | +); |
| 160 | + |
| 161 | +export const CentreColumn = () => ( |
| 162 | + <span style={{ color: 'green' }}>centre column</span> |
| 163 | +); |
| 164 | + |
| 165 | +export const RightColumn = () => ( |
| 166 | + <span style={{ color: 'blue' }}>right column</span> |
| 167 | +); |
| 168 | + |
| 169 | +export const Diagram = ({ children, viewBox }) => ( |
| 170 | + <svg viewBox={viewBox ?? "0 0 1300 500"} xmlns="http://www.w3.org/2000/svg"> |
| 171 | + <defs> |
| 172 | + <rect id="grid-column" x="0" y="0" width="60" height="500" fill="#ddd" /> |
| 173 | + </defs> |
| 174 | + <use href="#grid-column" x="20" /> |
| 175 | + <use href="#grid-column" x="100" /> |
| 176 | + <use href="#grid-column" x="180" /> |
| 177 | + <use href="#grid-column" x="260" /> |
| 178 | + <use href="#grid-column" x="340" /> |
| 179 | + <use href="#grid-column" x="420" /> |
| 180 | + <use href="#grid-column" x="500" /> |
| 181 | + <use href="#grid-column" x="580" /> |
| 182 | + <use href="#grid-column" x="660" /> |
| 183 | + <use href="#grid-column" x="740" /> |
| 184 | + <use href="#grid-column" x="820" /> |
| 185 | + <use href="#grid-column" x="900" /> |
| 186 | + <use href="#grid-column" x="980" /> |
| 187 | + <use href="#grid-column" x="1060" /> |
| 188 | + <use href="#grid-column" x="1140" /> |
| 189 | + <use href="#grid-column" x="1220" /> |
| 190 | + <rect x="20" y="2" width="220" height="496" stroke="red" stroke-width="4" fill="none" /> |
| 191 | + <rect x="260" y="2" width="620" height="496" stroke="green" stroke-width="4" fill="none" /> |
| 192 | + <rect x="980" y="2" width="300" height="496" stroke="blue" stroke-width="4" fill="none" /> |
| 193 | + {children} |
| 194 | + </svg> |
| 195 | +); |
0 commit comments