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.

Using feature detection to write CSS with cross-browser support

In early 2017, I presented a couple of workshops on the topic of CSS feature detection, titled CSS Feature Detection in 2017.

A friend of mine, Justin Slack from New Media Labs, recently sent me a link to the phenomenal Feature Query Manager extension (available for both Chrome and Firefox), by Nigerian developer Ire Aderinokun. This seemed to be a perfect addition to my workshop material on the subject.

However, upon returning to the material, I realized how much my work on the subject has aged in the last 18 months.

The CSS landscape has undergone some tectonic shifts:

  • The Atomic CSS approach, although widely hated at first, has gained some traction through libraries like Tailwind, and perhaps influenced the addition of several new utility classes to Bootstrap 4.
  • CSS-in-JS exploded in popularity, with Styled Components at the forefront of the movement.
  • The CSS Grid Layout spec has been adopted by browser vendors with surprising speed, and was almost immediately sanctioned as production ready.

The above prompted me to not only revisit my existing material, but also ponder the state of CSS feature detection in the upcoming 18 months.

In short:

  1. ❓ Why do we need CSS feature detection at all?
  2. 🛠️ What are good (and not so good) ways to do feature detection?
  3. 🤖 What does the future hold for CSS feature detection?

Cross-browser compatible CSS

When working with CSS, it seems that one of the top concerns always ends up being inconsistent feature support among browsers. This means that CSS styling might look perfect on my browsers of choice, but might be completely broken on another (perhaps an even more popular) browser.

Luckily, dealing with inconsistent browser support is trivial due to a key feature in the design of the CSS language itself. This behavior, called fault tolerance, means that browsers ignore CSS code they don’t understand. This is in stark contrast to languages like JavaScript or PHP that stop all execution in order to throw an error.

The critical implication here is that if we layer our CSS accordingly, properties will only be applied if the browser understands what they mean. As an example, you can include the following CSS rule and the browser will just ignore it— overriding the initial yellow color, but ignoring the third nonsensical value:

background-color: yellow;
background-color: blue; /* Overrides yellow */
background-color: aqy8godf857wqe6igrf7i6dsgkv; /* Ignored */

To illustrate how this can be used in practice, let me start with a contrived, but straightforward situation:

A client comes to you with a strong desire to include a call-to-action (in the form of a popup) on his homepage. With your amazing front-end skills, you are able to quickly produce the most obnoxious pop-up message known to man:

Unfortunately, it turns out that his wife has an old Windows XP machine running Internet Explorer 8. You’re shocked to learn that what she sees no longer resembles a popup in any shape or form.

But! We remember that by using the magic of CSS fault tolerance, we can remedy the situation. We identify all the mission-critical parts of the styling (e.g., the shadow is nice to have, but does not add anything useful usability-wise) and buffer prepend all core styling with fallbacks.

This means that our CSS now looks something like the following (the overrides are highlighted for clarity):

.overlay { background: grey; background: rgba(0, 0, 0, 0.4); border: 1px solid grey; border: 1px solid rgba(0, 0, 0, 0.4); padding: 64px; padding: 4rem; display: block; display: flex; justify-content: center; /* if flex is supported */ align-items: center; /* if flex is supported */ height: 100%; width: 100%;
} .popup { background: white; background-color: rgba(255, 255, 255, 1); border-radius: 8px; border: 1px solid grey; border: 1px solid rgba(0, 0, 0, 0.4); box-shadow: 0 7px 8px -4px rgba(0,0, 0, 0.2), 0 13px 19px 2px rgba(0, 0, 0, 0.14), 0 5px 24px 4px rgba(0, 0, 0, 0.12); padding: 32px; padding: 2rem; min-width: 240px;
} button { background-color: #e0e1e2; background-color: rgba(225, 225, 225, 1); border-width: 0; border-radius: 4px; border-radius: 0.25rem; box-shadow: 0 1px 3px 0 rgba(0,0,0,.2), 0 1px 1px 0 rgba(0,0,0,.14), 0 2px 1px -1px rgba(0,0,0,.12); color: #5c5c5c; color: rgba(95, 95, 95, 1); cursor: pointer; font-weight: bold; font-weight: 700; padding: 16px; padding: 1rem;
} button:hover { background-color: #c8c8c8; background-color: rgb(200,200,200); }

The above example generally falls under the broader approach of Progressive Enhancement. If you’re interested in learning more about Progressive Enhancement check out Aaron Gustafson’s second edition of his stellar book on the subject, titled Adaptive Web Design: Crafting Rich Experiences with Progressive Enhancement (2016).

If you’re new to front-end development, you might wonder how on earth does one know the support level of specific CSS properties. The short answer is that the more you work with CSS, the more you will learn these by heart. However, there are a couple of tools that are able to help us along the way:

  • Can I Use is a widely used directory that contains searchable, up to date support matrices for all CSS features.
  • Stylelint has a phenomenal plugin-called called No Unsupported Browser Features that gives scours errors for unsupported CSS (defined via Browserslist) either in your editor itself or via a terminal command.
  • There are several tools like BrowserStack or Cross Browser Testing that allow you to remotely test your website on different browsers. Note that these are paid services, although BrowserStack has a free tier for open source projects.

Even with all the above at our disposal, learning CSS support by heart will help us plan our styling up front and increase our efficiency when writing it.

Limits of CSS fault tolerance

The next week, your client returns with a new request. He wants to gather some feedback from users on the earlier changes that were made to the homepage—again, with a pop-up:

Once again it will look as follows in Internet Explorer 8:

Being more proactive this time, you use your new fallback skills to establish a base level of styling that works on Internet Explorer 8 and progressive styling for everything else. Unfortunately, we still run into a problem…

In order to replace the default radio buttons with ASCII hearts, we use the ::before pseudo-element. However this pseudo-element is not supported in Internet Explorer 8. This means that the heart icon does not render; however the display: none property on the <input type="radio"> element still triggers on Internet Explorer 8. The implication being that neither the replacement behavior nor the default behavior is shown.

Credit to John Faulds for pointing out that it is actually possible to get the ‘::before’ pseudo-element to work in Internet Explorer 8 if you replace the official double colon syntax with a single colon.

In short, we have a rule (display: none) whose execution should not be bound to its own support (and thus its own fallback structure), but to the support level of a completely separate CSS feature (::before).

For all intents and purposes, the common approach is to explore whether there are more straightforward solutions that do not rely on ::before. However, for the sake of this example, let’s say that the above solution is non-negotiable (and sometimes they are).

Enter User Agent Detection

A solution might be to determine what browser the user is using and then only apply display: none if their browser supports the ::before pseudo-element.

In fact, this approach is almost as old as the web itself. It is known as User Agent Detection or, more colloquially, browser sniffing.

It is usually done as follows:

  • All browsers add a JavaScript property on the global window object called navigator and this object contains a userAgent string property.
  • In my case, the userAgent string is: Mozilla/5.0 (Windows NT10.0;Win64;x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.9 Safari/537.36.
  • Mozilla Developer Network has a comprehensive list of how the above can be used to determine the browser.
  • If we are using Chrome, then the following should return true: (navigator.userAgent.indexOf("chrome") !== -1).
  • However, under the Internet Explorer section on MDN, we just get Internet Explorer. IE doesn’t put its name in the BrowserName/VersionNumber format.
  • Luckily, Internet Explorer provides its own native detection in the form of Conditional Comments.

This means that adding the following in our HTML should suffice:

<!--[if lt IE 9]> <style> input { display: block; } </style>
<![endif]-->

This means that the above will be applied, should the browser be a version of Internet Explorer lower than version 9 (IE 9 supports ::before)—effectively overriding the display: none property.
Seems straightforward enough?

Unfortunately, over time, some critical flaws emerged in User Agent Detection. So much so that Internet Explorer stopped supporting Conditional Comments from version 10 onward. You will also notice that in the Mozilla Developer Network link itself, the following is presented in an orange alert:

It’s worth re-iterating: it’s very rarely a good idea to use user agent sniffing. You can almost always find a better, more broadly compatible way to solve your problem!

The biggest drawback of User Agent Detection is that browser vendors started spoofing their user agent strings over time due to the following:

  • Developer adds CSS feature that is not supported in the browser.
  • Developer adds User Agent Detection code to serve fallbacks to the browser.
  • Browser eventually adds support for that specific CSS feature.
  • Original User Agent Detection code is not updated to take this into consideration.
  • Code always displays the fallback, even if the browser now supports the CSS feature.
  • Browser uses a fake user agent string to give users the best experience on the web.

Furthermore, even if we were able to infallibly determine every browser type and version, we would have to actively maintain and update our User Agent Detection to reflect the feature support state of those browsers (notwithstanding browsers that have not even been developed yet).

It is important to note that although there are superficial similarities between feature detection and User Agent Detection, feature detection takes a radically different approach than User Agent Detection. According to the Mozilla Developer Network, when we use feature detection, we are essentially doing the following:

  1. 🔎 Testing whether a browser is actually able to run a specific line (or lines) of HTML, CSS or JavaScript code.
  2. 💪 Taking a specific action based on the outcome of this test.

We can also look to Wikipedia for a more formal definition (emphasis mine):

Feature detection (also feature testing) is a technique used in web development for handling differences between runtime environments (typically web browsers or user agents), by programmatically testing for clues that the environment may or may not offer certain functionality. This information is then used to make the application adapt in some way to suit the environment: to make use of certain APIs, or tailor for a better user experience.

While a bit esoteric, this definition does highlight two important aspects of feature detection:

  • Feature detection is a technique, as opposed to a specific tool or technology. This means that there are various (equally valid) ways to accomplish feature detection.
  • Feature detection programmatically tests code. This means that browsers actually run a piece of code to see what happens, as opposed to merely using inference or comparing it against a theoretical reference/list as done with User Agent Detection.

CSS feature detection with @supports

The core concept is not to ask “What browser is this?” It’s to ask “Does your browser support the feature I want to use?”.

—Rob Larson, The Uncertain Web: Web Development in a Changing Landscape (2014)

Most modern browsers support a set of native CSS rules called CSS conditional rules. These allow us to test for certain conditions within the stylesheet itself. The latest iteration (known as module level 3) is described by the Cascading Style Sheets Working Group as follows:

This module contains the features of CSS for conditional processing of parts of style sheets, conditioned on capabilities of the processor or the document the style sheet is being applied to. It includes and extends the functionality of CSS level 2 [CSS21], which builds on CSS level 1 [CSS1]. The main extensions compared to level 2 are allowing nesting of certain at-rules inside ‘@media’, and the addition of the ‘@supports’ rule for conditional processing.

If you’ve used @media, @document or @import before, then you already have experience working with CSS conditional rules. For example when using CSS media queries we do the following:

  • Wrap a single or multiple CSS declarations in a code block with curly brackets, { }.
  • Prepend the code block with a @media query with additional information.
  • Include an optional media type. This can either be all, print, speech or the commonly used screen type.
  • Chain expressions with and/or to determine the scope. For example, if we use (min-width: 300px) and (max-width: 800px), it will trigger the query if the screen size is wider than 300 pixels and smaller than 800 pixels.

The feature queries spec (editor’s draft) prescribes behavior that is conveniently similar to the above example. Instead of using a query expression to set a condition based on the screen size, we write an expression to scope our code block according to a browser’s CSS support (emphasis mine):

The ‘@supports rule allows CSS to be conditioned on implementation support for CSS properties and values. This rule makes it much easier for authors to use new CSS features and provide good fallback for implementations that do not support those features. This is particularly important for CSS features that provide new layout mechanisms, and for other cases where a set of related styles needs to be conditioned on property support.

In short, feature queries are a small built-in CSS tool that allow us to only execute code (like the display: none example above) when a browser supports a separate CSS feature—and much like media queries, we are able to chain expressions as follows: @supports (display: grid) and ((animation-name: spin) or (transition: transform(rotate(360deg)).

So, theoretically, we should be able to do the following:

@supports (::before) { input { display: none; }
}

Unfortunately, it seems that in our example above the display: none property did not trigger, in spite of the fact that your browser probably supports ::before.

That’s because there are some caveats to using @supports:

  • First and foremost, CSS feature queries only support CSS properties and not CSS pseudo-element, like ::before.
  • Secondly, you will see that in the above example our @supports (transform: scale(2)) and (animation-name: beat) condition fires correctly. However if we were to test it in Internet Explorer 11 (which supports both transform: scale(2) and animation-name: beat) it does not fire. What gives? In short, @supports is a CSS feature, with a support matrix of its own.

CSS feature detection with Modernizr

Luckily, the fix is fairly easy! It comes in the form of an open source JavaScript library named Modernizr, initially developed by Faruk Ateş (although it now has some pretty big names behind it, like Paul Irish from Chrome and Alex Sexton from Stripe).

Before we dig into Modernizr, let’s address a subject of great confusion for many developers (partly due to the name “Modernizr” itself). Modernizr does not transform your code or magically enable unsupported features. In fact, the only change Modernzr makes to your code is appending specific CSS classes to your <html> tag.

This means that you might end up with something like the following:

<html class="js flexbox flexboxlegacy canvas canvastext webgl no-touch geolocation postmessage websqldatabase indexeddb hashchange history draganddrop websockets rgba hsla multiplebgs backgroundsize borderimage borderradius boxshadow textshadow opacity cssanimations csscolumns cssgradients cssreflections csstransforms csstransforms3d csstransitions fontface generatedcontent video audio localstorage sessionstorage webworkers applicationcache svg inlinesvg smil svgclippaths">

That is one big HTML tag! However, it allows us do something super powerful: use the CSS descendant selector to conditionally apply CSS rules.

When Modernizr runs, it uses JavaScript to detect what the user’s browser supports, and if it does support that feature, Modernizr injects the name of it as a class to the <html>. Alternatively, if the browser does not support the feature, it prefixes the injected class with no- (e.g., no-generatedcontent in our ::before example). This means that we can write our conditional rule in the stylesheet as follows:

.generatedcontent input { display: none
}

In addition, we are able to replicate the chaining of @supports expressions in Modernizr as follows:

/* default */
.generatedcontent input { } /* 'or' operator */
.generatedcontent input, .csstransforms input { } /* 'and' operator */
.generatedcontent.csstransformsinput { } /* 'not' operator */
.no-generatedcontent input { }

Since Modernizr runs in JavaScript (and does not use any native browser APIs), it’s effectively supported on almost all browsers. This means that by leveraging classes like generatedcontent and csstransforms, we are able to cover all our bases for Internet Explorer 8, while still serving bleeding-edge CSS to the latest browsers.

It is important to note that since the release of Modernizr 3.0, we are no longer able to download a stock-standard modernizr.js file with everything except the kitchen sink. Instead, we have to explicitly generate our own custom Modernizr code via their wizard (to copy or download). This is most likely in response to the increasing global focus on web performance over the last couple of years. Checking for more features contributes to more loading, so Modernizr wants us to only check for what we need.

So, I should always use Modernizr?

Given that Modernizr is effectively supported across all browsers, is there any point in even using CSS feature queries? Ironically, I would not only say that we should but that feature queries should still be our first port of call.

First and foremost, the fact that Modernizr does not plug directly into the browser API is it’s greatest strength—it does not rely on the availability of a specific browser API. However, this benefit comes a cost, and that cost is additional overhead to something most browsers support out of the box through @supports—especially when you’re delivering this additional overhead to all users indiscriminately in order to a small amount of edge users. It is important to note that, in our example above, Internet Explorer 8 currently only stands at 0.18% global usage).

Compared to the light touch of @supports, Modernizr has the following drawbacks:

  • The approach underpinning development of Modernizr is driven by the assumption that Modernizr was “meant from day one to eventually become unnecessary.”
  • In the majority of cases, Modernizr needs to be render blocking. This means that Modernizr needs to be downloaded and executed in JavaScript before a web page can even show content on the screen—increasing our page load time (especially on mobile devices)!
  • In order to run tests, Modernizr often has to actually build hidden HTML nodes and test whether it works. For example, in order to test for <canvas> support, Modernizr executes the follow JavaScript code: return !!(document.createElement('canvas').getContext && document.createElement('canvas').getContext('2d'));. This consumes CPU processing power that could be used elsewhere.
  • The CSS descendant selector pattern used by Modernizr increases CSS specificity. (See Harry Roberts’ excellent article on why “specificity is a trait best avoided.”)
  • Although Modernizr covers a lot of tests (150+), it still does not cover the entire spectrum of CSS properties like @support does. The Modernizr team actively maintains a list of these undetectables.

Given that feature queries have already been widely implemented across the browser landscape, (covering about 93.42% of global browsers at the time of writing), it’s been a good while since I’ve used Modernizr. However, it is good to know that it exists as an option should we run into the limitations of @supports or if we need to support users still locked into older browsers or devices for a variety of potential reasons.

Furthermore, when using Modernizr, it is usually in conjunction with @supports as follows:

.generatedcontent input { display: none;
} label:hover::before { color: #c6c8c9;
} input:checked + label::before { color: black;
} @supports (transform: scale(2)) and (animation-name: beat) { input:checked + label::before { color: #e0e1e2; animation-name: beat; animation-iteration-count: infinite; animation-direction: alternate; }
}

This triggers the following to happen:

  • If ::before is not supported, our CSS will fallback to the default HTML radio select.
  • If neither transform(scale(2)) nor animation-name: beat are supported but ::before is, then the heart icon will change to black instead of animate when selected.
  • If transform(scale(2), animation-name: beat and ::before are supported, then the heart icon will animate when selected.

The future of CSS feature detection

Up until this point, I’ve shied away from talking about feature detection in a world being eaten by JavaScript, or possibly even a post-JavaScript world. Perhaps even intentionally so, since current iterations at the intersection between CSS and JavaScript are extremely contentious and divisive.

From that moment on, the web community was split in two by an intense debate between those who see CSS as an untouchable layer in the “separation of concerns” paradigm (content + presentation + behaviour, HTML + CSS + JS) and those who have simply ignored this golden rule and found different ways to style the UI, typically applying CSS styles via JavaScript. This debate has become more and more intense every day, bringing division in a community that used to be immune to this kind of “religion wars”.

—Cristiano Rastelli, Let there be peace on CSS (2017)

However, I think exploring how to apply feature detection in the modern CSS-in-JS toolchain might be of value as follows:

  • It provides an opportunity to explore how CSS feature detection would work in a radically different environment.
  • It showcases feature detection as a technique, as opposed to a specific technology or tool.

With this in mind, let us start by examining an implementation of our pop-up by means of the most widely-used CSS-in-JS library (at least at the time of writing), Styled Components:

This is how it will look in Internet Explorer 8:

In our previous examples, we’ve been able to conditionally execute CSS rules based on the browser support of ::before (via Modernizr) and transform (via @supports). However, by leveraging JavaScript, we are able to take this even further. Since both @supports and Modernizr expose their APIs via JavaScript, we are able to conditionally load entire parts of our pop-up based solely on browser support.

Keep in mind that you will probably need to do a lot of heavy lifting to get React and Styled Components working in a browser that does not even support ::before (checking for display: grid might make more sense in this context), but for the sake of keeping with the above examples, let us assume that we have React and Styled Components running in Internet Explorer 8 or lower.

In the example above, you will notice that we’ve created a component, called ValueSelection. This component returns a clickable button that increments the amount of likes on click. If you are viewing the example on a slightly older browser, you might notice that instead of the button you will see a dropdown with values from 0 to 9.

In order to achieve this, we’re conditionally returning an enhanced version of the component only if the following conditions are met:

if ( CSS.supports('transform: scale(2)') && CSS.supports('animation-name: beat') && Modernizr.generatedcontent
) { return ( <React.Fragment> <Modern type="button" onClick={add}>{string}</Modern> <input type="hidden" name="liked" value={value} /> </React.Fragment> )
} return ( <Base value={value} onChange={select}> { [1,2,3,4,5,6,7,8,9].map(val => ( <option value={val} key={val}>{val}</option> )) } </Base>
);

What is intriguing about this approach is that the ValueSelection component only exposes two parameters:

  • The current amount of likes
  • The function to run when the amount of likes are updated
<Overlay> <Popup> <Title>How much do you like popups?</Title> <form> <ValueInterface value={liked} change={changeLike} /> <Button type="submit">Submit</Button> </form> </Popup>
</Overlay>

In other words, the component’s logic is completely separate from its presentation. The component itself will internally decide what presentation works best given a browser’s support matrix. Having the conditional presentation abstracted away inside the component itself opens the door to exciting new ways of building cross-browser compatible interfaces when working in a front-end and/or design team.

Here’s the final product:

…and how it should theoretically look in Internet Explorer 8:

Additional Resources

If you are interested in diving deeper into the above you can visit the following resources:

  • Mozilla Developer Network article on feature detection
  • Mozilla Developer Network article on user agent detection
  • Mozilla Developer Network article on CSS feature queries
  • Official feature queries documentation by the CSSWG
  • Modernizr Documentation

Schalk is a South African front-end developer/designer passionate about the role technology and the web can play as a force for good in his home country. He works full time with a group of civic tech minded developers at a South African non-profit called OpenUp.

He also helps manage a collaborative space called Codebridge where developers are encouraged to come and experiment with technology as a tool to bridge social divides and solve problems alongside local communities.

The post Using feature detection to write CSS with cross-browser support appeared first on CSS-Tricks.