@_ed/grid

An easy-to-use, responsive n-column grid system for React, built with styled-components integration and TypeScript support. Supports a mix of static grids (in px) and fluid grids (in vw) at different resolutions.

Usage no npm install needed!

<script type="module">
  import EdGrid from 'https://cdn.skypack.dev/@_ed/grid';
</script>

README

An easy-to-use, responsive n-column grid system for React, built with styled-components integration and TypeScript support. Supports a mix of static grids (in px) and fluid grids (in vw) at different resolutions.

Installation

yarn add @_ed/grid

Usage

This library provides three main components:

  • <GridProvider /> — Defines grid properties for descendant Row and Col elements.
  • <Row /> — Creates a new row in the grid.
  • <Col /> — Where the magic happens!

There are also a handful of useful mixins for use with styled-components.

GridProvider

To begin using the system, you'll first need to create a grid definition using createGrid(), and supply it to a GridProvider component like so:

// theme.ts (or theme.js)
import { createGrid } from '@_ed/grid'

export const grid = createGrid({
  columns: 12,
  breakpoints: {
    xs: {
      at: 0,
      fluid: true,
      padding: 5,
      gutter: 5,
      width: 100
    },
    md: {
      at: 900,
      fluid: true,
      padding: 2,
      gutter: 3,
      width: 100
    },
    lg: {
      at: 1500,
      fluid: false,
      padding: 40,
      gutter: 30,
      width: 1200
    }
  }
})

The above creates a 12-column, mobile-first grid, with three breakpoints, named xs, md and lg. The first two sizes are fluid, while the lg breakpoint is pixel based. It's useful to define your grid in a separate file, to reduce clutter.

To use these grid settings in your application:

// App.ts
import { GridProvider } from `@_ed/grid`
import { grid } from 'theme'

export function App() {
  return (
    <GridProvider grid={grid}>
      <Row>
        <Col cols={12}>
          <h1>My Cool Site!</h1>
        </Col>
      </Row>
    </GridProvider>
  )
}

Row

You must wrap groups of <Col /> components in a <Row /> to ensure correct layout.

Row currently only takes two props, gutterTop and gutterBottom, which are both optional. They can either be true or a number. Using these props will assign margin-top and/or margin-bottom, to the value of the current breakpoint's gutter size. This allows you to have the same spacing above/below, as between columns. There is also a mixin to achieve this anywhere else.

Col

The Col component takes the following props:

  • cols — (required) the number of columns this element should take up. This will be the default number of columns, but can be overridden per-breakpoint.
  • left — (optional, number) adds a margin-left to this element, where the value is the number passed, multiplied by the column width, plus any gutters. This affects the placement of this column, plus any columns to the right.
  • right — (optional, number) adds a margin-right to this element, where the value is the number passed, multiplied by the column width, plus any gutters. This affects any columns to the right.
  • drift — (optional, number) shifts the element by n columns. A negative number will drift the column to the left, and a positive to the right. This does not affect surrounding columns.
  • visible — (optional, defaults to true, boolean) whether or not this column is visible.

Each property can be overridden for each and any breakpoint. Simply prepend the breakpoint name, to the property name, camel-cased. The exception is that the breakpoint name is used instead of cols. For example, if you have a lg breakpoint, you can use lg, lgLeft, lgRight, lgDrift, lgVisible.

Grid Settings

Grids are created with createGrid — see the example above.

When creating a grid, each breakpoint can have the following parameters.

  • at (required, number) — the screen resolution at which this breakpoint takes effect. For the smallest mobile breakpoint, this should be 0
  • fluid (required, boolean) — whether or not this breakpoint is fluid, or static in width. Static breakpoints will use px for all CSS, whereas fluid will use vw for all CSS. You would typically make all of your breakpoints fluid, except for perhaps the largest one, if you'd like to limit the width of your content on a larger screen.
  • width (required, number) — the total width of the grid, in grid units, at this breakpoint, including all side padding, gutters and columns. For fluid, this should probably be 100. For static, it'll be in pixels (eg 1200).
  • gutter (required, number) — the spacing between each column, in grid units. For fluid, you might use 3 (3% of the viewport width), whereas for static you might use 30 (for 30px).
  • padding (required, number) — similar to gutter, except it instead defines the spacing between the content and the edge of the grid. You'd often set this to be the same value as gutter
  • columns (optional, number) — use this to override the number of columns at this breakpoint. For instance, you might set this to 4 for your mobile grid. In most cases, you'll probably want to just ignore this option.

Mixins

There are a handful of mixins available for use with styled-components. Some even allow you to bypass using Col and Row altogether :)

column(args) takes the same arguments as the Col component, detailed above. In fact, the Col component just uses this mixin!

import styled from 'styled-components'
import { column } from '@_ed/grid'

const BigRedCol = styled.div`
  background: red;
  ${column({
    cols: 12,
    mdCols: 6
  })}
`

row() takes no arguments. It adds the appropriate styling to create a Row-like component.

import styled from 'styled-components'
import { row } from '@_ed/grid'

const MyCustomRow = styled.div`
  margin-top: 100px;
  margin-bottom: 100px;
  ${row()}
`

columnPadding(args, fromSize) args should be an object, containing one or two arguments, gutterTop and gutterBottom, both numbers. It'll add margin-top or margin-bottom for each breakpoint, where the value used is the gutter size multiplied by the number supplied. Also takes an optional fromSize argument, which should be a breakpoint name. The padding will apply from this breakpoint and larger.

import styled from 'styled-components'
import { columnPadding } from '@_ed/grid'

const GridItem = styled.div`
  ${columnPadding({
    gutterTop: 2
  })}
`

each(callback) — calls your callback for each breakpoint, returning the result. The result for each breakpoint is automatically wrapped in a clamped media query (with min-width AND max-width), in that the result for each breakpoint only affects that exact breakpoint only (not mobile-first).

import styled, { css } from 'styled-components'
import { each } from '@_ed/grid'

const OneColHigh = styled.div`
  // Make this div the same height as 1 column width,
  // for every breakpoint
  ${each(breakpoint => css`
    height: ${breakpoint.colWidths[1]}${breakpoint.units};
  `)}
`

at(sizes, callback) — exactly the same as each, however you must specify one or more sizes to be iterated over. Just like each, the result of your callback for each breakpoint is wrapped in a min/max media query, rather than just a min-width one. The size argument should either be a string, or an array of strings. callback can also just be a piece of CSS, rather than a function.

import styled, { css } from 'styled-components'
import { at } from '@_ed/grid'

const MyBox = styled.div`
  // Font size is 30 on desktop, 10 on mobile
  font-size: 30px;
  ${at('xs', css`
    font-size: 10px;
  `)}
  // Padding-top is the same as the gutter size on xs and md
  ${at(['xs', 'md'], breakpoint => css`
    padding-top: ${breakpoint.gutter}${breakpoint.units};
  `)}

from(sizes, callback) — the same as at, except the media queries used are not clamped. This means that if you have breakpoints xs, md and lg, then from('md') will affect both md and lg.

import styled, { css } from 'styled-components'
import { from } from '@_ed/grid'

const MyBox = styled.div`
  // Padding-top is the same as the gutter size for `md` and higher
  ${from(['md'], breakpoint => css`
    padding-top: ${breakpoint.gutter}${breakpoint.units};
  `)}

queryAt(sizes) — produces a clamped media query in the form (min-width: X) and (max-width: X), except for the smallest breakpoint which would be (max-width: X) or the largest breakpoint which would be (min-width: X). The sizes arg can be a string or array of strings

import styled, { css } from 'styled-components'
import { queryAt } from '@_ed/grid'

const MyBox = styled.div`
  // Hide this element at the xs breakpoint only
  @media ${queryAt('xs')} {
    display: none;
  }

queryFrom(size) — produces a min-width media query, which will it's contents from the specified size and upwards

import styled, { css } from 'styled-components'
import { queryFrom } from '@_ed/grid'

const MyBox = styled.div`
  // Hide this element at md and higher breakpoints
  @media ${queryFrom('md')} {
    display: none;
  }

queryTo(size) — TODO, not built yet!

columnWidth(cols, size?) — sets the width of this element to be the same as a column with that number of columns. The size arg is an optional breakpoint name, for which this mixin will apply.

import styled, { css } from 'styled-components'
import { columnWidth } from '@_ed/grid'

const MyBox = styled.div`
  ${columnWidth(6)}  // default to 6
  ${columnWidth(8, 'lg')} // 8 cols wide at 'lg'

Grid Properties

When using mixins like each, or the useGrid() method, or when accessing the grid from props.theme.grid, you should note that each breakpoint has additional properties to the ones passed in with createGrid. Here is the schema:

  • name: string — the name of the breakpoint, eg xs
  • columns: number — the number of columns at this breakpoint. defaults to the number of columns for the grid.
  • min: number — the minimum viewport width for this breakpoint
  • max: number | null — the size of the next biggest breakpoint, or null for the largest
  • query: string — a string in the form (min-width: min), or an empty string for the smallest
  • rangedQuery: string — the same as query, but also includes a (max-width) value
  • colWidths: { [index: number]: number } — a dictionary of column widths. So colWidths[2] is how wide a Col taking up 2-columns would be.
  • units: string — either "vw" or "px"
  • size — an object with all primitive sizes used by this breakpoint. It has the properties width, column, gutter and padding

Tips

Mobile-First

This system is inherently mobile-first. When you use a <Col /> component, the cols prop will target all breakpoints. If you have 3 breakpoints, sm, md and lg, then the mdCols prop will target both md and lg breakpoints.

Nesting Rows and Cols

Rows and columns can be nested infinitely, however the nesting should alternate between Row and Col.

For example, this will result in display issues:

<Col cols={12}>
  <Col cols={6}>
    INVALID
  </Col>
  <Col cols={6}>
    INVALID
  </Col>
</Col>

Instead you should do this:

<Col cols={12}>
  <Row>
    <Col cols={6}>
      :)
    </Col>
    <Col cols={6}>
      :)
    </Col>
  </Row>
</Col>