CSS Grid in IE: Duplicate area names now supported!

Autoprefixer is now up to version 9.3.1 and there have been a lot of updates since I wrote the original three-part CSS Grid in IE series — the most important update of which is the new grid-areas system. This is mostly thanks to Bogdan Dolin, who has been working like crazy to fix loads of Autoprefixer issues. Autoprefixer’s grid translations were powerful before, but they have gotten far more powerful now!

Article Series:

  1. Debunking common IE Grid misconceptions
  2. CSS Grid and the new Autoprefixer
  3. Faking an auto-placement grid with gaps
  4. Duplicate area names now supported! (This Post)

How Autoprefixer supports duplicate area names

In Part 2 of the series, I pointed out why Autoprefixer wasn’t able to handle duplicate area names that were used across multiple selectors.

To summarize, this is the example I gave in the article:

.grid-alpha { grid-template-areas: "delta echo";
} .grid-beta { grid-template-areas: "echo delta";
} .grid-cell { /* What column does .grid-cell go in? */ -ms-grid-column: ???; grid-area: echo;
}

We thought that since Autoprefixer didn’t have access to the DOM, there was no way of knowing what grid the grid cell belonged to and thus which column the grid cell should go in.

However, I realized something. Grid cells are only ever affected by their direct parent element (ignoring display: contents). That meant that if the grid cell exists, it will always be a direct child of the grid template element. This gave me an idea for how we can solve the grid-area name conflict! 😁

.grid-alpha { grid-template-areas: "delta echo";
} .grid-beta { /* Uh oh, duplicate area names! */ grid-template-areas: "echo delta";
} .grid-cell { /* We will use the first occurrence by default */ -ms-grid-column: 2; grid-area: echo;
} /* We have detected a conflict! Prefix the class with the parent selector.
*/
.grid-beta > .grid-cell { /* NO MORE CONFLICT! */ -ms-grid-column: 1;
}

The entire grid-areas system needed to be re-written to achieve this, but it was totally worth the effort.

The way this new system works is that .grid-cell will default to using the first grid-template-areas property that it comes across. When it hits a conflicting grid template, it will create a new rule placing the parent selector in front of the child selector like so:

[full parent selector] > [direct child selector]

This is what we know purely from looking at the CSS and knowing how CSS Grid works:

  • A grid cell must be a direct descendant of the parent grid container for CSS Grid to work (assuming display: contents isn’t used).
  • grid-beta > .grid-cell will only work on .grid-cell elements that have been placed directly inside a .grid-beta element.
  • .grid-beta > .grid-cell will have no effect on .grid-cell elements placed directly inside .grid-alpha elements.
  • .grid-beta > .grid-cell will have no effect on .grid-cell elements nested deeply inside .grid-beta elements.
  • .grid-beta > .grid-cell will override the styling of the lonely .grid-cell CSS rule both because of rule order and specificity.

Because of those reasons, Autoprefixer can pretty safely resolve these conflicts without having any access to the DOM.

That last point in the list can be a little bit dangerous. It increases specificity which means that it may cause some IE styles to override others in a way that is different from how modern overrides are working. Since the generated rules only hold IE-specific grid styles, and they only apply under very specific circumstances, this is unlikely to cause an issue in 99.999% of circumstances. There is still potential for edge cases though.

If you ever find yourself needing to increase the specificity of the grid cell selector, here’s how to go about it.

Instead of writing this:

.grid { grid-template-areas: "area-name";
} .grid-cell { grid-area: area-name;
}

…we write the rule like this:

.grid { grid-template-areas: "area-name";
} .grid > .grid-cell { grid-area: area-name;
}

Autoprefixer will retain the selector and output something like this:

.grid { grid-template-areas: "area-name";
} .grid > .grid-cell { -ms-grid-column: 1; -ms-grid-row: 1; grid-area: area-name;
}

The exciting new possibilities!

So why is duplicate area name support so exciting?

Well, for one, it was just plain annoying when accidentally using a duplicate area name in older versions of Autoprefixer. It would break in IE and, in older versions of Autoprefixer, it would fail silently.

The main reason it is exciting though is that it opens up a whole new world of IE-friendly CSS Grid possibilities!

Use modifier classes to adjust a grid template

This was the use case that really made me want to get duplicate area name support into Autoprefixer. Think about this. You have a typical site with a header, a footer, a main area, and a sidebar down either side.

See the Pen Basic website layout by Daniel Tonon (@daniel-tonon) on CodePen.

Sometimes we want both sidebars, sometimes we want one sidebar, and sometimes we want no sidebars. This was very difficult to manage back when Autoprefixer didn’t support duplicate area names since we couldn’t share area names across multiple grid templates. We would have to do something like this for it to work:

/* Before Duplicate area names were supported - n = no side-bars - f = first sidebar only - s = second sidebar only - fs = first and second sidebars
*/
.main-area { display: grid; grid-template: "content-n" / 1fr;
} .main-area.has-first-sidebar { grid-template: "first-sb-f content-f" / 300px 1fr;
} .main-area.has-second-sidebar { grid-template: "content-s second-sb-s" / 1fr 300px;
}
.main-area.has-first-sidebar.has-second-sidebar { grid-template: "first-sb-fs content-fs second-sb-fs" / 200px 1fr 200px;
} .main-area > .content { grid-area: content-n; /* no side-bars */
} .main-area > .sidebar.first { grid-area: first-sb-f; /* first sidebar only */
} .main-area > .sidebar.second { grid-area: second-sb-s; /* second sidebar only */
} .main-area.has-first-sidebar > .content { grid-area: content-f; /* first sidebar only */
} .main-area.has-second-sidebar > .content { grid-area: content-s; /* second sidebar only */
} .main-area.has-first-sidebar.has-second-sidebar > .content { grid-area: content-fs; /* first and second sidebars */
} .main-area.has-first-sidebar.has-second-sidebar > .sidebar.first { grid-area: first-sb-fs; /* first and second sidebars */
} .main-area.has-first-sidebar.has-second-sidebar > .sidebar.second { grid-area: second-sb-fs; /* first and second sidebars */
}
Autoprefixer translation
/* Before Duplicate area names were supported - n = no sidebars - f = first sidebar only - s = second sidebar only - fs = first and second sidebars
*/ .main-area { display: -ms-grid; display: grid; -ms-grid-rows: auto; -ms-grid-columns: 1fr; grid-template: "content-n" / 1fr;
} .main-area.has-first-sidebar { -ms-grid-rows: auto; -ms-grid-columns: 300px 1fr; grid-template: "first-sb-f content-f" / 300px 1fr;
} .main-area.has-second-sidebar { -ms-grid-rows: auto; -ms-grid-columns: 1fr 300px; grid-template: "content-s second-sb-s" / 1fr 300px;
} .main-area.has-first-sidebar.has-second-sidebar { -ms-grid-rows: auto; -ms-grid-columns: 200px 1fr 200px; grid-template: "first-sb-fs content-fs second-sb-fs" / 200px 1fr 200px;
} .main-area > .content { -ms-grid-row: 1; -ms-grid-column: 1; grid-area: content-n; /* no side-bars */
} .main-area > .sidebar.first { -ms-grid-row: 1; -ms-grid-column: 1; grid-area: first-sb-f; /* first sidebar only */
} .main-area > .sidebar.second { -ms-grid-row: 1; -ms-grid-column: 2; grid-area: second-sb-s; /* second sidebar only */
} .main-area.has-first-sidebar > .content { -ms-grid-row: 1; -ms-grid-column: 2; grid-area: content-f; /* first sidebar only */
} .main-area.has-second-sidebar > .content { -ms-grid-row: 1; -ms-grid-column: 1; grid-area: content-s; /* second sidebar only */
} .main-area.has-first-sidebar.has-second-sidebar > .content { -ms-grid-row: 1; -ms-grid-column: 2; grid-area: content-fs; /* first and second sidebars */
} .main-area.has-first-sidebar.has-second-sidebar > .sidebar.first { -ms-grid-row: 1; -ms-grid-column: 1; grid-area: first-sb-fs; /* first and second sidebars */
} .main-area.has-first-sidebar.has-second-sidebar > .sidebar.second { -ms-grid-row: 1; -ms-grid-column: 3; grid-area: second-sb-fs; /* first and second sidebars */
}

🤢 Oh yeah, and don’t forget media queries! 🤮

That example was based off actual code that I had to write for an actual project… so yeah, I really wanted to get duplicate area name support into Autoprefixer.

Now that we do have duplicate area name support, we can simplify that code down to something much nicer 😊

/* Duplicate area names now supported! This code will work perfectly in IE with Autoprefixer 9.3.1
*/ .main-area { display: grid; grid-template: "content" / 1fr;
} .main-area.has-first-sidebar { grid-template: "first-sb content" / 300px 1fr;
} .main-area.has-second-sidebar { grid-template: "content second-sb" / 1fr 300px;
} .main-area.has-first-sidebar.has-second-sidebar { grid-template: "first-sb content second-sb" / 200px 1fr 200px;
} .content { grid-area: content;
} .sidebar.first { grid-area: first-sb;
} .sidebar.second { grid-area: second-sb;
}
Autoprefixer translation
.main-area { display: -ms-grid; display: grid; -ms-grid-rows: auto; -ms-grid-columns: 1fr; grid-template: "content" / 1fr;
} .main-area.has-first-sidebar { -ms-grid-rows: auto; -ms-grid-columns: 300px 1fr; grid-template: "first-sb content" / 300px 1fr;
} .main-area.has-second-sidebar { -ms-grid-rows: auto; -ms-grid-columns: 1fr 300px; grid-template: "content second-sb" / 1fr 300px;
} .main-area.has-first-sidebar.has-second-sidebar { -ms-grid-rows: auto; -ms-grid-columns: 200px 1fr 200px; grid-template: "first-sb content second-sb" / 200px 1fr 200px;
} .content { -ms-grid-row: 1; -ms-grid-column: 1; grid-area: content;
} .main-area.has-first-sidebar > .content { -ms-grid-row: 1; -ms-grid-column: 2;
} .main-area.has-second-sidebar > .content { -ms-grid-row: 1; -ms-grid-column: 1;
} .main-area.has-first-sidebar.has-second-sidebar > .content { -ms-grid-row: 1; -ms-grid-column: 2;
} .sidebar.first { -ms-grid-row: 1; -ms-grid-column: 1; grid-area: first-sb;
} .main-area.has-first-sidebar.has-second-sidebar > .sidebar.first { -ms-grid-row: 1; -ms-grid-column: 1;
} .sidebar.second { -ms-grid-row: 1; -ms-grid-column: 2; grid-area: second-sb;
} .main-area.has-first-sidebar.has-second-sidebar > .sidebar.second { -ms-grid-row: 1; -ms-grid-column: 3;
}

With that CSS, we are now able to use the has-first-sidebar and has-second-sidebar classes on the grid element to modify what grid template the browser uses. We no longer have to worry about defining exactly what grid grid cells are placed in.

Assign components reusable area names

One of the limitations of being forced to use unique area names was that components could not be given a single, consistent re-usable area name. We were forced to come up with unique area names for every grid.

Now that Autoprefixer has duplicate area name support, that limitation is gone. We can provide every component with a single area name that is based on their component name. We can then reference that one area name whenever we want to place that component inside a grid layout.

/* header.scss */
.header { grid-area: header;
} /* nav.scss */
.nav { grid-area: nav;
} /* sidebar.scss */
.sidebar { grid-area: sidebar;
} /* content.scss */
.content { grid-area: content;
} /* subscribe.scss */
.subscribe { grid-area: subscribe;
} /* footer.scss */
.footer { grid-area: footer;
} /* layout.scss */
.single-sidebar-layout { display: grid; grid-template-areas: "header header" "nav content" "subscribe content" "footer footer";
} .double-sidebar-layout { display: grid; grid-template-areas: "header header header" "nav content sidebar" "nav subscribe sidebar" "footer footer footer";
}
Autoprefixer translation
/* header.scss */
.header { -ms-grid-row: 1; -ms-grid-column: 1; -ms-grid-column-span: 2; grid-area: header;
} .double-sidebar-layout > .header { -ms-grid-row: 1; -ms-grid-column: 1; -ms-grid-column-span: 3;
} /* nav.scss */
.nav { -ms-grid-row: 2; -ms-grid-column: 1; grid-area: nav;
} .double-sidebar-layout > .nav { -ms-grid-row: 2; -ms-grid-row-span: 2; -ms-grid-column: 1;
} /* sidebar.scss */
.sidebar { -ms-grid-row: 2; -ms-grid-row-span: 2; -ms-grid-column: 3; grid-area: sidebar;
} /* content.scss */
.content { -ms-grid-row: 2; -ms-grid-row-span: 2; -ms-grid-column: 2; grid-area: content;
} .double-sidebar-layout > .content { -ms-grid-row: 2; -ms-grid-column: 2;
} /* subscribe.scss */
.subscribe { -ms-grid-row: 3; -ms-grid-column: 1; grid-area: subscribe;
} .double-sidebar-layout > .subscribe { -ms-grid-row: 3; -ms-grid-column: 2;
} /* footer.scss */
.footer { -ms-grid-row: 4; -ms-grid-column: 1; -ms-grid-column-span: 2; grid-area: footer;
} .double-sidebar-layout > .footer { -ms-grid-row: 4; -ms-grid-column: 1; -ms-grid-column-span: 3;
} /* layout.scss */
.single-sidebar-layout { display: -ms-grid; display: grid; grid-template-areas: "header header" "nav content" "subscribe content" "footer footer";
} .double-sidebar-layout { display: -ms-grid; display: grid; grid-template-areas: "header header header" "nav content sidebar" "nav subscribe sidebar" "footer footer footer";
}

Here’s what we’ve got. Note that this should be viewed in IE.

See the Pen component-area-name technique by Daniel Tonon (@daniel-tonon) on CodePen.

Duplicate area name limitation

There is one fairly common use case that Autoprefixer still can’t handle at the moment. When the parent selector of the grid cell does not match up with the grid template selector, it tries to resolve a duplicate area name:

.grand-parent .mother { grid-template-areas: "child";
} .grand-parent .father { grid-template-areas: "child child";
} /* This will work */
.grand-parent .mother .child { grid-area: child;
} /* This does not work because: - ".uncle" != ".grand-parent .mother" - ".uncle" != ".grand-parent .father" - "child" is a duplicate area name
*/
.uncle .child { grid-area: child;
}

Here is a more realistic scenario of the current limitation in Autoprefixer’s algorithm:

.component .grid { display: grid; grid-template-areas: "one two"; grid-template-columns: 1fr 1fr;
} /* This rule triggers duplicate area name conflicts. */
.component.modifier .grid { grid-template-areas: "one ... two"; grid-template-columns: 1fr 1fr 1fr;
} /* This does not work because: - ".component" != ".component .grid" - ".component" != ".component.modifier .grid" - area names "one" and "two" both have duplicate area name conflicts
*/
.component .cell-one { grid-area: one;
} .component .cell-two { grid-area: two;
}

There are really only three ways of resolving this conflict at the moment.

Option 1: Remove the parent selector from child elements

Without any safeguards in place for scoping styles to a particular component, this is by far the most dangerous way to resolve the issue. I don’t recommend it.

.component .grid { display: grid; grid-template-areas: "one two"; grid-template-columns: 1fr 1fr;
} .component.modifier .grid { grid-template-areas: "one ... two"; grid-template-columns: 1fr 1fr 1fr;
} .cell-one { grid-area: one;
} .cell-two { grid-area: two;
}
Autoprefixer translation
.component .grid { display: -ms-grid; display: grid; grid-template-areas: "one two"; -ms-grid-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} .component.modifier .grid { grid-template-areas: "one ... two"; -ms-grid-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr;
} .cell-one { -ms-grid-row: 1; -ms-grid-column: 1; grid-area: one;
} .component.modifier .grid > .cell-one { -ms-grid-row: 1; -ms-grid-column: 1;
} .cell-two { -ms-grid-row: 1; -ms-grid-column: 2; grid-area: two;
} .component.modifier .grid > .cell-two { -ms-grid-row: 1; -ms-grid-column: 3;
}

Option 2: Go back to using unique area names

This solution is pretty ugly, but if you have no control over the HTML, then this is probably the best way of handling the issue.

.component .grid { display: grid; grid-template-areas: "one two"; grid-template-columns: 1fr 1fr;
} .component .cell-one { grid-area: one;
} .component .cell-two { grid-area: two;
} .component.modifier .grid { grid-template-areas: "modifier_one ... modifier_two"; grid-template-columns: 1fr 1fr 1fr;
} .component.modifier .cell-one { grid-area: modifier_one;
} .component.modifier .cell-two { grid-area: modifier_two;
}
Autoprefixer translation
.component .grid { display: -ms-grid; display: grid; grid-template-areas: "one two"; -ms-grid-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} .component .cell-one { -ms-grid-row: 1; -ms-grid-column: 1; grid-area: one;
} .component .cell-two { -ms-grid-row: 1; -ms-grid-column: 2; grid-area: two;
} .component.modifier .grid { grid-template-areas: "modifier_one ... modifier_two"; -ms-grid-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr;
} .component.modifier .cell-one { -ms-grid-row: 1; -ms-grid-column: 1; grid-area: modifier_one;
} .component.modifier .cell-two { -ms-grid-row: 1; -ms-grid-column: 3; grid-area: modifier_two;
}

Option 3: Use a BEM-style naming convention

Yep, if you use the BEM naming convention (or something similar), this will practically never be an issue for you. This is easily the preferred way of dealing with the issue if it’s an option.

.component__grid { display: grid; grid-template-areas: "one two"; grid-template-columns: 1fr 1fr;
} .component__grid--modifier { grid-template-areas: "one ... two"; grid-template-columns: 1fr 1fr 1fr;
} .component__cell-one { grid-area: one;
} .component__cell-two { grid-area: two;
}
Autoprefixer translation
.component__grid { display: -ms-grid; display: grid; grid-template-areas: "one two"; -ms-grid-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
} .component__grid--modifier { grid-template-areas: "one ... two"; -ms-grid-columns: 1fr 1fr 1fr; grid-template-columns: 1fr 1fr 1fr;
} .component__cell-one { -ms-grid-row: 1; -ms-grid-column: 1; grid-area: one;
} .component__grid--modifier > .component__cell-one { -ms-grid-row: 1; -ms-grid-column: 1;
} .component__cell-two { -ms-grid-row: 1; -ms-grid-column: 2; grid-area: two;
} .component__grid--modifier > .component__cell-two { -ms-grid-row: 1; -ms-grid-column: 3;
}

I have some issues with BEM, but with a few tweaks to the syntax, it can be a really great way to control CSS specificity and scoping.

Other Autoprefixer updates

While I’m here, I’ll give you an update on a couple of other developments that have occurred in Autoprefixer since I wrote the original CSS Grid in IE series.

Before we dive in though, here is how to set things up in CodePen in case you want to follow along with your own experiments.

Using Autoprefixer Grid translations in CodePen

CodePen has already upgraded to version 9.3.1 of Autoprefixer. You will now easily be able to integrate IE-friendly CSS Grid styles into your pens. First, make sure that Autoprefixer is enabled in the CodePen settings (Settings > CSS > [Vendor Prefixing] > Autoprefixer).

Step 1: Turn on Autoprefixer in CodePen

Then add the all new /* autoprefixer grid: on */ control comment to the top of the CSS Panel. I will dive into this awesome new feature a bit more later.

Step 2: Add the control comment

You can now write modern IE-friendly CSS Grid code and CodePen will automatically add in all of the IE prefixes for you.

“View compiled CSS” button
The compiled CSS view

In order to actually test your pens in IE though, you will need to view the pen in “Debug Mode” (Change View > Debug Mode). The pen will need to be saved before you will have access to this view.

Enabling Debug Mode for testing in IE

If you’d like to try out a tool that shows you real-time Autoprefixer output, try the online tool just for that. Input CSS on the left. It will output Autoprefixer’s translated CSS on the right as you type.

autoprefixer.github.io

New Autoprefixer control comments

We don’t always have the ability to alter the Autoprefixer configuration settings. If you have ever tried to do some IE-friendly CSS Grid experiments in CodePen in the past, you will know the pain of not having direct access to the Autoprefixer settings. There are also some frameworks like Create React App and Angular that don’t allow users to alter the Autoprefixer settings. There was also a bit of a skill barrier that prevented some users from using Grid because they were unsure of how to enable Grid translations.

This limitation was causing pain for many users but then Andrey Alexandrov submitted a pull request that introduced a new control comment. This new control comment gave users the ability to easily turn grid translations on and off from inside the CSS file. This was far easier than doing it through configuration settings. It was also accessible to all users no matter how they compile their CSS.

Adding /* autoprefixer grid: on */ will enable grid translations for that entire block while /* autoprefixer grid: off */ will disable grid translations for that block. Only the first grid control comment in a block will be applied.

/* Globally enable grid prefixes */
/* autoprefixer grid: on */ .grid { display: grid;
} .non-ie .grid { /* Turn off grid prefixes but only for this block */ /* autoprefixer grid: off */ display: grid; /* Grid control comments affect the whole block. This control comment is ignored */ /* autoprefixer grid: on */ grid-column: 1;
}

The above code translates into the following (I’ve filtered out the explanation comments):

/* autoprefixer grid: on */ .grid { display: -ms-grid; display: grid;
} .non-ie .grid { /* autoprefixer grid: off */ display: grid; /* autoprefixer grid: on */ grid-column: 1;
}

@supports can disable grid translations

The new control comments aren’t the only way to selectively prevent Autoprefixer from outputting grid translation code.

Autoprefixer will never be able to support implicit grid auto-placement. If you use an @supports statement that checks for something like grid-auto-rows: 0, Autoprefixer will not output Grid translations for code entered inside that statement.

.prefixed .grid { display: -ms-grid; display: grid;
} /* Checking grid-auto-* support prevents prefixes */
@supports (grid-auto-rows: 0) { .modern .grid { display: grid; }
} /* Checking basic grid support still outputs prefixes */
@supports (display: grid) { .still-prefixed .grid { display: -ms-grid; display: grid; }
}

To support IE, Autoprefixer can generate something like 50 lines of extra CSS sometimes. If you are writing Grid code inside an @supports statement, you will want IE translations to be turned off to reduce the weight of your CSS. If you don’t want to use @supports (grid-auto-rows: 0) then you can use a control comment inside the @supports statement instead.

@supports (display: grid) { .prefixed .grid { display: -ms-grid; display: grid; }
} @supports (display: grid) { /* autoprefixer grid: off */ .modern .grid { display: grid; }
}

Autoprefixer now inherits grid-gaps

In the original article series, I said that Autoprefixer was unable to inherit grid-gap values. In version 9.1.1, grid-gap values were able to inherit through media queries. In version 9.3.1, they have become inheritable through more specific selectors as well.

.grid { display: grid; grid-gap: 20px; /* grid-gap is stated here */ grid-template: "one two" / 1fr 1fr;
} @media (max-width: 600px) { .grid { /* grid-gap is now inhereited here */ grid-template: "one" "two" / 1fr; }
} .grid.modifier { /* grid-gap is now inhereited here as well */ grid-template: "one" "two" / 1fr;
} .one { grid-area: one; }
.two { grid-area: two; }
Autoprefixer translation
.grid { display: -ms-grid; display: grid; grid-gap: 20px; /* grid-gap is stated here */ -ms-grid-rows: auto; -ms-grid-columns: 1fr 20px 1fr; grid-template: "one two" / 1fr 1fr;
} @media (max-width: 600px) { .grid { /* grid-gap is now inhereited here */ -ms-grid-rows: auto 20px auto; -ms-grid-columns: 1fr; grid-template: "one" "two" / 1fr; }
} .grid.modifier { /* grid-gap is now inherited here as well */ -ms-grid-rows: auto 20px auto; -ms-grid-columns: 1fr; grid-template: "one" "two" / 1fr;
} .one { -ms-grid-row: 1; -ms-grid-column: 1; grid-area: one;
} .grid.modifier > .one { -ms-grid-row: 1; -ms-grid-column: 1;
} .two { -ms-grid-row: 1; -ms-grid-column: 3; grid-area: two;
} .grid.modifier > .two { -ms-grid-row: 3; -ms-grid-column: 1;
} @media (max-width: 600px) { .one { -ms-grid-row: 1; -ms-grid-column: 1; } .two { -ms-grid-row: 3; -ms-grid-column: 1; }
}

grid-template: span X; now works

In Part 2 this series, I mentioned that the following syntax doesn’t work:

.grid-cell { grid-column: span 2;
}

This was fixed in version 9.1.1. It now translates into the following:

.grid-cell { -ms-grid-column-span: 2; grid-column: span 2;
}

New warning for mixing manual and area based placement

We had a user complain that Autoprefixer wasn’t handling grid areas correctly. Upon further investigation, we realized that they were applying both a grid-area and a grid-column setting within the same CSS rule.

.grid { display: grid; grid-template-areas: "a b";
} /* This doesn't work very well */
.grid-cell { grid-column: 2; grid-area: a;
}

Either use grid-area on its own, or us grid-column & grid-row. Never use both at the same time.

If you are curious, this is what Autoprefixer outputs for the above code:

.grid { display: -ms-grid; display: grid; grid-template-areas: "a b";
} /* This doesn't work very well */
.grid-cell { -ms-grid-row: 1; -ms-grid-column: 1; -ms-grid-column: 2; grid-column: 2; grid-area: a;
}

There is one exception to this. You may need a grid cell to overlap other grid cells in your design. You want to use grid-template-areas for placing your grid cells due to how much easier Autoprefixer makes the overall cell placement. You can’t really create cell overlaps using grid-template-areas though. To create this overlap, you can use grid-[column/row]-end: span X; after the grid-area declaration to force the overlap.

.grid { display: grid; grid-template-areas: "a . ." "a b b" "a . ."; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 1fr 1fr 1fr;
} .grid-cell-a { grid-area: a; /* place cell span after the grid-area to force an overlap */ grid-column-end: span 2;
}

See the Pen column-span + grid-area experiment by Daniel Tonon (@daniel-tonon) on CodePen.

However if you have declared grid gaps in your grid, you will need to write the column/row span prefix manually while taking the extra columns/rows generated by Autoprefixer into consideration (IE doesn’t support grid-gap). There is an issue for this on GitHub.

.grid { display: grid; grid-template-areas: "a . ." "a b c" "a . ."; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 1fr 1fr 1fr; grid-gap: 10px;
} .grid-cell-a { grid-area: a; /* IE column span added manually */ -ms-grid-column-span: 3; /* IE spans 2 columns + the gap */ grid-column-end: span 2; /* Modern browsers only span 2 columns */
}

See the Pen column-span + grid-area experiment 2 by Daniel Tonon (@daniel-tonon) on CodePen.

This technique currently produces a warning message in Autoprefixer that can’t easily be hidden. An issue for this is on GitHub.

New warning when using [align/justify/place]-[content/items]

Unfortunately. IE does not have the ability to align grid cells from the parent container. The only way to align grid cells in IE is by using the -ms-grid-row-align and the -ms-grid-column-align properties. These translate to align-self and justify-self in modern day grid syntax.

The following properties will trigger a warning in Autoprefixer if used in conjunction with a CSS Grid property:

  • align-items
  • align-content
  • justify-items
  • justify-content
  • place-items
  • place-content

The align-content, justify-content and place-content properties are pretty much impossible to replicate in IE. The align-items, justify-items and place-items properties, on the other hand, are quite easy to replicate using their self equivalents on the child elements (place-self support was added in 9.3.0).

/* [align/justify/place]-items is *NOT* IE friendly */ .align-justify-items { display: grid; align-items: start; justify-items: end;
} .place-items { display: grid; place-items: start end;
} /*[align/justify/place]-self *IS* IE friendly */ .align-justify-grid { display: grid; }
.align-justify-grid > * { align-self: start; justify-self: end;
} .place-grid { display: grid; }
.place-grid > * { place-self: start end;
}
Autoprefixer translation
/* [align/justify/place]-items is *NOT* IE friendly */ .align-justify-items { display: -ms-grid; display: grid; align-items: start; justify-items: end;
} .place-items { display: -ms-grid; display: grid; place-items: start end;
} /*[align/justify/place]-self *IS* IE friendly */ .align-justify-grid { display: -ms-grid; display: grid; }
.align-justify-grid > * { -ms-grid-row-align: start; align-self: start; -ms-grid-column-align: end; justify-self: end;
} .place-grid { display: -ms-grid; display: grid; }
.place-grid > * { -ms-grid-row-align: start; -ms-grid-column-align: end; place-self: start end;
}

.grid { [align/justify/place]-items } and .grid > * { [align/justify/place]-self } are generally quite interchangeable with one another. This substitution doesn’t always work, but in most cases, the two methods tend to act in much the same way.

Below is a pen demonstrating the difference between the IE friendly alignment method and the IE unfriendly alignment method. In modern browsers they look identical, but in IE one looks the same as the modern browsers and one does not.

See the Pen place-items experiment by Daniel Tonon (@daniel-tonon) on CodePen.

That’s all, folks

I hope you have enjoyed reading about the awesome new improvements the Autoprefixer community has made to the project over the past few months. The new control comments make enabling and disabling grid translations an absolute breeze. The new grid-areas system is also exciting. I love all the new IE-friendly CSS Grid options that the new areas system opens up, and I think you will too. 🙂

Article Series:

  1. Debunking common IE Grid misconceptions
  2. CSS Grid and the new Autoprefixer
  3. Faking an auto-placement grid with gaps
  4. Duplicate area names now supported! (This Post)

The post CSS Grid in IE: Duplicate area names now supported! appeared first on CSS-Tricks.

You might not need a loop

Ire Aderinokun has written a nifty piece using loops and when we might consider replacing it with another method, say .map() and .filter(). I particularly like what she has to say here:

As I mentioned earlier, loops are a great tool for a lot of cases, and the existence of these new methods doesn’t mean that loops shouldn’t be used at all.

I think these methods are great because they provide code that is in a way self-documenting. When we use the filter() method instead of a for loop, it is easier to understand at first glance what the purpose of the logic is.

However, these methods have very specific use cases and may be overkill if their full value isn’t being used. An example of this is the map() method, which can technically be used to replace almost any arbitrary loop. If in our first example, we only wanted to modify the original articles array and not create a new, modified, amazingArticles, using this method would be unnecessary. It’s important to use the method that suits each scenario, to make sure that we aren’t over- or under-performing.

If you’re interested in digging more into this subject, Adan Giese wrote a great post about the .filter() method a short while ago that’s definitely worth checking out. Oh, and speaking of lots of different ways to approach loops, Chris compiled a list of options for looping over querySelectorAll NodeLists where forEach is just one of many options.

Direct Link to ArticlePermalink

The post You might not need a loop appeared first on CSS-Tricks.

The Current State of Styling Scrollbars

If you need to style your scrollbars right now, one option is to use a collection of ::webkit prefixed CSS properties.

See the Pen CSS-Tricks Almanac: Scrollbars by Chris Coyier (@chriscoyier) on CodePen.

Sadly, that doesn’t help out much for Firefox or Edge, or the ecosystem of browsers around those.

But if that’s good enough for what you need, you can get rather classy with it:

See the Pen Custom Scrollbar styling by Devstreak (@devstreak) on CodePen.

There are loads of them on CodePen to browse. It’s a nice thing to abstract with a Sass @mixin as well.

There is good news on this front! The standards bodies that be have moved toward a standardizing methods to style scrollbars, starting with the gutter (or width) of them. The main property will be scrollbar-gutter and Geoff has written it up here. Hopefully Autoprefixer will help us as the spec is finalized and browsers start to implement it so we can start writing the standardized version and get any prefixed versions from that.

But what if we need cross-browser support?

Continue reading The Current State of Styling Scrollbars

State of Houdini (Chrome Dev Summit 2018)

Here’s a great talk by Das Surma where he looks into what Houdini is and how much of it is implemented in browsers. If you’re unfamiliar with that, Houdini is a series of technologies and APIs that gives developers low level access to how CSS properties work in a fundamental way. Check out Ana Tudor’s deep dive into its impact on animations for some incredible examples of it in practice.

What I particularly like about this video is the way Das mentions the CSS Paint API which lets you do a bunch of bizarre things with CSS, such as creating “squircle” shapes and changing how borders work. It looks wonderfully robust and it should give us super powers in the near future. Ruth John wrote up this extensive overview on the topic earlier this year and it’s worth a read as well.

Direct Link to ArticlePermalink

The post State of Houdini (Chrome Dev Summit 2018) appeared first on CSS-Tricks.

Add Instant awesomeness to your interfaces with this insanely large icon set

(This is a sponsored post.)

When you need to add icons to your interface, the whole process can really suck. “Should I use all these default bootstrap icons? Maybe I’ll just use the same Google Material icons for the hundredth time?”

Some important yet often overlooked things to consider when choosing an icon set includes, the size of the icons, style, consistency, and quantity. It’s frustrating to find icons that only cover half of the use cases you need.

We constantly felt this frustration too and decided to do something about it. This ultimately led to creating Streamline icon set.

Now in version 3.0, Streamline contains a whopping 30,000 icons in three distinct weights, similar to a font family. There are tons of options to pick the perfect icon for any interface you’re working with, whether it’s a big web application, documentation, or a marketing site.

“I own several icon sets but the one that I return to over and over is the copious Streamline pack, which almost always seems to have just the pictogram I need when I dig into its catalog.”

—Khoi Vinh, Adobe

Easy to Use

Streamline has also been meticulously organized into easy-to-navigate categories. You can see all of the categories in our handy dandy web-based icon viewer.

If you’re an IconJar user, you can also search for icons by name and drag and drop them into your project folder. We’re currently under development on this functionality for our web viewer too.

Every Streamline Icon pack comes with the following file types: .svg, .iconjar, .sketch, .fig, .ai, .pdf, .png, .xd.

So now matter how you like to work with icons, you have the file types you need.

“Streamline 3.0 is one of the most versatile and detailed icon packs I’ve ever used. The structure and hierarchy make it super easy to work with. This is an amazing product. Bravo, Vincent.”

—Stephanie Walter, UX & UI Designer

Optimized SVG

The SVG versions of Streamline is already dev-ready with proper viewbox tags in place and currentColor set as the color properties for all strokes and fills. You can pop in Streamline using your favorite SVG technique and start changing the color of the icons with CSS right out of the gate.

See the Pen QJQjMm by Matt D. Smith (@mds) on CodePen.

Weights

Every weight—light, regular, and bold—was designed with a very consistent style to give you tons of consistency within your interface.

Light

The classic Streamline style with bits of detail here and there. Designed with 1px stroke on a 24px grid. The Light icons are great for interfaces that need lots of fun personality. They also work well scaled up to 48px as small illustrations.

Regular

A new minimal and geometric style. Designed with a 1.5px stroke on a 24px grid. These are perfect to use on clean and modern web interfaces.

Bold

A new solid style akin to the latest iOS guidelines. Designed with fills and a 2px stroke on a 24px grid. The bold style gives a little more punch for an iOS style interface.

Put Streamline to work for you

There are two different package types available—Essential and Ultimate.

Essential contains 14 categories all related to interfaces and web design, whereas the Ultimate pack contains all 53 categories, including things like Pets, Weather, Finance, Outdoors, Transportation, and so much more.

👉 Check out the Streamline site to soak in all of the icon glory.

“Vincent’s icons are unique, versatile, and easy to work with. I’ve found them to be super useful across a range of projects.”

—Daniel Burka, Resolve to Save Lives

🤓 Some nerdy facts about the Streamline site:

  • Initials designs created in Figma
  • Coded from scratch with .pug, .sass, and .js
  • CodeKit for compiling all of the code
  • Grunt to create a sprite with all of the SVG assets
  • Animations created in After Effects, exported from AE with Lottie into an icon-animation.json format, and added to the site using bodymovin.js
  • Scrollmagic.js was used to manipulate the DOM based on scroll positions for the large icon parallax sections
  • jQuery.js was used to make our lives easier since we’re building a marketing site and not a full-scale application

Direct Link to ArticlePermalink

The post Add Instant awesomeness to your interfaces with this insanely large icon set appeared first on CSS-Tricks.

Swipeable card stack using Vue.js and interact.js

I recently had an opportunity to work on a fantastic research and development project at Netguru. The goal of project (codename: “Wordguru”) was to create a card game that anyone can play with their friends. You can see the outcome here.

One element of the development process was to create an interactive card stack. The card stack had a set of requirements, including:

  • It should contain a few cards from the collection.
  • The first card should be interactive.
  • The user should be able to swipe the card in different directions that indicate an intent to accept, reject or skip the card.

This article will explain how to create that and make it interactive using Vue.js and interact.js. I created an example for you to refer to as we go through the process of creating a component that is in charge of displaying that card stack and a second component that is responsible for rendering a single card and managing user interactions in it.

View Demo

Step 1: Create the GameCard component in Vue

Let’s start by creating a component that will show a card, but without any interactions just yet. We’ll call this file GameCard.vue and, in the component template, we’ll render a card wrapper and the keyword for a specific card. This is the file we’ll be working in throughout this post.

// GameCard.vue
<template> <div class="card" :class="{ isCurrent: isCurrent }" > <h3 class="cardTitle">{{ card.keyword }}</h3> </div>
</template>

In the script section of the component, we receive the prop card that contains our card content as well as an isCurrent prop that gives the card a distinct look when needed.

export default { props: { card: { type: Object, required: true }, isCurrent: { type: Boolean, required: true } }
},

Step 2: Create the GameCardStack component in Vue

Now that we have a single card, let’s create our card stack.

This component will receive an array of cards and render the GameCard for each card. It’s also going to mark the first card as the current card in the stack so a special styling is applied to it.

// GameCardsStack.vue
<template> <div class="cards"> <GameCard v-for="(card, index) in cards" :key="card" :card="card" :is-current="index === 0" /> </div>
</template> <script> import GameCard from "@/components/GameCard"; export default { components: { GameCard }, props: { cards: { type: Array, required: true } } };
</script>

Here’s what we’re looking at so far, using the styles pulled from the demo:

At this point, our card looks complete, but isn’t very interactive. Let’s fix that in the next step!

Step 3: Add interactivity to GameCard component

All our interactivity logic will live in the GameCard component. Let’s start by allowing the user to drag the card. We will use interact.js to deal with dragging.

We’ll set the interactPosition initial values to 0 in the script section. These are the values that indicate a card’s order in the stack when it’s moved from its original position.

<script>
import interact from "interact.js"; data() { return { interactPosition: { x: 0, y: 0 }, };
},
// ...
</script>

Next, we create a computed property that’s responsible for creating a transform value that’s applied to our card element.

// ...
computed: { transformString() { const { x, y } = this.interactPosition; return `translate3D(${x}px, ${y}px, 0)`; }
},
// ...

In the mounted lifecycle hook, we make use of the interact.js and its draggable method. That method allows us to fire a custom function each time the element is dragged (onmove). It also exposes an event object that carries information about how far the element is dragged from its original position. Each time user drags the card, we calculate a new position of the card and set it on the interactPosition property. That triggers our transformString computed property and sets new value of transform on our card.

We use the interact onend hook that allows us to listen when the user releases the mouse and finishes the drag. At this point, we will reset the position of our card and bring it back to its original position: { x: 0, y: 0 }.

We also need to make sure to remove the card element from the Interactable object before it gets destroyed. We do that in the beforeDestroy lifecycle hook by using interact(target).unset(). That removes all event listeners and makes interact.js completely forget about the target.

// ...
mounted() { const element = this.$refs.interactElement; interact(element).draggable({ onmove: event => { const x = this.interactPosition.x + event.dx; const y = this.interactPosition.y + event.dy; this.interactSetPosition({ x, y }); }, onend: () => { this.resetCardPosition(); } });
},
// ...
beforeDestroy() { interact(this.$refs.interactElement).unset();
},
// ...
methods: { interactSetPosition(coordinates) { const { x = 0, y = 0 } = coordinates; this.interactPosition = {x, y }; }, resetCardPosition() { this.interactSetPosition({ x: 0, y: 0 }); },
},
// ...

We need to add one thing in our template to make this work. As our transformString computed property returns a string, we need to apply it to the card component. We do that by binding to the :style attribute and then passing the string to the transform property.

<template> <div class="card" :class="{ isCurrent: isCurrent }" :style="{ transform: transformString }" > <h3 class="cardTitle">{{ card.keyword }}</h3> </div>
</template>

With that done, we have created interaction with our card — we can drag it around!

You may have noticed that the behavior isn’t very natural, specifically when we drag the card and release it. The card immediately returns to its original position, but it would be more natural if the card would go back to initial position with animation to smooth the transition.

That’s where transition comes into play! But adding it to our card introduces another issue: there’s a lag in the card following as it follows the cursor because transition is applied to the element at all times. We only want it applied when the drag ends. We can do that by binding one more class (isAnimating) to the component.

<template> <div class="card" :class="{ isAnimating: isInteractAnimating, isCurrent: isCurrent }" > <h3 class="cardTitle">{{ card.keyword }}</h3> </div>
</template>

We can add and remove the animation class by changing the isInteractAnimating property.

The animation effect should be applied initially and we do that by setting our property in data.

In the mounted hook where we initialize interact.js, we use one more interact hook (onstart) and change the value of isInteractAnimating to false so that the animation is disabled when the during the drag.

We’ll enable the animation again in the onend hook, and that will make our card animate smoothly to its original position when we release it from the drag.

We also need to update transformString computed property and add a guard to recalculate and return a string only when we are dragging the card.

data() { return { // ... isInteractAnimating: true, // ... };
}, computed: { transformString() { if (!this.isInteractAnimating) { const { x, y } = this.interactPosition; return `translate3D(${x}px, ${y}px, 0)`; } return null; }
}, mounted() { const element = this.$refs.interactElement; interact(element).draggable({ onstart: () => { this.isInteractAnimating = false; }, // ... onend: () => { this.isInteractAnimating = true; }, });
},

Now things are starting to look nice!

Our card stack is ready for second set of interactions. We can drag the card around, but nothing is actually happening — the card is always coming back to its original place, but there is no way to get to the second card.

This will change when we add logic that allows the user to accept and rejecting cards.

Step 4: Detect when the card is accepted, rejected, or skipped

The card has three types of interactions:

  • Accept card (on swipe right)
  • Reject card (on swipe left)
  • Skip card (on swipe down)

We need to find a place where we can detect if the card was dragged from its initial position. We also want to be sure that this check will happen only when we finish dragging the card so the interactions do not conflict with the animation we just finished.

We used that place earlier smooth the transition during animation — it’s the onend hook provided by the interact.draggable method.

Let’s jump into the code.

First, we need to store our threshold values. Those values are the distances as the card is dragged from its original position and allows us to determine if the card should be accepted, rejected, or skipped. We use X axis for right (accept) and left (reject), then use the Y axis for downward movement (skip).

We also set coordinates where we want to place a card after it gets accepted, rejected or skipped (coordinates out of user’s sight).

Since those values will not change, we will keep them in the static property of our component, which can be accessed with this.$options.static.interactYThreshold.

export default { static: { interactYThreshold: 150, interactXThreshold: 100 },

We need to check if any of our thresholds were met in our onend hook and then fire the appropriate method that happened. If no threshold is met, then we reset the card to its initial position.

mounted() { const element = this.$refs.interactElement; interact(element).draggable({ onstart: () => {...}, onmove: () => {...}, onend: () => { const { x, y } = this.interactPosition; const { interactXThreshold, interactYThreshold } = this.$options.static; this.isInteractAnimating = true; if (x > interactXThreshold) this.playCard(ACCEPT_CARD); else if (x < -interactXThreshold) this.playCard(REJECT_CARD); else if (y > interactYThreshold) this.playCard(SKIP_CARD); else this.resetCardPosition(); } });
}

OK, now we need to create a playCard method that’s responsible for handling those interactive actions.

Step 5: Establish the logic to accept, reject, and skip cards

We will create a method that accepts a parameter telling us the user’s intended action. Depending on that parameter, we will set the final position of the current card and emit the accept, reject, or skip event. Let’s go step by step.

First, our playCard method will remove the card element from the Interactable object so that it stops tracking drag events. We do that by using interact(target).unset().
Secondly, we set the final position of the active card depending on the user’s intention. That new position allows us to animate the card and remove it from the user’s view.

Next, we emit an event up to the parent component so we can deal with our cards (e.g. change the current card, load more cards, shuffle the cards, etc.). We want to follow the DDAU principle that states a component should refrain from mutating data it doesn’t own. Since our cards are passed down to our component, it should emit an event up to the place from where those cards come.

Lastly, we hide the card that was just played and add a timeout that allow the card to animate out of view.

methods: { playCard(interaction) { const { interactOutOfSightXCoordinate, interactOutOfSightYCoordinate, } = this.$options.static; this.interactUnsetElement(); switch (interaction) { case ACCEPT_CARD: this.interactSetPosition({ x: interactOutOfSightXCoordinate, }); this.$emit(ACCEPT_CARD); break; case REJECT_CARD: this.interactSetPosition({ x: -interactOutOfSightXCoordinate, }); this.$emit(REJECT_CARD); break; case SKIP_CARD: this.interactSetPosition({ y: interactOutOfSightYCoordinate }); this.$emit(SKIP_CARD); break; } this.hideCard(); }, hideCard() { setTimeout(() => { this.isShowing = false; this.$emit("hideCard", this.card); }, 300); }, interactUnsetElement() { interact(this.$refs.interactElement).unset(); this.interactDragged = true; },
}

And, there we go!

Summary

Let’s recap what we just accomplished:

  • First we created a component for a single card.
  • Next we created another component that renders the cards in a stack.
  • Thirdly, we implemented interact.js to allow interactive dragging.
  • Then we detected when the user wants takes an action with the current card.
  • Finally, we established the to handle those actions.

Phew, we covered a lot! Hopefully this gives you both a new trick in your toolbox as well as a hands-on use case for Vue. And, if you’ve ever had to build something similar, please share in the comments because it would be neat to compare notes.

The post Swipeable card stack using Vue.js and interact.js appeared first on CSS-Tricks.

Exclusions will hopefully be like more powerful grid-friendly floats

Exclusions (which are currently in a “working draft” spec as I write) are kinda like float in that they allow inline content to wrap around an element. But not exactly a float. Chen Hui Jing has an excellent explanation:

An exclusion element is a block-level element which is not a float, and generates an exclusion box. An exclusion element establishes a new block formatting context.

An element becomes an exclusion when its wrap-flow property is computed to something other than its initial value of auto. When an element becomes an exclusion, inline content will wrap around the exclusion areas, but within their own formatting contexts.

Source: Chen’s article

Support is limited to Edge and IE (again, as I write):

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Opera Firefox IE Edge Safari
No No No 10* 12* No

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
No No No No No No

Chen makes a great case for why they are useful, but another round of discussion has cropped up lately as well. Rob Weychert documents a simple layout situation in which an image is floated left and text is wrapping around it:

Source: Rob’s article

As those light red bars indicate, Rob has set up some display: grid; columns to align elements in the article to those axes. A classic “editorial layout” indeed. But there really is no good mechanism to place that image onto the grid and maintain the wrapping. By placing both the content and the image into separate grid items, you don’t get the wrapping. You can use float, but that’s not using the grid.

Rachel Andrew chimed in that the answer is CSS exclusions. While Rob’s example ultimately had to use floats, Rachel re-did it with exclusions. Exclusions make the code much simpler.

/* with floats, replicating exactly what the grid is doing */
img { float: left; width: calc( 3 * ((100% - 50px - 15em) / 6) + 50px + 2em );
} /* with exclusions, using the grid */
img { grid-row: 2; grid-column: 1 / 3; -ms-wrap-flow: both;
}

Perhaps we can chime in with thumbs up on Rachel’s call to see what’s up with the status of the spec and with other author use cases.

The post Exclusions will hopefully be like more powerful grid-friendly floats appeared first on CSS-Tricks.

Prettier & Beautify

Aww, what a cute blog post title, right?

Prettier is an “opinionated code formatter.” I highly suggest using it. They have a document going deeper into the reasons, but their three marketing bullet points say it best:

  • You press save and code is formatted
  • No need to discuss style in code review
  • Saves you time and energy

But Prettier doesn’t do all languages. Notably HTML. It’s great at JSX, and I’ve gotten really used to enjoying that. But then when I switch to a Rails .erb template or a WordPress site where I’m editing a .php file, or even a plain ol’ .html file… no Prettier.

Continue reading Prettier & Beautify

Push and ye shall receive

Sometimes the seesaw of web tech is fascinating. Service workers have arrived, and beyond offline networking (read Jeremy’s book) which is possibly their best feature, they can enable push notifications via the Push API.

I totally get the push (pun intended) to make that happen. There is an omnipresent sentiment that we want the web to win, as there should be in this industry. Losing on the web means losing to native apps on all the different platforms out there. Native apps aren’t evil or anything — they are merely competitive and exclusionary in a way the web isn’t. Making the web a viable platform for any type of “app” is a win for us and a win for humans.

One of the things native apps do well is push notifications which gives them a competitive advantage. Some developers choose native for stuff like that. But now that we actually have them on the web, there is pushback from the community and even from the browsers themselves. Firefox supports them, then rolled out a user setting to entirely block them.

We’re seeing articles like Moses Kim’s Don’t @ me:

Push notifications are a classic example of good UX intentions gone bad because we know no bounds.

Very few people are singing the praises of push notifications. And yet! Jeremy Keith wrote up a great experiment by Sebastiaan Andeweg. Rather than an obnoxious and intrusive push notification…

Here’s what Sebastiaan wanted to investigate: what if that last step weren’t so intrusive? Here’s the alternate flow he wanted to test:

  1. A website prompts the user for permission to send push notifications.
  2. The user grants permission.
  3. A whole lot of complicated stuff happens behinds the scenes.
  4. Next time the website publishes something relevant, it fires a push message containing the details of the new URL.
  5. The user’s service worker receives the push message (even if the site isn’t open).
  6. The service worker fetches the contents of the URL provided in the push message and caches the page. Silently.

It worked.

Imagine a PWA podcast app that works offline and silently receives and caches new podcasts. Sweet. Now we need a permissions model that allows for silent notifications.

The post Push and ye shall receive appeared first on CSS-Tricks.

Why can’t we use Functional CSS and regular CSS at the same time?

Harry Nicholls recently wrote all about simplifying styles with functional CSS and you should definitely check it out. In short, functional CSS is another name for atomic CSS or using “helper” or “utility” classes that would just handle padding or margin, background-color or color, for example.

Harry completely adores the use of adding multiple classes like this to an element:

So what I’m trying to advocate here is taking advantage of the work that others have done in building functional CSS libraries. They’re built on solid foundations in design, people have spent many hours thinking about how these libraries should be built, and what the most useful classes will be.

And it’s not just the classes that are useful, but the fundamental design principles behind Tachyons.

This makes a ton of sense to me. However, Chris notes that he hasn’t heard much about the downsides of a functional/atomic CSS approach:

What happens with big redesigns? Is it about the same, time- and difficulty-wise, or do you spend more time tearing down all those classes? What happens when you need a style that isn’t available? Write your own? Or does that ruin the spirit of all this and put you in dangerous territory? How intense can all the class names get? I can think of areas I’ve styled that have three or more media queries that dramatically re-style an element. Putting all that information in HTML seems like it could get awfully messy. Is consistency harder or easier?

This also makes a ton of sense to me, but here’s the thing: I’m a big fan of both methods and even combine them in the same projects.

Before you get mad, hear me out

At Gusto, the company I work for today, I’ve been trying to design a system that uses both methods because I honestly believe that they can live in harmony with one another. Each solve very different use cases for writing CSS.

Here’s an example: let’s imagine we’re working in a big ol’ React web app and our designer has handed off a page design where a paragraph and a button need more spacing beneath them. Our code looks like this:

<p>Item 1 description goes here</p>
<Button>Checkout item</Button>

This is just the sort of problem for functional CSS to tackle. At Gusto, we would do something like this:

<div class="margin-bottom-20px"> <p>Item 1 description goes here</p> <button>Checkout item</button>
</div>

In other words, we use functional classes to make layout adjustments that might be specific to a particular feature that we’re working on. However! That Button component is made up of a regular ol’ CSS file. In btn.scss, we have code like this which is then imported into our btn.jsx component:

.btn { padding: 10px 15px; margin: 0 15px 10px; // rest of the styles go here
}

I think making brand new CSS files for custom components is way easier than trying to make these components out of a ton of classes like margin-*, padding-*, etc. Although, we could be using functional styles in our btn.jsx component instead like this:

const Button = ({ onClick, className, children }) => { return ( <button className='padding-top-10px padding-bottom-10px padding-left-15px padding-right-15px margin-bottom-none margin-right-15px margin-left-15px margin-bottom-10px ${className}')} onClick={onClick} > {children} </button> );
};

This isn’t a realistic example because we’re only dealing with two properties and we’d probably want to be styling this button’s background color, text color, hover states, etc. And, yes, I know these class names are a little convoluted but I think my point still stands even if you combine vertical and horizontal classes together.

So I reckon that we solve the following three issues with functional CSS by writing our custom styles in a separate CSS file for this particular instance:

  1. Readability
  2. Managing property dependencies
  3. Avoiding the painful fact that visual design doesn’t like math

As you can see in the earlier code example, it’s pretty difficult to read and immediately see which classes have been applied to the button. More classes means more difficulty to scan.

Secondly, a lot of CSS property/value pairs are written in relation to one another. Say, for example, position: relative and position: absolute. In our stylesheets, I want to be able to see these dependencies and I believe it’s harder to do that with functional CSS. CSS often depends on other bits of CSS and it’s important to see those connections with comments or groupings of properties/values.

And, finally, visual design is an issue. A lot of visual design requires imperfect numbers that don’t properly scale. With a functional CSS system, you’ll probably want a system of base 10, or base 8, where each value is based on that scale. But when you’re aligning items together visually, you may need to do so in a way that it won’t align to those values. This is called optical adjustment and it’s because our brains are, well, super weird. What makes sense mathematically often doesn’t visually. So, in this case, we’d need to add more bottom padding to the button to make the text feel like it’s positioned in the center. With a functional CSS approach it’s harder to do stuff like that neatly, at least in my experience.

In those cases where you need to balance readability, dependencies, and optical adjustments, writing regular CSS in a regular old-fashioned stylesheet is still my favorite thing in the world. But functional CSS still solves a ton of other problems very eloquently.

For example, what we’re trying to prevent with functional classes at Gusto is creating tons of stylesheets that do a ton of very specific or custom stuff. Going back to that earlier example with the margin beneath those two elements for a second:

<div className='margin-bottom-20px'> <p>Item 1 description goes here</p> <Button>Checkout item</Button>
</div>

In the past our teams might have written something like this instead:

<div className='cool-feature-description-wrapper'> <p>Item 1 description goes here</p> <button>Checkout item</button>
</div>

A new CSS file called cool_feature_description_wrapper.scss would need to be created in our application like so:

.cool-feature-description-wrapper { margin-bottom: 20px;
}

I would argue that styles like this make our code harder to understand, harder to read, and encourages diversions from our library of components. By replacing this with a class from our library of functional classes, it’s suddenly much easier to read, and to change in the future. It also solves a custom solution for our particular needs without forking our library of styles.

So, I haven’t read much about balancing both approaches this way, although I assume someone has covered this in depth already. I truly believe that a combination of these two methods is much more useful than trying to solve all problems with a single bag of tricks.

I know, right? Nuanced opinions are the worst.

The post Why can’t we use Functional CSS and regular CSS at the same time? appeared first on CSS-Tricks.