flash-grid:-learn-css-grid-by-building-a-grid-system

Lately, I’ve been experimenting with the idea of building a lightweight grid system based on CSS Grid.

We do have a grid system in CodyFrame, and it’s based on Flexbox. However, CSS Grid has so many powerful features not available in Flexbox, so I ended up creating Flash Grid.

I’m going to share the whole process behind creating Flash Grid. If you’d like to learn more about CSS Grid, this is a great place to start, since we’ll touch the main CSS Grid properties, and we’ll share some practical tricks to get the most out of this powerful layout system.

In case you’d rather skip the tutorial and just grab the code:


Let’s start! ?

The first step is creating the .grid class:

$grid-columns: 12 !default;

.grid {
  --grid-cols: #{$grid-columns};
  display: grid;
  grid-gap: var(--grid-gap, 0); // default grid-gap = 0
  grid-template-columns: repeat(var(--grid-cols), 1fr); // grid of 12 flexible columns

  > * {
    grid-column-end: span var(--grid-cols); // each grid item takes full-width by default
  }
}

In defining the number of grid columns, we use the !default SCSS flag in case the grid system is imported as a module, and we want this value to be customizable.

The grid-template-columns is the property where we define the grid layout: we want 12 responsive columns. The width of each column is 1fr. Fr (fraction unit) is a smart unit, equal to 1 part of the available space. Because our grid is composed of 12x 1fr columns, each flexible column takes 1/12 of the available width.

The repeat() function allows us to pass a single width value (1fr). Another way to define the same grid would be:

.grid {
  grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr; // ?
}

…but, you know…not so elegant!

Here’s a quick overview of the grid we’ve just created:

12 columns grid

In the screenshot above, notice the numbers in between columns (for now, focus only on the positive numbers on top). These line numbers can be used to place grid items.

In the .grid snippet, we’re also targeting all the grid children, and setting their grid-column-end value equal to span 12.

By default, we want each child to take the whole available width. grid-column-end is used to specify the grid item’s end position. You can use this property to set an end line (e.g., grid-column-end: 3;). But if you use the “span” magic word, you define how many columns should be occupied by the grid item. For example, grid-column-end: span 12; means “span this element across 12 columns”.

Why setting a default 12 columns span for the grid items? We’re working mobile-first. We can assume, in most cases, our grid items will occupy the full-width (12 columns) at first, and then a smaller amount of columns on bigger screens. Our default value prevents us from having to specify on each grid item .col-12 (span 12) manually.

The number of columns is set as a CSS custom property, in case you want to change it on a component level (or by creating other utility classes). For example:

.grid--2 {
  --grid-cols: 2;
}

Next, we can define utility classes for the grid-gap property:

.grid-gap-xxxxs { --grid-gap: var(--space-xxxxs, 0.125rem); }
.grid-gap-xxxs  { --grid-gap: var(--space-xxxs, 0.25rem); }
.grid-gap-xxs   { --grid-gap: var(--space-xxs, 0.375rem); }
.grid-gap-xs    { --grid-gap: var(--space-xs, 0.5rem); }
.grid-gap-sm    { --grid-gap: var(--space-sm, 0.75rem); }
.grid-gap-md    { --grid-gap: var(--space-md, 1.25rem); }
.grid-gap-lg    { --grid-gap: var(--space-lg, 2rem); }
.grid-gap-xl    { --grid-gap: var(--space-xl, 3.25rem); }
.grid-gap-xxl   { --grid-gap: var(--space-xxl, 5.25rem); }
.grid-gap-xxxl  { --grid-gap: var(--space-xxxl, 8.5rem); }
.grid-gap-xxxxl { --grid-gap: var(--space-xxxxl, 13.75rem); }

The spacing variables are part of CodyFrame. You can replace them with your own spacing scale or use the fallbacks specified in each variable (the value after the comma is applied if the variable is undefined).

The grid-gap property is used to define the space between grid items.

To complete the basic grid system, we should define the .col classes:

@for $i from 1 through $grid-columns {
  .col-#{$i}  { grid-column-end: span #{$i}; }
}

We use the SASS @for loop to generate the .col classes according to the number of columns specified in the $grid-columns variable.

The compiled CSS is:

.col-1  { grid-column-end: span 1; }
.col-2  { grid-column-end: span 2; }
.col-3  { grid-column-end: span 3; }
.col-4  { grid-column-end: span 4; }
.col-5  { grid-column-end: span 5; }
.col-6  { grid-column-end: span 6; }
.col-7  { grid-column-end: span 7; }
.col-8  { grid-column-end: span 8; }
.col-9  { grid-column-end: span 9; }
.col-10 { grid-column-end: span 10; }
.col-11 { grid-column-end: span 11; }
.col-12 { grid-column-end: span 12; }

The col classes specify the number of columns occupied by a grid item. Remember that the “span” word means “span the element across x columns”, where x is the number specified after span.



Add some CSS Grid spice

To recap, the barebone version of Flash Grid includes the definition of the grid, the grid-gap, and the col utility classes:

Now it’s time to add some spice! ?

Here’s the .grid-auto-cols utility class:

.grid-auto-cols { // cols = same size
  display: grid;
  grid-gap: var(--grid-gap, 0);
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
}

This class is similar to the .grid class, except we don’t set a number of columns. auto-fit replaces the 12 in the .grid class. It means letting CSS Grid decide the number of columns based on the width value of the columns (the second value of the repeat() function).

But wait! The width value (1fr in the .grid class) is now replaced by a minmax() function. It literally means the minimum width of a column is 0, while the max value is 1fr. We’re setting a range of values for the column width.

The result: you get a grid where all columns have the same width, regardless of their content or the number of grid items.

Grid Auto Cols

Using a similar approach, we create the .grid-auto-{size} utility classes:

.grid-auto-xs, .grid-auto-sm, .grid-auto-md, .grid-auto-lg, .grid-auto-xl { // auto-sized grid
  display: grid;
  grid-gap: var(--grid-gap, 0);
  grid-template-columns: repeat(auto-fit, minmax(var(--col-min-width), 1fr));
}

.grid-auto-xs { --col-min-width: 8rem; }
.grid-auto-sm { --col-min-width: 10rem; }
.grid-auto-md { --col-min-width: 15rem; }
.grid-auto-lg { --col-min-width: 20rem; }
.grid-auto-xl { --col-min-width: 25rem; }

Unlike .grid-auto-cols, these new classes have a minimum width value equal to --col-min-width. The result is a responsive grid where a new column is added when there’s enough space for it (the minimum width specified in the minmax() function).

Alt Text

? With the .grid-auto utility classes (.grid-auto-cols and .grid-auto-{size}) you can create responsive layouts with no need to use .col classes on the grid items. Actually, you shouldn’t use .col classes at all if you want the .grid-auto classes to work properly.

Finally, to take advantage of the grid line numbers, we can create a new set of utility classes: col-start-{line-number} and .col-end-{line-number}.

@for $i from 1 through $grid-columns {
  .col-start-#{$i} { grid-column-start: #{$i}; }
  .col-end-#{$i 1} { grid-column-end: #{$i 1}; }
}

The .col-start classes go from .col-start-1 to col-start-12, while the .col-end classes go from .col-end-2 to .col-end-13.

Use them if you want a grid item to span between a specific start and end line.

Remember these are the line numbers:

12 columns grid

The negative numbers at the bottom are an alternative way to target the same lines. Why they’re useful: if you want to target the final line, regardless of the number of columns, you can do the following:

.col-end { 
  grid-column-end: -1; 
}

The .col-start/end classes allow you to create advanced grids:



Class breakpoint modifiers

To make our grid editable at different breakpoints, we can add class modifiers. In CodyFrame, our convention is to add a @{breakpoint} suffix to the classes (e.g., col-4@sm) to target breakpoints.

Here’s an example of the class modifiers at the x-small breakpoint:

$breakpoints: (
  xs: 32rem, 
  sm: 48rem,
  md: 64rem,
  lg: 80rem,
  xl: 90rem
) !default;

@mixin breakpoint($breakpoint) {
  @media (min-width: map-get($map: $breakpoints, $key: $breakpoint)) { @content; }
}

@include breakpoint(xs) {
  .grid-auto-xs@xs { --col-min-width: 8rem; }
  .grid-auto-sm@xs { --col-min-width: 10rem; }
  .grid-auto-md@xs { --col-min-width: 15rem; }
  .grid-auto-lg@xs { --col-min-width: 20rem; }
  .grid-auto-xl@xs { --col-min-width: 25rem; }

  .grid-auto-cols@xs { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); }

  @for $i from 1 through $grid-columns {
    .col-#{$i}@xs  { grid-column-end: span #{$i}; }
    .col-start-#{$i}@xs { grid-column-start: #{$i}; }
    .col-end-#{$i 1}@xs { grid-column-end: #{$i 1}; }
  }

  .col-start-auto@xs { grid-column-start: auto; }
  .col-end-auto@xs { grid-column-end: auto; }
}



Beta version of ⚡️ Flash Grid

The beta of Flash Grid is good to go!

Take it for a spin on Codepen:

Final SCSS code:

// ⚡️ Flash Grid
$grid-columns: 12 !default;

.grid, [class*="grid-auto-"] {
  display: grid;
  grid-gap: var(--grid-gap, 0);
}

.grid {
  --grid-cols: #{$grid-columns};
  grid-template-columns: repeat(var(--grid-cols), 1fr);

  > * {
    grid-column-end: span var(--grid-cols);
  }
}

.grid-auto-xs, .grid-auto-sm, .grid-auto-md, .grid-auto-lg, .grid-auto-xl { // auto-sized grid
  grid-template-columns: repeat(auto-fit, minmax(var(--col-min-width), 1fr));
}

.grid-auto-xs { --col-min-width: 8rem; }
.grid-auto-sm { --col-min-width: 10rem; }
.grid-auto-md { --col-min-width: 15rem; }
.grid-auto-lg { --col-min-width: 20rem; }
.grid-auto-xl { --col-min-width: 25rem; }

.grid-auto-cols { // cols = same size
  grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
}

.grid-gap-xxxxs { --grid-gap: var(--space-xxxxs, 0.125rem); }
.grid-gap-xxxs  { --grid-gap: var(--space-xxxs, 0.25rem); }
.grid-gap-xxs   { --grid-gap: var(--space-xxs, 0.375rem); }
.grid-gap-xs    { --grid-gap: var(--space-xs, 0.5rem); }
.grid-gap-sm    { --grid-gap: var(--space-sm, 0.75rem); }
.grid-gap-md    { --grid-gap: var(--space-md, 1.25rem); }
.grid-gap-lg    { --grid-gap: var(--space-lg, 2rem); }
.grid-gap-xl    { --grid-gap: var(--space-xl, 3.25rem); }
.grid-gap-xxl   { --grid-gap: var(--space-xxl, 5.25rem); }
.grid-gap-xxxl  { --grid-gap: var(--space-xxxl, 8.5rem); }
.grid-gap-xxxxl { --grid-gap: var(--space-xxxxl, 13.75rem); }

@for $i from 1 through $grid-columns {
  .col-#{$i}  { grid-column-end: span #{$i}; }
  .col-start-#{$i} { grid-column-start: #{$i}; }
  .col-end-#{$i 1} { grid-column-end: #{$i 1}; }
}

.col-start { grid-column-start: 1; }
.col-end { grid-column-end: -1; }

// breakpoints
$breakpoints: (
  xs: 32rem, 
  sm: 48rem,
  md: 64rem,
  lg: 80rem,
  xl: 90rem
) !default;

@mixin breakpoint($breakpoint) {
  @media (min-width: map-get($map: $breakpoints, $key: $breakpoint)) { @content; }
}

@include breakpoint(xs) {
  .grid-auto-xs@xs { --col-min-width: 8rem; }
  .grid-auto-sm@xs { --col-min-width: 10rem; }
  .grid-auto-md@xs { --col-min-width: 15rem; }
  .grid-auto-lg@xs { --col-min-width: 20rem; }
  .grid-auto-xl@xs { --col-min-width: 25rem; }

  .grid-auto-cols@xs { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); }

  @for $i from 1 through $grid-columns {
    .col-#{$i}@xs  { grid-column-end: span #{$i}; }
    .col-start-#{$i}@xs { grid-column-start: #{$i}; }
    .col-end-#{$i 1}@xs { grid-column-end: #{$i 1}; }
  }

  .col-start-auto@xs { grid-column-start: auto; }
  .col-end-auto@xs { grid-column-end: auto; }
}

@include breakpoint(sm) {
  .grid-auto-xs@sm { --col-min-width: 8rem; }
  .grid-auto-sm@sm { --col-min-width: 10rem; }
  .grid-auto-md@sm { --col-min-width: 15rem; }
  .grid-auto-lg@sm { --col-min-width: 20rem; }
  .grid-auto-xl@sm { --col-min-width: 25rem; }

  .grid-auto-cols@sm { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); }

  @for $i from 1 through $grid-columns {
    .col-#{$i}@sm  { grid-column-end: span #{$i}; }
    .col-start-#{$i}@sm { grid-column-start: #{$i}; }
    .col-end-#{$i 1}@sm { grid-column-end: #{$i 1}; }
  }

  .col-start-auto@sm { grid-column-start: auto; }
  .col-end-auto@sm { grid-column-end: auto; }
}

@include breakpoint(md) {
  .grid-auto-xs@md { --col-min-width: 8rem; }
  .grid-auto-sm@md { --col-min-width: 10rem; }
  .grid-auto-md@md { --col-min-width: 15rem; }
  .grid-auto-lg@md { --col-min-width: 20rem; }
  .grid-auto-xl@md { --col-min-width: 25rem; }

  .grid-auto-cols@md { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); }

  @for $i from 1 through $grid-columns {
    .col-#{$i}@md  { grid-column-end: span #{$i}; }
    .col-start-#{$i}@md { grid-column-start: #{$i}; }
    .col-end-#{$i 1}@md { grid-column-end: #{$i 1}; }
  }

  .col-start-auto@md { grid-column-start: auto; }
  .col-end-auto@md { grid-column-end: auto; }
}

@include breakpoint(lg) {
  .grid-auto-xs@lg { --col-min-width: 8rem; }
  .grid-auto-sm@lg { --col-min-width: 10rem; }
  .grid-auto-md@lg { --col-min-width: 15rem; }
  .grid-auto-lg@lg { --col-min-width: 20rem; }
  .grid-auto-xl@lg { --col-min-width: 25rem; }

  .grid-auto-cols@lg { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); }

  @for $i from 1 through $grid-columns {
    .col-#{$i}@lg  { grid-column-end: span #{$i}; }
    .col-start-#{$i}@lg { grid-column-start: #{$i}; }
    .col-end-#{$i 1}@lg { grid-column-end: #{$i 1}; }
  }

  .col-start-auto@lg { grid-column-start: auto; }
  .col-end-auto@lg { grid-column-end: auto; }
}

@include breakpoint(xl) {
  .grid-auto-xs@xl { --col-min-width: 8rem; }
  .grid-auto-sm@xl { --col-min-width: 10rem; }
  .grid-auto-md@xl { --col-min-width: 15rem; }
  .grid-auto-lg@xl { --col-min-width: 20rem; }
  .grid-auto-xl@xl { --col-min-width: 25rem; }

  .grid-auto-cols@xl { grid-template-columns: repeat(auto-fit, minmax(0, 1fr)); }

  @for $i from 1 through $grid-columns {
    .col-#{$i}@xl  { grid-column-end: span #{$i}; }
    .col-start-#{$i}@xl { grid-column-start: #{$i}; }
    .col-end-#{$i 1}@xl { grid-column-end: #{$i 1}; }
  }

  .col-start-auto@xl { grid-column-start: auto; }
  .col-end-auto@xl { grid-column-end: auto; }
}
flash-is-responsible-for-the-internet’s-most-creative-era

These days, our web browsers—whether on mobile or desktop—are highly functional and can do all sorts of things that we could only dream of a decade prior.

But despite that, one could argue that the web has actually gotten less creative over time, not more. This interpretation of events is a key underpinning of Web Design: The Evolution of the Digital World 1990-Today (Taschen, $50), a new visual-heavy book from author Rob Ford and editor Julius Wiedemann that does something that hasn’t been done on the broader internet in quite a long time: It praises the use of Flash as a creative tool, rather than a bloated malware vessel, and laments the ways that visual convention, technical shifts, and walled gardens have started to rein in much of this unvarnished creativity.

This is a realm where small agencies supporting big brands, creative experimenters with nothing to lose, and teenage hobbyists could stand out simply by being willing to try something risky. It was a canvas with a built-in distribution model. What wasn’t to like, besides a whole host of malware?

The 640-page book, full of pictures of interactive websites from prior eras, benefits from taking a wide view of the visual culture of the past: Starting at the embryonic stages of the World Wide Web, it follows the art of web design through periods of extreme experimentation on the way to the convention-driven scaffolding we have today. The book makes a compelling case through its general structure that the sweet spot of creative web design came during the late 1990s through the mid-2000s—periods in which major brands were willing to invest a whole lot of money in a website intended for show, not just tell.

Ford, who is known for running the long-running Favourite Web Awards (FWA), is very much in the “show” category. In an email interview, Ford listed off a dizzying array of iconic websites, pages that once wowed the broader internet and helped uncover key design mechanisms—for example, Ford says 1998’s EYE4U, an early influence on many Flash developers, “showed us responsive design 15 years before the term was coined,” while sites like 2002’s Who’s We Studios and 2003’s tokyoplastic brought personality to the equation.

There was a lot of it because of the artistic influences these creators brought forth. “It’s worth noting how many super-creative talents have a ’background‘ in rave and club culture, whether that be as punters or promoters,” Ford said.

These sites, reliant on animation and Flash’s underlying ActionScript language, were the kind that excited creatives, ready to embrace an artistic medium, but frustrated usability experts, who would rail against the way the sites flouted basic convention.

If any one website sort of hits these two tensions perfectly, it’s Subservient Chicken, the popular Burger King-produced web interactive which hits right in the middle of the nearly three-decade period covered in this book. At the time of its creation, it was widely discussed and dissected by advertisers who realized that its combination of visuals and ELIZA-style text commands represented something new. Given the move towards chatbots and memetic videos in the years since, it feels downright predictive.

“Subservient Chicken gave us something we hadn’t experienced before, that was real time (even though it actually wasn’t real time, it faked it very well) interaction but, more importantly, an emotional ‘live’ personal experience,” Ford notes, adding that it also predicted voice assistants that work in similar ways.

But the aggressive creativity offered by Flash eventually would prove impossible to bring to the mobile era in quite the same way, as portability and improved HTML rendering capabilities made it obsolete. Around the time of Steve Jobs’ famous open letter to Adobe, Ford noted that many of the Flash era’s creators “completely moved away from the web and used their talents elsewhere.” There were still some notable HTML5-based creations during this period—including the Arcade Fire’s Google Chrome “experiment” “The Wilderness Downtown,” which Ford calls “the biggest, most influential website in over a decade.” But the social era—particularly Facebook Pages—proved “a final nail in the coffin for web design,” he noted.

But all those wild ideas had to go somewhere, and many of them didn’t appear in the App Store. Ford says that while the modern web has largely eschewed the creative risks of the Flash era, it can be found in physical mediums and augmented reality, places where many of the creative explosions that web tools like Flash and HTML5 initially allowed can be furthered and built upon—with many of the same creators behind the initial rise responsible for much of the modern excitement.

“The progressive interaction and visual creativity is happening outside of the web browser now,” he explained. “The rise in interactive installations, AR, and experiential in general is where the excitement of the early days is finally happening again.”

This book, which hits next month, comes just at a time when Flash—a tool first developed by FutureWave, then improved upon by Macromedia and exploited on a mass scale by Adobe—is about to meet its maker, and the internet has moved past it for perfectly sensible reasons. (Seriously, Flash is hacked all to hell and you probably should avoid it in most circumstances.)

While a book may be static rather than interactive, this feels like a fitting coda for a kind of digital creativity that—like Geocities and MySpace pages, multimedia CD-ROMs, and Prodigy graphical interfaces before it—has faded in prominence. But when it was there, we needed it, because of all the creative folks it inspired.

“Without the rebels we’d still be looking at static websites with gray text and blue hyperlinks,” Ford said.