Daniel Tonon – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Sat, 19 Dec 2020 11:34:31 +0000 en-US hourly 1 https://wordpress.org/?v=6.1.1 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 Daniel Tonon – CSS-Tricks https://css-tricks.com 32 32 45537868 How to Section Your HTML https://css-tricks.com/how-to-section-your-html/ https://css-tricks.com/how-to-section-your-html/#comments Tue, 18 Jun 2019 14:22:53 +0000 http://css-tricks.com/?p=289070 The sectioning elements in HTML5 are <nav>, <aside>, <article>, and <section>. <body> is also kind of a sectioning element since all content lying inside of it is part of the default document section.

Here is …


How to Section Your HTML originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
The sectioning elements in HTML5 are <nav>, <aside>, <article>, and <section>. <body> is also kind of a sectioning element since all content lying inside of it is part of the default document section.

Here is a brief explanation of each sectioning element and how they are used:

  • <nav> – Equivalent to role="navigation". Major site navigation that consistently appears frequently across the site. Examples include the primary navigation, secondary navigation, and in-page navigation.
  • <aside> – Equivalent to role="complementary". Content that is only tangentially related (or not related) to the main content. Think of something like a sidebar with supplementary information, a note within an article, or the outer container for a list of related articles at the bottom of a blog post.
  • <article> – Equivalent to role="article". Content that is self-contained in that it makes sense on its own when taken out of context. That could mean a widget, a blog post or even a comment within a blog post.
  • <section> – Equivalent to role="region". Content that needs extra context from its parent sectioning element to make sense. This is a generic sectioning element that is used whenever it doesn’t make sense to use the other more semantic ones.

👋 It has been brought to our attention in the comments (and on Twitter) that some of the techniques used in this article result in a poor user experience for screen reader users. We’ll be making updates to make sure the article reflects the best possible information. For now, please see Adrian Roselli’s Pen below for a more appropriate approach to sectioning the example layout. Thanks for being such an awesome community where we can all learn and grow together!

Contents

This is a very long article that I suspect you will want to come back to and reference multiple times. To make that easier, here is a list of all the article headings:

When to use <nav>

The <nav> element only ever needs to be used once per navigation block. Sub-navigation that is already contained inside a <nav> element does not need to be wrapped in a second <nav> element.

<nav aria-label="Primary">
  <ul>
    <li><a href="#">Primary link</a></li>
    <li><a href="#">Primary link</a></li>
    <li>
      <a href="#">Primary link</a>
      <!-- <nav> element is *not* needed again here -->
      <ul>
        <li><a href="#">Secondary link</a></li>
        <li><a href="#">Secondary link</a></li>
      </ul>
    </li>
  </ul>
</nav>

The <nav> element is intended for only major navigation blocks. “Major” is a very subjective term though. html5doctor.com has a pretty good explanation of when it is and isn’t appropriate to use <nav>, keep in mind that the following are opinions and not official W3C rulings:

The key phrase is ‘major’ navigation. We could debate all day about the definition of ‘major’, but to me it means:

  • Primary navigation
  • Site search
  • Secondary navigation (arguably)
  • In-page navigation (within a long article, for example)

While there isn’t any right or wrong here, a straw poll coupled with my own interpretation tells me that the following shouldn’t be enclosed by <nav>:

  • Pagination controls
  • Social links (although there may be exceptions where social links are the major navigation, in sites like About me or Flavours, for example)
  • Tags on a blog post
  • Categories on a blog post
  • Tertiary navigation
  • Fat footers

html5doctor.com (strikethrough mine)

Breadcrumbs are another piece of content that should be wrapped in a <nav> element as evidenced by this W3C breadcrumb HTML example.

A <nav> is unnecessary around a search form

I disagree with HTML5 Doctor’s opinion that a site search form should be wrapped in a <nav> element (thus why I crossed it out). <nav> is intended to be wrapped around navigation links, not a form. The site search actually has its own special role that defines it as a search landmark. If you add role="search" to the search <form> element, it is announced to screen reader users as a search landmark. Screen reader users will also be able to navigate to it when navigating via landmarks. If there are multiple search forms on the page, they should be differentiated using aria-label or aria-labelledby (more details on these attributes later). Don’t include the word “search” in the aria label though — that’s like saying “image of” in image alt text; it’s unnecessary. Instead, focus on what the search form is searching through. So, for the global site search, giving it aria-label="site" would be appropriate.

<!-- <nav> is not needed on a search form. -->
<!-- role="search" is enough -->
<form role="search" aria-label="site">
  <label>
    <span>Search</span>
    <input type="search" />
  </label>
  <button type="submit">Submit</button>
</form>

A role="search" form won’t appear in a document outline but I think this is okay considering search forms are often small and self-contained. It still gets the majority of benefits that you get from using sectioning elements. Adding a sectioning element to the mix bombards the screen reader user with messages telling them that it is a search form (one for the sectioning element, one for the search role, and one for the search input label).

Don’t use the word “nav” or “navigation” in the label

Like with role="search", adding “navigation” to the label of a <nav> element only results in a screen reader saying “navigation” twice.

<nav aria-label="primary navigation">
  <!-- Screen reader: "primary navigation navigation landmark" -->
</nav>

<nav aria-label="primary">
  <!-- Screen reader: "primary navigation landmark" -->
</nav>

Questions to ask yourself

That same HTML5 Doctor article lists two questions that you can ask yourself to help you figure out if something should be wrapped in a <nav> or not:

  • Would another sectioning element also be appropriate? If yes, maybe use that instead.
  • Would you add a link to it in a “skip to” block for accessibility? If not, then it might not be worth using a <nav> element.

In those cases where the navigation is too minor to justify the use of the <nav> element, <section> is most likely the element that you should use instead.

The most common use case for a <nav> is to wrap it around a list of links but it doesn’t have to be a list of links. If your navigation works in a different sort of way, you can still use the <nav> element.

<!-- Example taken from the <nav> element specification -->
<!-- https://html.spec.whatwg.org/multipage/sections.html#the-nav-element:the-nav-element-5 -->
<nav>
  <h1>Navigation</h1>
  <p>You are on my home page. To the north lies <a href="/blog">my
  blog</a>, from whence the sounds of battle can be heard. To the east
  you can see a large mountain, upon which many <a
  href="/school">school papers</a> are littered. Far up thus mountain
  you can spy a little figure who appears to be me, desperately
  scribbling a <a href="/school/thesis">thesis</a>.</p>
  <p>To the west are several exits. One fun-looking exit is labeled <a
  href="https://games.example.com/">"games"</a>. Another more
  boring-looking exit is labeled <a
  href="https://isp.example.net/">ISP™</a>.</p>
  <p>To the south lies a dark and dank <a href="/about">contacts
  page</a>. Cobwebs cover its disused entrance, and at one point you
  see a rat run quickly out of the page.</p>
</nav>

In this same vein, it’s okay to have small bits like intro text in the <nav> element as long as the primary focus of the content is on the navigation links. Introductory content is best placed inside a <header> in the <nav> element. I’ll go into more depth on headers and footers later.

<nav>
  <header>
    <h2>In this section</h2>
    <p>This is some intro text describing what you will find in this section.</p>
  </header>
  <ul>
    <li><a href="#">Sub section one</a></li>
    <li><a href="#">Sub section two</a></li>
  </ul>
</nav>

Avoid nesting an <aside> inside an <aside>

In the same way that <nav> shouldn’t really ever be nested inside another <nav> element, <aside> elements also tend not to be nested inside each other. <aside> is used to represent content that is tangentially related to the content around it. That means placing an aside inside an aside is basically announcing a tangent away from something that in itself is a tangent away from the main content.

<!-- Don't do this -->
<aside aria-label="Side bar">

  <aside>
    <h2>Share</h2>
    <ul>
      <!-- List of social media links -->
    </ul>    
  </aside>

  <aside>
    <h2>Recommendations:</h2>
    <ul>
      <li>
        <article>
          <h2><a href="#">Related article title</a></h2>
          <p>Article description</p>
        </article>
      </li>
      <!-- List of recommended articles continues -->
    </ul>
  </aside>

</aside>

If you have a sidebar that has multiple sections, don’t nest <aside> elements inside of <aside> elements like in the example above. Instead, make the sidebar a single <aside> and then use <section> (or another appropriate sectioning element) to create the different sections.

<!-- Do this instead -->
<aside aria-label="Side bar">

  <section>
    <h2>Share</h2>
    <ul>
      <!-- List of social media links -->
    </ul>    
  </section>

  <section>
    <h2>Recommended articles:</h2>
    <ul>
      <li>
        <article>
          <h2><a href="#">Related article title</a></h2>
          <p>Article description</p>
        </article>
      </li>
      <!-- List of recommended articles continues -->
    </ul>
  </section>

</aside>

Article is like “Block”; Section is like “Element”

<section> and <article> are easy to get confused with one another. If you are familiar with “Block Element Modifier” (BEM) syntax, then an easy way to think of the difference between the two is that an <article> is a bit like the “B” (or “Block”) in BEM. It is a container that stores self-contained content that still makes sense when placed in a different context. Individual tweets on Twitter and each list item on a search results page would be considered <article> elements.

<section> is like the “E” (or “Element”) in BEM. It is a sub-section that requires context from its parent sectioning element to make sense. <section> is a generic catch-all sectioning element that you use when it doesn’t make sense to use the other sectioning elements. So, if in doubt, go with <section>.

Note that if something is styled as a “Block” in BEM, that doesn’t automatically mean that it is an <article> element. Same goes for BEM “Elements” and <section> elements. The element of something should be based on the meaning of the content, not how the content looks.

Comments sections

Something that may surprise people is that individual comments on a blog post are also considered articles, even though they are in reply to the main blog post. The <article> element wrapping around the main blog post should also wrap around the comments section though. This is to represent that the comments go with the main article.

<article>
  <h1>I am an awesome blog post!</h1>
  <p>I am some body text content.</p>

  <section>
    <h2>Comments</h2>
    <ul>
      <li>
        <article>
          <h2>Username</h2>
          <p>This is the comment body text.</p>
          <footer>
            <p>
              Meta data like post date makes sense
              in either the header or the footer.
            </p>
          </footer>
        </article>
      </li>
    </ul>
  </section>
</article>

Don’t swap div for a section

Just because we have these fancy sectioning elements now, it doesn’t mean that the good old <div> element has lost all of its usefulness. <div> has no semantic meaning, so it is quite useful whenever we are altering the HTML purely for the sake of styling purposes.

Let’s say that we have a blog post contained inside an <article> element that we need to wrap in something for the sake of styling purposes.

<!-- I need a wrapper element -->
<article>
  <h1>I am a blog post</h1>
  <p>I am some content</p>
</article>

Reaching for the <section> element in this circumstance is not the right thing to do.

<!-- Do not do this -->
<section class="wrapper">
  <article>
    <h1>I am a blog post</h1>
    <p>I am some content</p>
  </article>
</section>

Though <section> is technically a generic element, <div> is the far more appropriate option in this circumstance. This new wrapping container is not meant to have any semantic meaning behind it and that is exactly what <div> is designed to be used for.

<!-- Use a <div> if the element is only used for styling purposes -->
<div class="wrapper">
  <article>
    <h1>I am a blog post</h1>
    <p>I am some content</p>
  </article>
</div>

Another way to remember this: if you can’t think of a meaningful heading to apply to a <section>, then it probably shouldn’t be a <section>.

Headers and footers

Although they don’t necessarily need to, sectioning elements may contain a single <header> and a single <footer> with the header being at the top of the section and the footer being at the bottom.

A basic sectioning element with a header and a footer.

Sectioning elements can be nested inside one another as many times as is needed based on the content.

Nested sectioning elements

The header and footer in a sectioning element can also contain sectioning elements.

Headers and footers containing sectioning elements

The one major restriction around nesting sectioning elements is that headers and footers cannot be nested inside other headers and footers.

Nesting headers and footers inside one another is not allowed.

What goes inside headers?

Headers are used for introductory content. Appropriate things to include in <header> elements include (but are not limited to):

  • The heading element (<h1><h6>)
  • An introductory paragraph or statement.
  • A profile picture
  • A logo
  • A search form
  • Primary navigation
  • Author’s name
  • Post/updated date
  • Meta data
  • Social media links

What goes inside footers?

Footer elements primarily hold things like meta data and minor supporting content. Appropriate things to include in <footer> elements include (but are not limited to):

  • Copyright information
  • Legalities
  • Footnotes
  • Low priority site navigation
  • Author’s name
  • Post/updated date
  • Meta data
  • Social media links

You will notice that there is some cross over between the header and the footer in terms of content that is appropriate to both. This is mostly because meta-type content fits well in either element. It mainly comes down to the design that you are trying to achieve. <header> elements do tend to signify that the content inside of them is of greater importance than the content inside of a <footer> element though.

Sectioning elements and the document outline algorithm

An important thing to know about these sectioning elements is that they are all supposed to feature a <h#> element inside of them (or be labeled in some other way, but more on that later). This is primarily for the sake of something called the document outline algorithm. This is an algorithm that uses sectioning elements to help determine what level a heading (<h#>) should be without having to rely exclusively on the number that the developer has provided. So, have you ever wondered whether or not it’s okay to have more than one <h1> on a page? This is meant to make that a non-issue (but hold on for a sec, because there is more to the story).

<!-- No Document outline algorithm -->
<article>
  <h1>Primary heading</h1>

  <h2>Secondary heading</h2>
  <p>Some text.</p>

  <h3>Tertiary heading</h3>
  <p>Some text.</p>
</article>
<!-- With document outline algorithm -->
<article>
  <h1>Primary heading</h1> <!-- Recognized as <h1> -->
  
  <!-- sectioning element sets new heading level context -->
  <section>
    <h1>Secondary heading</h1> <!-- Recognized as <h2> -->
    <p>Some text.</p>

    <h2>Tertiary heading</h2> <!-- Recognized as <h3> -->
    <p>Some text.</p>
  </section>

</article>

There is a lot more to learn about the document outline algorithm. I’ll stop here though because…

No browser supports the document outline algorithm

There is not a single browser that supports this method of creating a heading structure. This is a shame. It would make building accessible websites much easier if we didn’t have to worry so much about using the correct heading level all the time.

As far as I’m aware, there are two main reasons why no browser has implemented the algorithm. One is that browser vendors are afraid of breaking the heading structure of sites that have used sectioning elements incorrectly. The other reason is that the current document outline algorithm spec is difficult to implement and no browser vendor has been willing to put the time into implementing it yet.

In reference to the first reason, there is a long discussion about incorporating a new <h> element instead of using the <h1> element to tell browsers to use the document outline algorithm. I was in favor of this new <h> element idea until I realized that an attribute on the <html> element or adding a <meta> tag to the <head> would work even better as a means of telling browsers it was safe to use the algorithm. It is also better for headings to fall back to a <h1> in unsupported browsers than falling back to a <span>.

If you would like to play around with this <h> concept though, there is a plugin called hfill. It allows you to nest <hx> headings inside sectioning elements to create the document outline without having to worry about heading levels so much. There is a demo available for you to try it out. The major flaw in this plugin though is that the only way to increment heading levels is by nesting sectioning elements inside one another. There is no <h1>-is-greater-than-<h2> dynamic in this plugin which is the main reason I fell out of love with this <h> element idea. This lack of heading hierarchy would make CMS rich text editors far too difficult for clients to use.

As for the issue around implementation difficulty, work is being done to produce a simplified spec that browser vendors are more likely to adopt. The document outline algorithm has been in the HTML specifications for years. Hopefully this simplified spec will allow the algorithm to become a reality.

Although the algorithm is not supported anywhere yet, we can still build with the algorithm in mind. If we build with it in mind, then we gain the following benefits:

  1. We future-proof our sites in case the algorithm ever does get implemented.
  2. We can significantly improve the user experience for screen reader users.
  3. We potentially improve search engine optimization (SEO) due to search engines being able to better understand the site’s content.
  4. We can create a better user experience for users by allowing them to use native browser features that make use of sectioning elements, like Reader Mode.

Sectioning content

Take a look at this mock-up layout I put together and think about how you might split it up into sections.

Mock-up layout featuring a logo, primary nav, and search in the header, a secondary navigation in a left sidebar, and a main content area  in the middle with primary and secondary headings, a sidebar on the right with share links and links to recommended articles.

This is how I would split the layout up into sectioning elements (only the solid lines represent sectioning elements).

Layout showing where various sectioning elements would appear.

In terms of HTML markup, it looks like this:

<body>

  <header>
    <a href="/" title="Go to home page">
      <img src="logo.png" alt="Site logo">
    </a>
    <nav>
      <ul>
        <li><a href="#">Primary nav</a></li>
        <li><a href="#">Primary nav</a></li>
        <li><a href="#">Primary nav</a></li>
        <li><a href="#">Primary nav</a></li>
      </ul>
    </nav>
    <form role="search" aria-label="site">
      <label>
        <span>Search</span>
        <input type="search"/>
      </label>
      <button type="submit">Submit</button>
    </form>
  </header>

  <nav>
    <ul>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
    </ul>
  </nav>

  <main>
    <article>
      <h1>Main article heading</h1>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quae sunt igitur communia vobis cum antiquis, iis sic utamur quasi concessis; Nihil acciderat ei, quod nollet, nisi quod anulum, quo delectabatur, in mari abiecerat. Unum est sine dolore esse, alterum cum voluptate. Laboro autem non sine causa; Theophrasti igitur, inquit, tibi liber ille placet de beata vita? Nihil opus est exemplis hoc facere longius. Duo Reges constructio interrete. Graecum enim hunc versum nostis omnes Suavis laborum est praeteritorum memoria. Haec et tu ita posuisti, et verba vestra sunt.</p>

      <h2>Article secondary heading</h2>
      <p>Nos commodius agimus. A mene tu? Tantum dico, magis fuisse vestrum agere Epicuri diem natalem, quam illius testamento cavere ut ageretur. Tenesne igitur, inquam, Hieronymus Rhodius quid dicat esse summum bonum, quo putet omnia referri oportere? Nihilo beatiorem esse Metellum quam Regulum. Sed quanta sit alias, nunc tantum possitne esse tanta. Philosophi autem in suis lectulis plerumque moriuntur. Esse enim, nisi eris, non potes.</p>
      <p>Sunt enim quasi prima elementa naturae, quibus ubertas orationis adhiberi vix potest, nec equidem eam cogito consectari. Id Sextilius factum negabat. Quorum sine causa fieri nihil putandum est. Quae autem natura suae primae institutionis oblita est?</p>
    </article>
  </main>

  <aside>
    <section>
      <h2>Share</h2>
      <ul>
        <li><a href="#">Facebook</a></li>
        <li><a href="#">Twitter</a></li>
        <li><a href="#">Email</a></li>
      </ul>
    </section>
    <section>
      <h2>Recommended</h2>
      <ul>
        <li>
          <article>
            <h3><a href="#">Related article</a></h3>
            <p>Article description</p>
          </article>
        </li>
        <li>
          <article>
            <h3><a href="#">Related article</a></h3>
            <p>Article description</p>
          </article>
        </li>
      </ul>
    </section>
  </aside>

  <footer>
    <ul>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
    </ul>
  </footer>

</body>

The <main> element

There is a very important semantic element that I used in the markup above that I haven’t covered yet and that is the <main> element. The <main> element represents the primary content of the page. It is not supposed to feature any side bars or navigation elements in it. You also must not have more than one <main> element on the page unless all other <main> elements on the page have a hidden attribute applied to them (this is for the sake of SPAs).

The <main> element is not a sectioning element. This means that it doesn’t help contribute to the document outline algorithm and it can’t feature a <header> or <footer> element as a direct child. It is a landmark element though so screen reader users are able to navigate to it quite easily.

I’m not 100% sure if using <article> in the <main> element like I have done above is necessary. Semantically, it does make sense. The main content is self-contained, thus justifying use of the <article> element in this way. From a document outline algorithm perspective, the <article> element also helps with the document structure.

From a usability point of view, it feels a bit unnecessary and the document outline algorithm doesn’t even work anywhere at the moment. I’m going to continue using it throughout my examples but I would be interested to know what other people think about this in the comments section.

You need to label your sections. Here are three methods.

I am going to be saying the word “label” a lot throughout this article. Keep in mind that I am not talking about the <label> element. The <label> element is not used to label sectioning elements.

Sectioning elements require labels so that screen reader users are able to quickly identify what content they can find inside that particular section of the site. I consider using sectioning elements without providing associated section labels as an accessibility fail, unless it is the only one of its type on the page. It is also recommended that the exact same label text not be used on multiple sectioning elements (or heading elements). This makes each section more recognizable to screen reader users which helps them navigate the site more easily.

There are three ways to label a sectioning element. In the following examples, I refer to “transport” and “portability” as a way of explaining how easy it is to save the section into a component and use that component multiple times in multiple different contexts.

I also provide lists of positives and negatives in the examples as well. In these lists, I assume that you want the section label to be readable by screen readers but hidden from sighted users.

Method 1: Add an aria-label attribute

This is the quickest and easiest way to label a sectioning element.

<section aria-label="Label for this section">
  <p>Content for this section</p>
</section>
The aria-label translation issue

The main draw back of aria-label (at the time of writing) is that most browsers are unable to translate these values for users who speak a different language than you. The developers at Google recently fixed this bug in Chrome, however this is still a problem for every other browser.

If your website has a large international audience or you know that many of your users do not speak your language, you should probably avoid using this attribute until all browsers support the translation of this property. If you don’t have those sorts of users, it’s pretty safe to assume that the non-sighted users viewing your site are able to understand your language — well enough to be able to navigate your site, anyway.

If you need more convincing, let’s say your site has very few international users. That means your users generally come from the same country as you. If they come from the same country then they are likely to speak the same language as you, so there is already a fairly small percentage of your users that don’t understand the native language of your site. Now take into account that aria-label only affects screen reader users. That is now only a fraction of an already small percentage of your users who will experience the issue. And now consider that Chrome (by far the most popular browser in the world) now supports translation of the aria-label attribute. The user has to also not be using an up to date version of Chrome as their browser for the translation issue to be a problem. If you factor all of that together, it is highly probable that you may not have any users who are both able to perceive the aria-label attributes and are incapable of comprehending what they say. This makes me feel like the bad multi-lingual support in aria-label isn’t really worth worrying that much about unless you have a large international audience or you have many users that you know do not speak your language.

Positives
  • Super quick and easy to implement.
  • Doesn’t affect heading structure.
  • Makes components easy to transport.
  • Is invisible to sighted users.
Negatives
  • Not translated into other languages in non-Chrome browsers (at time of writing).
  • Often not supported by page structure analysis tools.
  • Confusion can arise from not knowing what level other headings inside the section should be at.

Method 2: Add a <h#> element to it

By <h#> I mean <h1>, <h2>, <h3>,<h4>,<h5>, or <h6> depending on what makes sense. Adding a heading to a sectioning element is a quick way to label it.

Heading placement

The heading can be placed either directly in the sectioning element, like this:

<section>
  <h1>Heading</h1>
  <p>content</p>
</section>

…or placed inside the <header> element:

<section>
  <header>
    <h1>Heading</h1>
    <p>I'm a byline</p>
  </header>

  <p>Content</p>
</section>

You can also place as many <div> wrapper elements between the sectioning element and the heading as you want.

<!-- This is perfectly fine -->
<section>
  <div>
    <header>
      <div>
        <h1>Heading</h1>
        <p>I'm a byline</p>
      </div>
    </header>

    <p>Content</p>
  </div>
</section>
Only one heading of the highest level per sectioning element

There should really only be one heading of the highest level in a sectioning element. The spec says that when there are multiple top level headings or headings of a higher level than the first, the browser is supposed to close the previous sectioning element and start a new one of the same type.

The first element of heading content in an element of sectioning content represents the heading for that explicit section. Subsequent headings of equal or higher rank start new implied subsections that are part of the previous section’s parent section. Subsequent headings of lower rank start new implied subsections that are part of the previous one. In both cases, the element represents the heading of the implied section.

—HTML 5.3, Headings and Sections

In reality, the browser uses the first heading as the section label but these implied sections are never created. It just announces the heading as is when it encounters it. It’s not earth-shatteringly bad but it is somewhat confusing.

<!-- Avoid this: -->
<section>
  <h2>Heading level two labeling a section</h2>
  <p>Content</p>

  <!-- Don't use same level or higher headings as the one labeling the section -->
  <h2>This is also a heading level two</h2>
  <p>Content</p>
</section>

<!-- Do this instead: -->
<div>
  <section>
    <h2>Heading level two labeling a section</h2>
    <p>Content</p>
  </section>
  <section>
    <h2>Heading level two labeling a different section</h2>
    <p>Content</p>
  </section>
</div>
The heading always comes first

If a sectioning element has a <h#> element, that top level heading should always be the very first piece of content inside that sectioning element. Failing to do so counts as an accessibility fail.

If you find yourself needing to place content before your heading (like an image, for example), you can use Flexbox to rearrange the visual order. This will allow it to look like the image comes before the heading but in the markup the heading comes before the image. There is a bug in IE that will sometimes cause text to not wrap in a flex-direction: column; element. You can work around this issue by applying a max-width to the flex-child element.

<!-- Don't do this -->
<section>
  <img src="image.jpg" alt="Don't place content or images before the heading" />
  <h2>Headings should always come first</h2>
  <p>Place regular content after the heading</p>
</section>

<!-- Do this instead -->
<section class="example">
  <h2>Headings should always come first</h2>
  <img src="image.jpg" alt="Don't place content or images before the heading" />
  <p>Place regular content after the heading</p>
</section>

<style>
.example {
  display: flex;
  flex-direction: column;
}
.example img {
  order: -1;
}
</style>

Note that rearranging the visual order to satisfy WCAG Guideline 1.3.2: Meaningful Sequence can conflict directly with WCAG Guideline 2.4.3: Focus Order. For example, if that image is a link to an article and the heading you are placing it above is also a link to the article, placing the heading first breaks the focus order. Placing the image first breaks the meaningful sequence.

In situations like this where these two guidelines conflict with one another, my opinion is that the 1.3.2: Meaningful Sequence guideline is the more important guideline to follow if you aren’t able to resolve the conflict in some way. Failing focus order leads to the user suffering a moment of discomfort as they are tabbing through the content and focus is sent to an unexpected location. Failing to follow a meaningful sequence leads to a confused user unsure of the relationship between different bits of content.

Making visually hidden section labels out of headings

Headings are visible to sighted users by default. This makes them super useful if you want the heading to be visible. A lot of the time, we don’t want the label for our sectioning element to be visible though. In order to stop our sighted users from seeing the label, we need to use some CSS.

<style>
.visually-hidden {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
</style>

<section>
  <h1 class="visually-hidden">Heading</h1>
  <p>content</p>
</section>
Headings are well-supported by structure analysis tools

Headings also have a huge advantage for developers in that any page structure analysis tool that you can find will have support for them. This makes heading structures easy to test and debug. The other two section labeling methods have very poor support in testing tools. Not even the official W3C Validator service supports the alternatives at the moment. I posted an issue to have this fixed — please consider helping to fix the issue if you are good at coding in Java.

Positives
  • Quick to implement.
  • Reliably appears in page structure analysis tools making it easy to test and debug.
  • All browsers will translate the text into other languages.
  • No confusion over what level other headings inside the section should be.
Negatives
  • Affects document heading structure.
  • Need to ensure that the heading is at the correct level before use.
  • Visible to the user by default.
  • Requires CSS to hide the heading from visual users.
  • Can make components less portable due to heading structure requirements.

Method 3: Use an aria-labelledby attribute

This is what it looks like to create a hidden section label using aria-labelledby.

<section aria-labelledby="unique-id">
  <div hidden id="unique-id">Label for this section</div>
  <p>Content for this section</p>
</section>
Labels can be hidden without CSS

Note that I used the hidden attribute in the example to hide the div rather than a visually-hidden CSS class. aria-labelledby is able to read out text that is normally hidden from screen reader users. This adds the bonus effect of preventing the text from being read out twice by the screen reader. Don’t use the aria-hidden attribute though. Screen readers will not find the label text. Well, NVDA couldn’t find the label text when I tested it. I’m not sure about other screen readers.

Major portability issue

aria-labelledby is the most difficult to use out of all the section labeling methods. The main aspect that makes it difficult to use is that the aria-labelledby attribute works off IDs. Things always get more complicated whenever IDs are involved. This is due to web pages only being allowed to have a single instance of an ID on the page at any one time. This makes component portability difficult.

Due to this portability issue, I would really only recommend this option if you need to support a multi-lingual audience and don’t want to mess around with the heading structure.

No need to place the label near the sectioning element

You don’t need to place the element with the label text inside or near the section element that it labels. The text for the label can be placed in a completely different location to the sectioning element. This is thanks to the ID linking the two elements together. I’m not necessarily saying that it is a good idea to do this, but it is a feature of aria-labelledby that you should be aware of.

<div hidden id="unique-id">Label for this section</div>

<!-- 1,000 lines of other HTML -->

<section aria-labelledby="unique-id">
  <p>Content for this section</p>
</section>
Turn non-heading elements into section labels

There is one other key reason you may want to use aria-labelledby. If you have a visible non-heading element on the page that you want to use as the label for a section, aria-labelledby is perfect for this. A <legend> element inside a <fieldset> is a common use case for this. This doesn’t mean that you have to wrap fieldsets in sectioning elements. I’m just pointing it out in case you spot a need for it.

<section aria-labelledby="section_label">
  <fieldset>
    <legend id="section_label">
      I am both the fieldset legend and the section label
    </legend>

    <!-- Form fields go here -->
  
  </fieldset>
</section>
Positives
  • All browsers will translate the text into other languages.
  • Can assign a non-heading element as the section label.
  • Text for the label does not need to be placed near the section it is labeling.
Negatives
  • Requires the use of IDs to work.
  • Difficult to transport.
  • It can potentially be difficult to track down where the text for the label is stored in your source code.
  • Text is visible by default unless a hidden attribute is used.
  • Text might get read out twice by some screen readers if the text is not hidden.
  • Confusion can arise from not knowing what level other headings inside the section should be at.

Only use one method at a time

Don’t use a <h#>, an aria-label and/or an aria-labelledby attribute at the same time on the same sectioning element. Only every use one labeling method at a time for each sectioning element. Using multiple methods is super confusing and leads to the label being overwritten. It’s a bit like declaring the same property twice in CSS. I wasn’t sure how a screen reader would actually handle this so I created the most ridiculous <section> ever and ran it through NVDA.

<!-- Don't do this -->
<section aria-label="Is this the section label?" aria-labelledby="is_this_the_label">
  <h1>Or is this the section label?</h1>
  <p id="is_this_the_label">Only ever use one at a time.</p>
</section>

This is the order of priority that NVDA gave to the various labeling methods from strongest to weakest:

  1. aria-labelledby
  2. aria-label
  3. <h#>

Adding section labels to our example layout

For a long time, I used headings as the only means of labeling sections. The poor multi-lingual support provided by aria-label scared me; and aria-labelledby was far too cumbersome to be my primary labeling method. We run into a bit of an issue though if we use only headings to label sections. I’ll show you what I mean.

<style>
  .visually-hidden {
    position: absolute;
    opacity: 0;
    pointer-events: none;
  }
</style>

<body>

  <header>
    <a href="/" title="Go to home page">
      <img src="logo.png" alt="Site logo">
    </a>
    <nav>
      <h2 class="visually-hidden">Primary</h2>
      <ul>
        <li><a href="#">Primary nav</a></li>
        <li><a href="#">Primary nav</a></li>
        <li><a href="#">Primary nav</a></li>
        <li><a href="#">Primary nav</a></li>
      </ul>
    </nav>
    <form role="search" aria-label="site">
      <label>
        <span>Search</span>
        <input type="search"/>
      </label>
      <button type="submit">Submit</button>
    </form>
  </header>

  <nav>
    <h2 class="visually-hidden">Secondary</h2>
    <ul>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
    </ul>
  </nav>

  <main>
    <article>
      <h1>Main article heading</h1>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quae sunt igitur communia vobis cum antiquis, iis sic utamur quasi concessis; Nihil acciderat ei, quod nollet, nisi quod anulum, quo delectabatur, in mari abiecerat. Unum est sine dolore esse, alterum cum voluptate. Laboro autem non sine causa; Theophrasti igitur, inquit, tibi liber ille placet de beata vita? Nihil opus est exemplis hoc facere longius. Duo Reges constructio interrete. Graecum enim hunc versum nostis omnes Suavis laborum est praeteritorum memoria. Haec et tu ita posuisti, et verba vestra sunt.</p>

      <h2>Article secondary heading</h2>
      <p>Nos commodius agimus. A mene tu? Tantum dico, magis fuisse vestrum agere Epicuri diem natalem, quam illius testamento cavere ut ageretur. Tenesne igitur, inquam, Hieronymus Rhodius quid dicat esse summum bonum, quo putet omnia referri oportere? Nihilo beatiorem esse Metellum quam Regulum. Sed quanta sit alias, nunc tantum possitne esse tanta. Philosophi autem in suis lectulis plerumque moriuntur. Esse enim, nisi eris, non potes.</p>
      <p>Sunt enim quasi prima elementa naturae, quibus ubertas orationis adhiberi vix potest, nec equidem eam cogito consectari. Id Sextilius factum negabat. Quorum sine causa fieri nihil putandum est. Quae autem natura suae primae institutionis oblita est?</p>
    </article>
  </main>

  <aside>
    <h2 class="visually-hidden">Sidebar</h2>
    <section>
      <h3>Share</h3>
      <ul>
        <li><a href="#">Facebook</a></li>
        <li><a href="#">Twitter</a></li>
        <li><a href="#">Email</a></li>
      </ul>
    </section>
    <section>
      <h3>Recommended</h3>
      <ul>
        <li>
          <article>
            <h4><a href="#">Related article</a></h4>
            <p>Article description</p>
          </article>
        </li>
        <li>
          <article>
            <h4><a href="#">Related article</a></h4>
            <p>Article description</p>
          </article>
        </li>
      </ul>
    </section>
  </aside>

  <footer>
    <ul>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
    </ul>
  </footer>
</body>

If we look at our heading structure now, it will look like this (italics = visually hidden; bold = visible):

  • <h2> Primary [nav]
  • <h2> Secondary [nav]

Notice that our <h1> heading isn’t at the top of the list? It really doesn’t feel right having two <h2> headings above the <h1> heading.

This form of heading structure is actually allowed by the W3C so it doesn’t count as an accessibility fail. I still think that this is a pretty bad UX for screen reader users though. It is not a logical progression from <h1> to <h2>. It makes the most sense if the first heading you encounter on the page is a <h1> then progress into <h2> then <h3> and so on.

Making Heading 1 be the first heading

For a very long time, I thought the absolute best way to handle this conundrum was to make the <h1> visually hidden and have it be the very first piece of content on the page. The thing that everyone thinks is the <h1> actually becomes a <h2>.

This is what that sort of structure looks like in practice:

<style>
  .visually-hidden {
    position: absolute;
    opacity: 0;
    pointer-events: none;
  }
</style>

<!-- Don't do this -->
<body>
  <header>
    <h1 class="visually-hidden">Main article heading</h1>
    <a href="/" title="Go to home page">
      <img src="logo.png" alt="Site logo">
    </a>
    <nav>
      <h2 class="visually-hidden">Primary</h2>
      <ul>
        <li><a href="#">Primary nav</a></li>
        <li><a href="#">Primary nav</a></li>
        <li><a href="#">Primary nav</a></li>
        <li><a href="#">Primary nav</a></li>
      </ul>
    </nav>
    <form role="search" aria-label="site">
      <label>
        <span>Search</span>
        <input type="search"/>
      </label>
      <button type="submit">Submit</button>
    </form>
  </header>

  <nav>
    <h2 class="visually-hidden">Secondary</h2>
    <ul>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
    </ul>
  </nav>

  <main>
    <article>
      <h2><span class="visually-hidden">Body:</span> Main article heading</h2>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quae sunt igitur communia vobis cum antiquis, iis sic utamur quasi concessis; Nihil acciderat ei, quod nollet, nisi quod anulum, quo delectabatur, in mari abiecerat. Unum est sine dolore esse, alterum cum voluptate. Laboro autem non sine causa; Theophrasti igitur, inquit, tibi liber ille placet de beata vita? Nihil opus est exemplis hoc facere longius. Duo Reges constructio interrete. Graecum enim hunc versum nostis omnes Suavis laborum est praeteritorum memoria. Haec et tu ita posuisti, et verba vestra sunt.</p>

      <h3>Article secondary heading</h3>
      <p>Nos commodius agimus. A mene tu? Tantum dico, magis fuisse vestrum agere Epicuri diem natalem, quam illius testamento cavere ut ageretur. Tenesne igitur, inquam, Hieronymus Rhodius quid dicat esse summum bonum, quo putet omnia referri oportere? Nihilo beatiorem esse Metellum quam Regulum. Sed quanta sit alias, nunc tantum possitne esse tanta. Philosophi autem in suis lectulis plerumque moriuntur. Esse enim, nisi eris, non potes.</p>
      <p>Sunt enim quasi prima elementa naturae, quibus ubertas orationis adhiberi vix potest, nec equidem eam cogito consectari. Id Sextilius factum negabat. Quorum sine causa fieri nihil putandum est. Quae autem natura suae primae institutionis oblita est?</p>
    </article>
  </main>

  <aside>
    <h2 class="visually-hidden">Sidebar</h2>
    <section>
      <h3>Share</h3>
      <ul>
        <li><a href="#">Facebook</a></li>
        <li><a href="#">Twitter</a></li>
        <li><a href="#">Email</a></li>
      </ul>
    </section>
    <section>
      <h3>Recommended</h3>
      <ul>
        <li>
          <article>
            <h4><a href="#">Related article</a></h4>
            <p>Article description</p>
          </article>
        </li>
        <li>
          <article>
            <h4><a href="#">Related article</a></h4>
            <p>Article description</p>
          </article>
        </li>
      </ul>
    </section>
  </aside>

  <footer>
    <ul>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
    </ul>
  </footer>
</body>

Now we have a document outline that looks like this (italics = visually hidden; bold = visible):

  • <h1> Main article heading
    • <h2> Primary [nav]
    • <h2> Secondary [nav]
    • <h2> Body: Main article heading
      • <h3> Article secondary heading
    • <h2> Sidebar
      • <h3> Share
      • <h3> Recommended
        • <h4> Related article
        • <h4> Related article

This mostly feels right. The <h1> is at the top and it all flows down perfectly with the <h2> elements representing major page sections and the <h3> elements representing sub sections. The main awkward bit is that the actual <h1> and the thing that everyone thinks is a <h1> are essentially duplicates of one another.

It wasn’t until I wrote up the first version of this article, had it nearly published, then had it thrown out the window, that I started to think differently. I talked with two accessibility consultants about the issue. They both agreed that, though this is a clever technical solution to the problem, it detracts from the experience of the very people that it is trying to help.

The issue is that when every other website in the world places the <h1> heading at the top of the main content area, that is what screen reader users come to expect. When your site is the special snowflake that does things differently, it confuses screen reader users and it takes them some time to figure out how your heading structure is supposed to work.

So, with that in mind, I’ve settled on a new method for handling the labeling of sectioning elements. Basically, any time I would have used a visually hidden heading, I would use an aria-label attribute now instead. If the site has a large non-native speaking audience, I would use aria-labelledby instead of aria-label.

Concerns with the simplified outline algorithm spec

If the simplified outline algorithm is approved in its current state, we will actually need to start structuring our sites like the visually hidden <h1> example anyway (just replace the <h2>, <h3> and <h4> elements with <h1> elements).

The original spec aimed to create the outline through the labeling of sectioning elements. This new spec is clearly aimed at trying to create the outline purely through heading levels. The algorithm basically calculates the heading level based on the number of ancestor sectioning elements a heading has plus the heading’s base heading level value. It’s a bit more nuanced than that in the spec, but that is the general idea of how it works in simple terms.

The simplified algorithm currently makes no mention of aria-label or aria-labelledby. This means that those attributes will not help contribute to the document outline that the simplified algorithm generates. With a lack of aria-label support, this would mean labeling a sectioning element with aria-label could easily lead to skipped heading levels deeper in the tree.

<!-- Simplified algorithm skipped heading levels issue -->
<body>
  <main>
    <h1>Primary heading for the page</h1> <!-- interpreted as <h1> -->
    <p>This is some content</p>
  </main>

  <!-- sectioning elements increase heading levels -->
  <aside aria-label="Side bar"> <!-- aria-label does not contribute -->
    <section>
      <h1>Share</h1> <!-- interpreted as <h3> -->
      <ul>
        <!-- list of social media links -->
      </ul>
    </section>

    <section>
      <h1>Recommended articles:</h1>  <!-- interpreted as <h3> -->
      <ul>
        <!-- list of recommended articles -->
      </ul>
    </section>
  </aside>
</body>

The simplified spec also considers it invalid to:

It does, however, allow for there to be more than one level 1 heading at the root of the document, which I find very odd and bad for accessibility (though my concern about this seems to have been ignored).

I have voiced the issues I have with the spec and proposed possible solutions in the GitHub discussion.

For the moment, it is still best to use aria-label and/or aria-labelledby attributes instead of visually hidden headings to label sectioning elements. It isn’t worth diminishing the experience of our present day users for the sake of a spec that hasn’t even been finalized or accepted yet.

Using aria on the example layout sectioning elements

Using aria-label

This is what the HTML structure looks like if we use aria-label attributes to label the sectioning elements:

<body>
  <header>
    <a href="/" title="Go to home page">
      <img src="logo.png" alt="Site logo">
    </a>
    <nav aria-label="Primary">
      <ul>
        <li><a href="#">Primary nav</a></li>
        <li><a href="#">Primary nav</a></li>
        <li><a href="#">Primary nav</a></li>
        <li><a href="#">Primary nav</a></li>
      </ul>
    </nav>
    <form role="search" aria-label="site">
      <label>
        <span>Search</span>
        <input type="search"/>
      </label>
      <button type="submit">Submit</button>
    </form>
  </header>

  <nav aria-label="Secondary">
    <ul>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
  </ul>
  </nav>

  <main>
    <article>
      <h1>Main article heading</h1>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quae sunt igitur communia vobis cum antiquis, iis sic utamur quasi concessis; Nihil acciderat ei, quod nollet, nisi quod anulum, quo delectabatur, in mari abiecerat. Unum est sine dolore esse, alterum cum voluptate. Laboro autem non sine causa; Theophrasti igitur, inquit, tibi liber ille placet de beata vita? Nihil opus est exemplis hoc facere longius. Duo Reges constructio interrete. Graecum enim hunc versum nostis omnes Suavis laborum est praeteritorum memoria. Haec et tu ita posuisti, et verba vestra sunt.</p>

      <h2>Article secondary heading</h2>
      <p>Nos commodius agimus. A mene tu? Tantum dico, magis fuisse vestrum agere Epicuri diem natalem, quam illius testamento cavere ut ageretur. Tenesne igitur, inquam, Hieronymus Rhodius quid dicat esse summum bonum, quo putet omnia referri oportere? Nihilo beatiorem esse Metellum quam Regulum. Sed quanta sit alias, nunc tantum possitne esse tanta. Philosophi autem in suis lectulis plerumque moriuntur. Esse enim, nisi eris, non potes.</p>
      <p>Sunt enim quasi prima elementa naturae, quibus ubertas orationis adhiberi vix potest, nec equidem eam cogito consectari. Id Sextilius factum negabat. Quorum sine causa fieri nihil putandum est. Quae autem natura suae primae institutionis oblita est?</p>
    </article>
  </main>

  <aside aria-label="Sidebar">
    <section>
      <h2>Share</h2>
      <ul>
        <li><a href="#">Facebook</a></li>
        <li><a href="#">Twitter</a></li>
        <li><a href="#">Email</a></li>
      </ul>
    </section>
    <section>
      <h2>Recommended</h2>
      <ul>
        <li>
          <article>
            <h3><a href="#">Related article</a></h3>
            <p>Article description</p>
          </article>
        </li>
        <li>
          <article>
            <h3><a href="#">Related article</a></h3>
            <p>Article description</p>
          </article>
        </li>
      </ul>
    </section>
  </aside>

  <footer>
    <ul>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
    </ul>
  </footer>
</body>

Here is the layout in CodePen in case you want to have a play around with it (sorry mobile users, it’s not mobile friendly):

Using aria-labelledby

But let’s assume that you have a huge international audience that speaks all sorts of languages. In that case, it is better to use the aria-labelledby attribute. Here is what that would look like:

<body>

  <header>
    <a href="/" title="Go to home page">
      <img src="logo.png" alt="Site logo">
    </a>
    <nav aria-labelledby="primary-nav-label">
      <div id="primary-nav-label" hidden>Primary</div>
      <ul>
        <li><a href="#">Primary nav</a></li>
        <li><a href="#">Primary nav</a></li>
        <li><a href="#">Primary nav</a></li>
        <li><a href="#">Primary nav</a></li>
      </ul>
    </nav>
    <form role="search" aria-labelledby="search-label">
      <div id="search-label" hidden>Site</div>
      <label>
        <span>Search</span>
        <input type="search"/>
      </label>
      <button type="submit">Submit</button>
    </form>
  </header>

  <nav aria-labelledby="secondary-nav-label">
    <div id="secondary-nav-label" hidden>Secondary</div>
    <ul>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
      <li><a href="#">Secondary nav</a></li>
    </ul>
  </nav>

  <main>
    <article>
      <h1>Main article heading</h1>
      <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quae sunt igitur communia vobis cum antiquis, iis sic utamur quasi concessis; Nihil acciderat ei, quod nollet, nisi quod anulum, quo delectabatur, in mari abiecerat. Unum est sine dolore esse, alterum cum voluptate. Laboro autem non sine causa; Theophrasti igitur, inquit, tibi liber ille placet de beata vita? Nihil opus est exemplis hoc facere longius. Duo Reges constructio interrete. Graecum enim hunc versum nostis omnes Suavis laborum est praeteritorum memoria. Haec et tu ita posuisti, et verba vestra sunt.</p>

      <h2>Article secondary heading</h2>
      <p>Nos commodius agimus. A mene tu? Tantum dico, magis fuisse vestrum agere Epicuri diem natalem, quam illius testamento cavere ut ageretur. Tenesne igitur, inquam, Hieronymus Rhodius quid dicat esse summum bonum, quo putet omnia referri oportere? Nihilo beatiorem esse Metellum quam Regulum. Sed quanta sit alias, nunc tantum possitne esse tanta. Philosophi autem in suis lectulis plerumque moriuntur. Esse enim, nisi eris, non potes.</p>
      <p>Sunt enim quasi prima elementa naturae, quibus ubertas orationis adhiberi vix potest, nec equidem eam cogito consectari. Id Sextilius factum negabat. Quorum sine causa fieri nihil putandum est. Quae autem natura suae primae institutionis oblita est?</p>
    </article>
  </main>

  <aside aria-labelledby="sidebar-label">
    <div id="sidebar-label" hidden>Sidebar</div>
    <section>
      <h2>Share</h2>
      <ul>
        <li><a href="#">Facebook</a></li>
        <li><a href="#">Twitter</a></li>
        <li><a href="#">Email</a></li>
      </ul>
    </section>
    <section>
      <h2>Recommended</h2>
      <ul>
        <li>
          <article>
            <h3><a href="#">Related article</a></h3>
            <p>Article description</p>
          </article>
        </li>
        <li>
          <article>
            <h3><a href="#">Related article</a></h3>
            <p>Article description</p>
          </article>
        </li>
      </ul>
    </section>
  </aside>

  <footer>
    <ul>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
      <li><a href="#">Footer link</a></li>
    </ul>
  </footer>
</body>

Results of using aria

The heading structure for the site at this point looks like this:

  • <h1> Main article heading
    • <h2> Article secondary heading
    • <h2> Share
    • <h2> Recommended
      • <h3> Related article
      • <h3> Related article

The document outline (assuming that the original outline algorithm is implemented) looks like this:

  • <body> Document
    • <nav> Primary
    • <nav> Secondary
    • <article> Main article heading
      • <section (implied)> Article secondary heading
    • <aside> Sidebar
      • <section> Share
      • <section> Recommended
        • <article> Related article
        • <article> Related article

You might be thinking that the document outline looks a bit bare. Shouldn’t things like the header and footer and search be announced in there as well? Keep in mind that this is just the explicit stuff. We get a lot of implicit information provided to the user for free by using correct HTML elements in a good structure. This is a simplified version of how a screen reader user might experience the site:

  • [Text used in the <title> element]
    • Banner landmark
      • Link, site logo [(on focus) “go to home page”]
      • “Primary” navigation landmark
        • [List of navigation links]
      • “Site” search landmark
    • “Secondary” navigation landmark
      • [List of navigation links]
    • Main landmark
      • “Main article heading” article landmark, heading level 1
        • [Content]
        • Heading level 2, “Article secondary heading”
          • [Content]
    • “Sidebar” complimentary landmark
      • “Share” region landmark, heading level 2
        • [List of share links]
      • “Recommended” region landmark, heading level 2
        • List with 2 items
          • Item, “Related article” article landmark, heading level 3
            • [Content]
          • Item, “Related article” article landmark, heading level 3
            • [Content]
    • Content info landmark
      • [List of footer links]

As you can see, the site structure becomes quite clear and understandable to screen reader users when you factor in all of the extra implicit information that you get from using a good HTML structure

So, even though no browser supports the document outline algorithm, it is still worth putting some effort into thinking about the outline. Screen readers still tell users what type of section something is, where sections start and (sometimes) end (depends on the screen reader), and what the section label is. This means that your efforts to make a good document structure do not go to waste.

This type of structure comes with multiple benefits:

  • The page is 100% compatible with the document outline algorithm, future proofing it in-case the algorithm is ever implemented in a real browser.
  • The heading structure is completely logical.
  • Screen reader users navigating via headings can quickly jump to important information.
  • Screen reader users navigating via landmarks have lots of useful landmarks to move about the page.
  • Screen reader users are able to quickly understand what each section contains without having to read any of the content inside of them.
  • Content is grouped into semantic sections, so screen reader users do not get confused when leaving one section and entering another.
  • Search engines are able to better understand what information each section holds, which could potentially improve SEO.
  • Sighted users can take advantage of native browser features like Reader Mode.

What happens when you need <h7>?

There is one more sticking point when it comes to labeling sectioning elements that I haven’t addressed yet. Let’s say you have somehow managed to use up all six native heading levels and are now stuck needing one more. What do you do?

You could use the aria-labelledby technique if it is just for the sake of labeling a section. Let’s say that you really want this heading to appear in the heading structure though, or maybe you just want to avoid using IDs as much as possible. Whatever the reason, you need an <h7> element but <h7> doesn’t exist.

This is when the aria-level attribute comes to the rescue. The aria-level attribute will define what the heading level should be for elements that have role="heading" applied to them. This is how the W3C recommend creating a <h7> element:

<div role="heading" aria-level="7">This is a valid heading level 7 element</div>

Not all screen readers support this syntax. I know that JAWS treats these like <h2> elements rather than <h7> elements. If you know of any screen readers that this doesn’t work in, please report the bug to the screen reader developer and also leave a comment down below.

When I need to reach for an <h7>, I’ll often use the implied role="heading" from an <h6> element instead. The aria-level attribute will override the implicit “6” level of the <h6> element. This isn’t exactly endorsed by the W3C though. It is cleaner and will allow the heading to still appear in document outline and heading structure testing tools (though they will typically appear as <h6> or <h2> level headings, not as <h7> level headings).

<h6 aria-level="7">This is also a valid heading level 7 element</h6>

By using aria-level, you now have access to an infinite number of heading levels!

Does your site have a good structure?

Now that you know how to do a proper HTML structure, are you able to apply what you have learned to your website?

I found a pretty good browser extension called “Headings Map” that is available for both Chrome and Firefox. This extension will allow you to easily see both a flat heading structure representation of your site (i.e. how all browsers currently read the heading structure) and what the document structure looks like in a browser that supports the document outline algorithm (i.e. how a theoretical future browser that supports the outline algorithm would present the site structure). The HTML5 Outline view needs to be enabled in the settings menu first. This is to prevent users from being fooled into thinking that they are able to use the outline algorithm in production sites.

The Headings Map extension showing the flat heading structure result on the left and the result of the Document Outline Algorithm on the right.

Headings Map does not currently support the aria-label and aria-labelledby attributes on sectioning elements in the HTML5 outline tab. I have been talking with the developer and he is working on fixing this issue. If you know of a good document outline testing tool that already takes aria-label and aria-labelledby into account, please share a link to it in the comments.

Once you have a good document structure testing tool, check that both the heading structure and the document outline display a logical order with no missing headings or missing section labels anywhere.

Download and use a screen reader

The best way to test the implied semantics that you get from using correct HTML is to download an actual screen reader and try navigating your site with it. NVDA is one of the most used screen readers used by real screen reader users. It’s also free!

Be aware that the default settings for NVDA are optimized for usage by blind users. These default settings can drive sighted users insane. To enjoy your time using NVDA, perform the following steps (steps are based on a Windows set up, I don’t have a Mac):

  1. Download NVDA and install it
  2. Create a shortcut to NVDA in your taskbar (You will be opening and closing it regularly while testing)
  3. Open NVDA from the taskbar
  4. Find NVDA in your system tray (see below)
  5. Right-click the tray icon > “preferences” > “settings”
  6. Select “mouse” in the left panel
  7. Deselect “Enable mouse tracking” (You can now move your mouse without NVDA screaming at you)
  8. Press “OK”
  9. Right-click the tray icon > “Tools” > “Speech Viewer” (You can now see a log of everything NVDA says, don’t rely purely on this when testing though)
  10. In the Speech Viewer, check the “Show Speech Viewer on Startup” checkbox (It will open the Speech Viewer when you open NVDA)
  11. Familiarize yourself with some of the keyboard controls
  12. To close NVDA, Right-click the tray icon > “Exit” > “OK”
Location of NVDA on Windows

NVDA currently doesn’t support <article> and <section> elements. There is an issue on GitHub for supporting <article> elements. When I began writing this article <section> elements were already supported. Support for <section> seems to have dropped for some reason. This means NVDA should be fixed. It doesn’t mean you should stop using the correct semantics in your HTML.

Build your website with the document outline in mind then test the semantics with Headings Map and NVDA (or another screen reader). If you do, you will make your screen reader users very happy. You might even make the search engines happier too. 😊


Special thanks to Kevin Galvin (a principal consultant at me 2 accessibility) for advice around the usability issues of using a visually hidden <h1> element at the top of the page and suggesting aria-label as an alternative to using visually hidden headings.


How to Section Your HTML originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/how-to-section-your-html/feed/ 39 289070
CSS Grid in IE: Duplicate area names now supported! https://css-tricks.com/css-grid-in-ie-duplicate-area-names-now-supported/ Mon, 26 Nov 2018 15:06:59 +0000 http://css-tricks.com/?p=278973 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 …


CSS Grid in IE: Duplicate area names now supported! originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
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 &gt; .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] &gt; [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 &gt; .grid-cell will only work on .grid-cell elements that have been placed directly inside a .grid-beta element.
  • .grid-beta &gt; .grid-cell will have no effect on .grid-cell elements placed directly inside .grid-alpha elements.
  • .grid-beta &gt; .grid-cell will have no effect on .grid-cell elements nested deeply inside .grid-beta elements.
  • .grid-beta &gt; .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 &gt; .grid-cell {
  grid-area: area-name;
}

Autoprefixer will retain the selector and output something like this:

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

.grid &gt; .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 &gt; .content {
  grid-area: content-n; /* no side-bars */
}

.main-area &gt; .sidebar.first {
  grid-area: first-sb-f; /* first sidebar only */
}

.main-area &gt; .sidebar.second {
  grid-area: second-sb-s; /* second sidebar only */
}

.main-area.has-first-sidebar &gt; .content {
  grid-area: content-f; /* first sidebar only */
}

.main-area.has-second-sidebar &gt; .content {
  grid-area: content-s; /* second sidebar only */
}

.main-area.has-first-sidebar.has-second-sidebar &gt; .content {
  grid-area: content-fs; /* first and second sidebars */
}

.main-area.has-first-sidebar.has-second-sidebar &gt; .sidebar.first {
  grid-area: first-sb-fs; /* first and second sidebars */
}

.main-area.has-first-sidebar.has-second-sidebar &gt; .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 &gt; .content {
  -ms-grid-row: 1;
  -ms-grid-column: 1;
  grid-area: content-n; /* no side-bars */
}

.main-area &gt; .sidebar.first {
  -ms-grid-row: 1;
  -ms-grid-column: 1;
  grid-area: first-sb-f; /* first sidebar only */
}

.main-area &gt; .sidebar.second {
  -ms-grid-row: 1;
  -ms-grid-column: 2;
  grid-area: second-sb-s; /* second sidebar only */
}

.main-area.has-first-sidebar &gt; .content {
  -ms-grid-row: 1;
  -ms-grid-column: 2;
  grid-area: content-f; /* first sidebar only */
}

.main-area.has-second-sidebar &gt; .content {
  -ms-grid-row: 1;
  -ms-grid-column: 1;
  grid-area: content-s; /* second sidebar only */
}

.main-area.has-first-sidebar.has-second-sidebar &gt; .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 &gt; .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 &gt; .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 &gt; .content {
  -ms-grid-row: 1;
  -ms-grid-column: 2;
}

.main-area.has-second-sidebar &gt; .content {
  -ms-grid-row: 1;
  -ms-grid-column: 1;
}

.main-area.has-first-sidebar.has-second-sidebar &gt; .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 &gt; .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 &gt; .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 &gt; .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 &gt; .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 &gt; .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 &gt; .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 &gt; .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 &gt; .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 &gt; .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 &gt; .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 &gt; .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

Update: CodePen also now supports the newer /* autoprefixer grid: autoplace */ control comment. This means that you can now use CodePen to experiment with Autoprefixers autoplacement functionality. 😃

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: autoplace */ 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: autoplace */

.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: autoplace */
  grid-column: 1;
}

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

/* autoprefixer grid: autoplace */

.grid {
  display: -ms-grid;
  display: grid;
}

.non-ie .grid {
  /* autoprefixer grid: off */
  display: grid;

  /* autoprefixer grid: autoplace */
  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 &gt; .one {
  -ms-grid-row: 1;
  -ms-grid-column: 1;
}

.two {
  -ms-grid-row: 1;
  -ms-grid-column: 3;
  grid-area: two;
}

.grid.modifier &gt; .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 &gt; * {
  align-self: start;
  justify-self: end;
}

.place-grid { display: grid; }
.place-grid &gt; * {
  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 &gt; * {
  -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 &gt; * {
  -ms-grid-row-align: start;
  -ms-grid-column-align: end;
  place-self: start end;
}

.grid { [align/justify/place]-items } and .grid &gt; * { [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)

CSS Grid in IE: Duplicate area names now supported! originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
278973
CSS Grid in IE: Faking an Auto-Placement Grid with Gaps https://css-tricks.com/css-grid-in-ie-faking-an-auto-placement-grid-with-gaps/ https://css-tricks.com/css-grid-in-ie-faking-an-auto-placement-grid-with-gaps/#comments Fri, 06 Jul 2018 14:03:23 +0000 http://css-tricks.com/?p=273207 This is the third and final part in a three-part series about using CSS grid safely in Internet Explorer 11 (IE11) without going insane.

In Part 1, I covered some of the common misconceptions that people have about IE11’s …


CSS Grid in IE: Faking an Auto-Placement Grid with Gaps originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
This is the third and final part in a three-part series about using CSS grid safely in Internet Explorer 11 (IE11) without going insane.

In Part 1, I covered some of the common misconceptions that people have about IE11’s native CSS grid implementation. In Part 2, I showed the world how easy it actually is to write IE-friendly CSS grid code.

Today, I’m going step away from CSS grid for a moment to show you a flexbox technique that replicates basic CSS grid auto-placement functionality. This CSS grid replica will even look like a grid-gap has been applied to it. I need to be super clear though: this is not about how to make actual CSS grid auto-placement work in IE.

Article Series:

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

How to make a fake grid with cell gaps

Step 1: HTML

I’ll use this basic HTML as an example:




<div class="grid-wrapper">




<div class="grid">



<div class="grid-cell"></div>






<div class="grid-cell"></div>






<div class="grid-cell"></div>






<div class="grid-cell"></div>






<div class="grid-cell"></div>






<div class="grid-cell"></div>



</div>



  
</div>

Step 2: Border-box box sizing

The first thing that we need to do in the CSS is make sure that all of the boxes are being sized based on border-box rather than content-box. The best way to do that is using the box-sizing: border-box inheritance technique:

html {
  box-sizing: border-box;
}

*, *::before, *::after {
  box-sizing: inherit;
}

That will be applied globally. If you are working on an an existing project that doesn’t have box-sizing set to border-box, then change html in the snippet to a selector that targets the element you want to turn into a grid.

Step 3: Flex

Next, you will need to turn on some flexbox settings:

.grid {
  /* Forces equal cell heights */
  display: flex;
  flex-wrap: wrap;
}

Step 4: Widths

Now, set up your column widths. We’ll make ourselves a simple three-column grid:

.grid-cell {
  /* Sets column count */
  width: calc(100% / 3); /* calc() method */
  width: 33.33%; /* percentage method */
}

The calc() method allows the column widths to be changed a bit more easily. You state how many columns you want and the browser does the math for you. This is especially handy for when you need a larger number of columns, like 7 or 8. The browser support for calc() is strong but not as strong as a raw percentage value which has been supported by browsers since the dawn of CSS.

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

ChromeFirefoxIEEdgeSafari
19*4*11126*

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
1081071086.0-6.1*

The percentage method has slightly better browser support and can be a bit more stable in IE. If you don’t need to support Opera Mini, I would still recommend going with the calc() method first. Test in IE, and if the layout breaks, first try using 99.999% instead of 100% in the calc function (calc(99.999% / 3)). If that doesn’t work, then try switching to the percentage method. I will be using the calc() method in all of my examples. Note that if you are using a CSS pre-processor like SCSS, you can have the best of both worlds by getting the pre-processor to do the math for you. This comes at the cost of not being as easily able to edit or review the column counts in browser dev tools.

/* Set column count using SCSS */
.grid-cell {
  width: (100% / 3);
}

/* CSS output into the browser */
.grid-cell {
  width: 33.3333333333%;
}

Let’s give the grid cells some height and an inner box-shadow so that we can see what’s going on. I’m not adding border — I’ll be using that later. 😉

.grid-cell {
  /* So that we can see the grid cells */
  box-shadow: inset 0 0 0 1px #000;
  height: 100px;
}

.grid-wrapper {
  /* Allows us to see the edges of the grid */
  box-shadow: 0 0 10px 2px green;
}

You should now have something that looks like this:

A basic 3 x 2 grid with no gaps

That’s boring though, right? Everyone knows how to do that. Where are these grid gaps I keep talking about? I want my gaps!!!

Step 5: Border

Here’s where we get to the interesting part. Since we set box-sizing to border-box, the 33.33% width now includes the border. What this means is that we can start safely mixing fixed and percentage based units! 😃

.grid {
  /* Creates an equal outer gap */
  padding: 20px 0 0 20px;
}

.grid-cell {
  /* Creates gaps */
  border: 0 solid transparent;
  border-width: 0 20px 20px 0;
}

This results in something that looks like a grid with equal spacing everywhere:

A 3 x 2 grid with equal space between each cell and the outer edges of the grid

To help give you a better idea of what is going on, take a look at the following image:

Color-coded diagram of the grid

The blue area on the top and left sides of the grid is the padding for the .grid element. The yellow outlines show the area that each .grid-cell element takes up. The red areas on the bottom and right sides of each cell are the border for each .grid-cell element.

That might be the look that you actually want. On the other hand, that isn’t what a grid with a grid-gap setting looks like. That is why we have another step.

Step 6: Margin and overflow

In order to get the grid pressing hard up against the edges of its container, we need a bit of help from negative margins and overflow: hidden:

.grid {
  /* Pulls grid cells hard against edges */
  margin: -20px;
}

.grid-wrapper {
  /* Prevents odd margin behavior */
  overflow: hidden;
}

We need to apply overflow: hidden to prevent this from happening:

Top and bottom negative margin is ignored if overflow is not hidden

Applying the negative margin and overflow: hidden will get us to this beautiful recreation of basic grid-gap functionality:

A 3 x 2 grid that looks identical to a CSS grid with a gap setting

The top and left padding on the grid is actually optional. You can opt to leave off the padding and change the margin value as shown below if you prefer:

.grid {
  /* Margin needs to be this if leaving off the top and left .grid padding */
  margin: 0 -20px -20px 0;
}

But, hold on! The job isn’t quite over yet. Take a look at what happens if we add a background color to one of the grid cells:

A pink background applied to the top left cell overflows into the grid gap

Not exactly what we want, so there is one more step.

Step 7: background-clip

In order to prevent the grid cell background from bleeding into our fake grid-gap, we need to add background-clip: padding-box to it.

.grid-cell {
  /* Prevents background bleed */
  background-clip: padding-box;
}

Now we have this:

The background bleed has been fixed!

If you have never heard of the background-clip property before, you might be worried about browser support… well don’t be. background-clip has been around since IE9!

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

ChromeFirefoxIEEdgeSafari
1549127

Mobile / Tablet

Android ChromeAndroid FirefoxAndroidiOS Safari
1081074.47.0-7.1

Step 8: Media Queries!

Most of the time, grids need to be able to change the number of columns that they have as they grow and shrink. Using other methods can be a massive pain. You might have to calculate a bunch of nth-childs so you can remove the right margin or whatever. With this method, you only have to change a single value! 😃

.grid-cell {
  /* Sets the default column count */
  width: calc(100% / 1); /* 1 column */
}

@media (min-width: 400px){
  .grid-cell {
    width: calc(100% / 2); /* 2 columns */
  }
}

@media (min-width: 600px){
  .grid-cell {
    width: calc(100% / 3); /* 3 columns */
  }
}
3 x 2 grid with gaps
2 x 3 grid with gaps
1 x 6 grid with gaps

Here’s how it looks when we put it all together:

See the Pen Fake grid by Daniel Tonon (@daniel-tonon) on CodePen.

Ain’t nobody got time for dat!

That’s a lot of work compared to what modern Grid can do in just three lines of CSS! To make the task easier, I created an SCSS-powered mixin I call Gutter Grid. Once Gutter Grid is installed in the project, you can quickly create a three-column grid with 20px gaps using the following SCSS code:

.grid-wrapper {
  overflow: hidden; /* Need this for the chrome bug */
}

.grid {
  @include grid($cols: 3, $gutter: 20px);
}

You can write it even shorter like this if that feels too verbose:

.grid-wrapper {
  overflow: hidden;
}

.grid {
  @include grid(3, 20px);
}

Gutter Grid comes pre-built with a few sets of breakpoints so you may not have to write any breakpoints at all if your grid spans the whole page! If you do need custom breakpoints, though, then Gutter Grid lets you easily customize them like so:

// Verbose custom breakpoints
@include grid($cols: 7, $gutter: 20px, $breakpoints: (
  4 : 960px, // At 960px or below, there will be 4 columns
  2 : (max, 600px), // You can use mq-scss syntax here as well
  1 : 480px,
));

// Short version  
@include grid(7, 20px, (
  4 : 960px,
  2 : 600px,
  1 : 480px,
));

As you might have noticed in the example, Gutter Grid also supports the same media query syntax that this thing called mq-scss uses. If you are wondering what that is, well, it’s a Sass mixin that I created that makes writing and managing media queries about a million times easier. Using mq-scss statements to dictate column count allows for very fine control over when the column count changes.

Adding shadows to the grid cells

Since we are working with shadows now, I’ll take the box shadow off of the example image. Our starting grid looks like this now:

A basic 3 x 2 grid with gaps and a green border around the outside

If we try to add an outer box-shadow to each grid cell right now… well it doesn’t look so good:

Adding shadows to the grid cells causes the shadows to be out of alignment

Let’s fix that.

Step 1: New HTML

In order to create nice shadows, we need to add an extra div inside each grid cell. You can’t really use ::before or ::after for this since they are unable to contain HTML content inside of them.



<div class="grid-wrapper">



<div class="grid">




<div class="grid-cell">



<div class="grid-cell-inner"></div>



</div>







<div class="grid-cell">



<div class="grid-cell-inner"></div>



</div>







<div class="grid-cell">



<div class="grid-cell-inner"></div>



</div>







<div class="grid-cell">



<div class="grid-cell-inner"></div>



</div>







<div class="grid-cell">



<div class="grid-cell-inner"></div>



</div>







<div class="grid-cell">



<div class="grid-cell-inner"></div>



</div>




</div>



</div>

Step 2: Flex it

Now, we need to make each grid cell a flex container. This will allow the inner part of the grid cell to take up the full height of its parent. We will also need to set the inner element to a width of 100%. This ensures that it will take up the full dimensions of its parent, both horizontally and vertically:

.grid-cell {
  /* Forces inner to take up full grid cell height */
  display: flex;
}

.grid-cell-inner {
  /* Forces inner to take up full grid cell width */
  width: 100%;
}

Let’s see what we get if we try adding box shadows to the inner elements:

.grid-cell-inner {
  box-shadow: 0 0 10px 3px blue;
}
Box shadows appear around grid cells but are getting cut off

That’s much nicer, but it is still not quite there yet. The hidden overflow that we are using to prevent the Chrome bug is getting in the way.

Step 3: Hacky padding

So, we need to allow overflow but still avoid this odd margin behavior. The only other safe way I’ve found to do that is with padding. By adding 1px of padding to the top and bottom of the outer grid wrapper element, it will fix the Chrome bug.

.grid-wrapper {
  /* Prevents odd margin behaviour in Chrome */
  padding: 1px 0;
}

This comes at the expense of having an extra 1px of space at the top and bottom of the grid. The image below demonstrates how this ends up looking. The shadows have been lightened to show the 1px gap more clearly.

1px of padding at the top and bottom of the grid

Note: You can avoid the 1px of top padding by opting not to include the top padding gap value on the grid element. The 1px of bottom padding can’t be avoided though.

A border width applied to the outer grid wrapper will also work, so technically I didn’t need to apply the padding to the example above. Most of the time, if we are applying a shadow to grid cells, then we probably don’t want to see a border wrapped around them. The above example was more demonstrating how minor the padding is.

Update: You can avoid the odd margin behavior completely by using 0.1px worth of padding rather than 1px. This will get rounded down to 0px in the browser but still prevent the odd margin behavior from occurring. This means that the edges of the grid can sit hard up against the edges of it’s container whilst still allowing overflow content to be visible! Thanks to Lu Nelson for his suggestion in the comments! 😁

This is what the grid looks like without the outer border:

3 x 2 shadow cell grid
2 x 3 shadow cell grid
1 x 6 shadow cell grid

Here is a Pen showing the final product:

See the Pen Fake grid with shadows by Daniel Tonon (@daniel-tonon) on CodePen.

Gutter Grid shadows

Let’s cover how to add shadows on Gutter Grid cells. You can use the same HTML structure we used in the previous example.

Now, apply this SCSS to create a three-column grid that has a 20px gutter:

.grid {
  @include grid(3, 20px, $inners: true);
}

This new $inners: true property tells Gutter Grid that the grid cells each have a single child element that needs to take up the full height and width of its parent grid cell.

Instead of using overflow: hidden, use 0.1px of bottom padding on the wrapper element (updated based on Lu Nelson’s suggestion).

.grid-wrapper {
  padding-bottom: 0.1px;
}

Gutter Grid will not output a top outer gutter if it doesn’t need to. This helps avoid issues around the negative margin quirk. After all, if there is no top outer gutter to negate with a faulty negative margin, then there is no bug to worry about. The bottom outer gutter is still a problem, though, and that is why we need the 0.1px of bottom padding. This 0.1px of bottom padding gets rounded down to 0px in the browser while still fixing the margin quirk leading to no gap at the bottom of the grid.

Now, add your shadows and you’ve got yourself a Gutter Grid with shadow cells!

.grid-cell-inner {
  box-shadow: 0 0 10px 3px blue;
}
3 x 2 shadow cell grid

I’ve only scraped the surface of what Gutter Grid can do in this article. Make sure to read the full documentation to learn what else it’s capable of.

We come to the end of our IE grid adventure

I hope you have enjoyed this foray into the world of IE and CSS grid. Make sure to read through Part 1 and Part 2 if you haven’t already. Having read all three articles, you will now be fully equipped to make some truly stunning layouts that look just as good in IE as they do in modern browsers.

If you ever see anyone complaining about not being able to use CSS grid because of IE, you know what to do. Playfully slap them on the head for being so foolish and send them here to get the truth.

Now go forth, my friends, and build some grids! 😃🎉

Article Series:

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

CSS Grid in IE: Faking an Auto-Placement Grid with Gaps originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-grid-in-ie-faking-an-auto-placement-grid-with-gaps/feed/ 18 273207
CSS Grid in IE: CSS Grid and the New Autoprefixer https://css-tricks.com/css-grid-in-ie-css-grid-and-the-new-autoprefixer/ https://css-tricks.com/css-grid-in-ie-css-grid-and-the-new-autoprefixer/#comments Wed, 04 Jul 2018 15:00:17 +0000 http://css-tricks.com/?p=273125 In Part 1 of this series, I debunked a few misconceptions that many people have around the Internet Explorer (IE) implementation of CSS grid. This article builds on that knowledge. It would be best to go back and read …


CSS Grid in IE: CSS Grid and the New Autoprefixer originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
In Part 1 of this series, I debunked a few misconceptions that many people have around the Internet Explorer (IE) implementation of CSS grid. This article builds on that knowledge. It would be best to go back and read that article first if you haven’t already.

Today I’m going to be tackling the biggest misconception of all: that utilizing the IE implementation of CSS grid is extremely difficult. You can easily use CSS grid in IE right now without having to give it any sort of crappy fallback layout. It really isn’t that hard.

Article Series:

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

Giving IE a crappy fallback layout is a bit of a tough sell to clients. It is especially tough if this is for an intranet where 90% of the users are using IE. If you want IE compatibility, first you need to resign to the fact that you can’t use all of the fancy bells and whistles that can be found in the modern CSS grid spec. Don’t let this get you down though, you would be amazed by how much IE11 can handle, especially if it has some help from Autoprefixer.

Before I get too far into this, you need to know that this isn’t a general “how to use CSS grid” article. This article is for those that understand CSS grid, but are either too afraid or not allowed to use it, thanks to IE.

I’ll be assuming that you already have a good understanding of how to use CSS grid in modern browsers for this article. If you aren’t sure about how to use CSS grid yet, go watch Rachel Andrew’s excellent CSS grid video tutorial series. After that, go play some levels of the browser game Grid Garden to get some hands-on CSS grid experience. Once you understand the basics of how to use CSS grid in modern browsers, come back here and learn how to use it in a way that produces identical results in IE10 and 11.

Setting up Autoprefixer

Before you do anything else, you will want to get Autoprefixer up and running. Autoprefixer is a tool used for automatically adding browser-specific CSS prefixes to your CSS so that you don’t have to. It’s a bit like Babel but for CSS. (We have a great overview by the creator of Autoprefixer here on CSS Tricks.)

If you have never used Autoprefixer (or any build tool) before, then first install Node on your computer then follow this guide on how to get a basic workflow up and running. The bit in the guide specifically about Autoprefixer is found here. (I recommend the Gulp setup over Grunt if you aren’t sure which to go with.)

If you are already using Autoprefixer in your projects, make sure to update it to the latest version using this command:

npm i autoprefixer@latest -D

Some features used in the latest version of Autoprefixer are not supported in PostCSS version 5. This means that you may also need to update your version of PostCSS to at least version 6. If using Gulp, that means updating gulp-postcss to at least v7.0.0. If using Grunt, you may need to update grunt-postcss to at least v0.9.0.

Once you have an environment up and running, you will need to set the Autoprefixer grid setting to true. Without that setting turned on, Autoprefixer will not be able to do any of the cool stuff that I’m about to show you.

Update: In version 9.3.1 you can enable CSS Grid translations by simply adding a control comment to your CSS file. Learn more about it in Part 4.

The exact method for turning the grid setting on depends a lot on how you compile your code. If you were using the guide that I linked to earlier, you would have seen this snippet of code:

var processorsArray = [
  // snipped for brevity
  require('autoprefixer')({ browsers: ['last 2 versions', 'ie 6-8', 'Firefox &gt; 20']  })
];

WordPress is converting all of the “>” symbols inside code snippets to &gt;. There doesn’t seem to be a way to stop it from doing that. Please pretend that all instances of &gt; inside code sippets are actually the “>” symbol.

Add grid: 'autoplace' to the options to turn grid prefixing on:

var processorsArray = [
  // snipped for brevity
  require('autoprefixer')({ grid: 'autoplace', browsers: ['last 2 versions', 'ie 6-8', 'Firefox &gt; 20']  })
];

Update: In version 9.4.0 limited autoplacement support was introduced to Autoprefixer. For backwards compatibility reasons, the new recommended setting for enabling grid translations is now grid: 'autoplace'. I have updated all the code snippets to reflect this new recommendation.

By the way, the browser settings in the tutorial are pretty outdated and will be outputting far more prefixes than you actually need. Just going with “>1%” will mean that when a browser dies, Autoprefixer will automatically stop writing prefixes for that browser. It bases browser usage on global browser data retrieved from caniuse.com.

So, these are the Autoprefixer settings that you should end up using:

var processorsArray = [
  require('autoprefixer')({ grid: 'autoplace', browsers: ['&gt; 1%'] })
];

Now onto the fun stuff! 😃

Autoprefixer has new super powers!

You may have read Rachel Andrew’s article “Should I try to use the IE implementation of CSS Grid Layout?” That article is all about CSS grid and understanding it’s support in IE10 and 11. It’s a great article and very useful for understanding IE’s limitations. Definitely still worth a read if you haven’t already checked it out. Note that it is extremely outdated in terms of its information on Autoprefixer though.

Autoprefixer has come a long way since Rachel wrote her IE11 CSS grid article and Autoprefixer now supports far more CSS grid translations. Take a look at this new, updated version of Rachel’s table based on Autoprefixer version 8.6.4. Note that items in bold represent a change in Autoprefixer support compared to Rachel’s article:

CSS Grid Property IE10 Implementation Autoprefixer? Notes
grid-template-columns -ms-grid-columns Yes
grid-template-rows -ms-grid-rows Yes
grid-template-areas NA Yes Autoprefixer uses this to understand what the grid looks like but does not add any extra properties.
grid-template NA Yes Shorthand for grid-template-columns, grid-template-rows, and grid-template-areas
grid-auto-columns NA No IE doesn’t support auto-placement
grid-auto-rows NA No IE doesn’t support auto-placement
grid-auto-flow NA No IE doesn’t support auto-placement
grid NA No See this GitHub issue as to why this isn’t supported
grid-row-start -ms-grid-row Yes1 Span syntax requires grid-row-end to be defined
grid-column-start -ms-grid-column Yes1 Span syntax requires grid-column-end to be defined
grid-row-end NA Yes1 grid-row-start must be defined
grid-column-end NA Yes1 grid-column-start must be defined
grid-row NA Yes1
grid-column NA Yes1
grid-area NA Yes2 Autoprefixer translates the grid area into column/row coordinates
grid-row-gap NA Yes3 Generates extra rows/columns in IE.
grid-column-gap NA Yes3 Generates extra rows/columns in IE.
grid-gap NA Yes3 Generates extra rows/columns in IE.
NA -ms-grid-column-span Yes Translated from grid-column-end and grid-area.
NA -ms-grid-row-span Yes Translated from grid-row-end and grid-area.
align-self -ms-grid-row-align Yes
justify-self -ms-grid-column-align Yes

Table disclaimers

I will cover these in far more detail later in the article:

  1. Autoprefixer cannot prefix negative integers.
  2. Each grid element must have unique area names.
  3. Autoprefixer only prefixes grid-gap if both grid-template-areas and grid-template-columns have been defined. It also cannot inherit grid-gap through media queries.

grid-template-areas is your new best friend

As you can see from the updated (and much more positive-looking) table, there are a lot of cool new features in Autoprefixer now. The most important of which is its support for grid-template-areas (and by extension grid-template). By supporting grid-template-areas, Autoprefixer now understands exactly what your grid looks like. This paved the way for Autoprefixer to also support (in a limited capacity) grid-gap.

Note that Autoprefixer still does not support the shortcut grid property. This was an intentional design decision that you can learn more about in this GitHub issue. The short story is that the grid property is not only a shortcut for the templating settings but also the auto-placement settings. Since IE can’t do auto-placement and the grid-template property can essentially do anything that the grid property can do (that IE can handle), it was decided that the grid property wasn’t worth supporting.

Back when Rachel wrote her article, this was the sort of grid code you would need to write in order to support IE:

/* IE-friendly source code 26 November 2016 */
/* Code needed to make Autoprefixer work properly */

.grid {
  display: grid;
  grid-template-columns: 1fr 10px 1fr;
  grid-template-rows: 100px 10px 100px;
}

.cell-A {
  grid-column: 1;
  grid-row: 1;
}

.cell-B {
  grid-column: 3;
  grid-row: 1;
}

.cell-C {
  grid-column: 1;
  grid-row: 3;
}

.cell-D {
  grid-column: 3;
  grid-row: 3;
}

Now, you can write code like this instead:

/* Today’s IE-friendly source code */
/* Autoprefixer can now make this code IE safe */

.grid {
  display: grid;
  grid-gap: 10px;
  grid-template:
    "a   b" 100px
    "c   d" 100px /
    1fr  1fr;
}

.cell-A {
  grid-area: a;
}

.cell-B {
  grid-area: b;
}

.cell-C {
  grid-area: c;
}

.cell-D {
  grid-area: d;
}

Autoprefixer will take the above code and translate it into this much more IE-friendly code for you:

/* Autoprefixer’s IE-friendly translation */

.grid {
 display: -ms-grid;
 display: grid;
 grid-gap: 10px;
 -ms-grid-rows: 100px 10px 100px;
 -ms-grid-columns: 1fr 10px 1fr;
 grid-template:
   "a   b" 100px
   "c   d" 100px /
   1fr  1fr;
}

.cell-A {
  -ms-grid-row: 1;
  -ms-grid-column: 1;
  grid-area: a;
}

.cell-B {
  -ms-grid-row: 1;
  -ms-grid-column: 3;
  grid-area: b;
}

.cell-C {
  -ms-grid-row: 3;
  -ms-grid-column: 1;
  grid-area: c;
}

.cell-D {
  -ms-grid-row: 3;
  -ms-grid-column: 3;
  grid-area: d;
}

Best of all, you can then simply change the grid-template or grid-template-areas property with a media query and Autoprefixer will automatically update all of the grid cell coordinates for you:

/* Changing a grid-template with a media-query */

.grid {
  display: grid;
  grid-gap: 10px;
  grid-template:
    "a   b" 100px
    "c   d" 100px
    "e   f" 100px /
    1fr  1fr;
}

@media (min-width: 600px){
  .grid {
    /* Duplicating the grid-gap is no longer necessary as of Autoprefixer v9.1.1 */
    grid-gap: 10px;
    grid-template:
      "a   b   c" 100px
      "d   e   f" 100px /
      1fr  1fr 1fr;
  }
}

.cell-A {
  grid-area: a;
}

.cell-B {
  grid-area: b;
}

.cell-C {
  grid-area: c;
}

.cell-D {
  grid-area: d;
}

.cell-E {
  grid-area: e;
}

.cell-F {
  grid-area: f;
}

The above gets translated into this IE-friendly code:

/* Autoprefixer’s IE-friendly media query translation */

.grid {
  display: -ms-grid;
  display: grid;
  grid-gap: 10px;
  -ms-grid-rows: 100px 10px 100px 10px 100px;
  -ms-grid-columns: 1fr 10px 1fr;
  grid-template:
    "a   b" 100px
    "c   d" 100px
    "e   f" 100px /
    1fr  1fr;
}

@media (min-width: 600px) {
  .grid {
    /* Duplicating the grid-gap is no longer necessary as of Autoprefixer v9.1.1 */
    grid-gap: 10px;
    -ms-grid-rows: 100px 10px 100px;
    -ms-grid-columns: 1fr 10px 1fr 10px 1fr;
    grid-template:
      "a   b   c" 100px
      "d   e   f" 100px /
      1fr  1fr 1fr;
  }
}

.cell-A {
  -ms-grid-row: 1;
  -ms-grid-column: 1;
  grid-area: a;
}

.cell-B {
  -ms-grid-row: 1;
  -ms-grid-column: 3;
  grid-area: b;
}

.cell-C {
  -ms-grid-row: 3;
  -ms-grid-column: 1;
  grid-area: c;
}

.cell-D {
  -ms-grid-row: 3;
  -ms-grid-column: 3;
  grid-area: d;
}

.cell-E {
  -ms-grid-row: 5;
  -ms-grid-column: 1;
  grid-area: e;
}

.cell-F {
  -ms-grid-row: 5;
  -ms-grid-column: 3;
  grid-area: f;
}

@media (min-width: 600px) {
  .cell-A {
    -ms-grid-row: 1;
    -ms-grid-column: 1;
  }
  
  .cell-B {
    -ms-grid-row: 1;
    -ms-grid-column: 3;
  }
  
  .cell-C {
    -ms-grid-row: 1;
    -ms-grid-column: 5;
  }
  
  .cell-D {
    -ms-grid-row: 3;
    -ms-grid-column: 1;
  }
  
  .cell-E {
    -ms-grid-row: 3;
    -ms-grid-column: 3;
  }
  
  .cell-F {
    -ms-grid-row: 3;
    -ms-grid-column: 5;
  }
}

The duplicate media query might look a bit ugly but I assure you that this is the best possible way that Autoprefixer can handle the media query translation. The new IE grid cell coordinates could not be placed into the same media query as the grid template definition. Doing so would lead to one of two possible outcomes.

One outcome is that the default grid cell positions would override the altered positions stated in the media query. This would cause the media query to have no effect in IE.

The other outcome would be for Autoprefixer to shift all of the styles inside the media query (yes, that includes all of the styles that you wrote yourself) to another location in the style sheet, potentially causing horrific CSS specificity issues. You can learn more about the thinking behind this decision in the now closed GitHub issue for it.

Autoprefixer still can’t save you from everything

Even Superman can’t always save everyone and Autoprefixer is no different. While Autoprefixer is able to cut down on a lot of the workload involved in making our grids IE-compatible, it can’t fix everything. It can only translate things that IE can understand. These are the many critical things that you need to be aware of if you don’t want to open the site up in IE one day and have it blow up in your face.

Grid Gap has limited support

As you may have seen in that last example, grid-gap isn’t currently able to be inherited through media queries. If you want to use grid-gap, you will need to duplicate the grid-gap setting across all of the media queries where you define a grid template for that grid.

Update: The grid-gap media query inheritance bug was fixed in Autoprefixer version 9.1.1. It is now safe to use grid-gap in your default grid template and then let the gap setting trickle down into all of your media query grid templates.

That isn’t the only problem around using grid-gap though. It is only supported by Autoprefixer when both grid-template-areas and grid-template-columns have been defined.

Autoprefixer adds grid-gap support by using grid-template-areas to understand what your grid looks like. It then takes your grid-template-columns and grid-template-rows definitions and injects the grid-gap value between each row and column, creating extra rows and columns in IE.

If you try to use grid-gap on its own without grid-template-areas, Autoprefixer has no way of knowing what cell belongs to what grid. Without that critical knowledge, it cannot safely inject the extra columns and rows that IE needs.

That explains grid-template-areas but why do we also need to define grid-template-columns? Shouldn’t something like this be just as easy for Autoprefixer to translate?

.grid {
  display: grid;
  grid-gap: 20px;
  grid-template-areas:
    "a  b  c"
    "d  e  f";
}

.cell-a {
  grid-area: a;
}

.cell-f {
  grid-area: f;
}

Rows and columns in CSS grid default to a value of auto so can’t Autoprefixer just add something like -ms-grid-columns: auto 20px auto 20px auto;? It does that for rows, so why can’t it do the same thing for columns?

Well my inquisitive friend, I explained in Part 1 that auto in IE acts a bit differently to auto in modern browsers. When columns are set to auto in IE, they will always shrink down to the value of max-content. Modern grid columns, on the other hand, will expand to 1fr if there are no other fr units being used in that grid template declaration. This discrepancy can cause a massive difference in appearance between the modern and the IE version of a grid. The Autoprefixer team felt that it was too dangerous to make assumptions about this, so they made grid-template-columns a mandatory setting in order for grid-gap to take effect.

So that explains why Autoprefixer doesn’t support grid-gap when grid-template-columns is missing. If auto behaves so differently in IE, then why does Autoprefixer support grid-gap without the user explicitly having to define grid-template-rows? Isn’t that just as bad?

Not really. When you set display: grid; on something, it’s width will grow to the full width of its container. Its height, on the other hand, typically shrinks to the height of its content. Of course this isn’t always the case. There are a number of reasons why a grid might be taller than its content. If the grid is also a flex item and the flex container is taller than the grid, then that would be one reason why the grid might be taller than its content. In general though, if there are no other forces involved, then a grid container will always be the same height as its content.

Since the height of a typical grid is the same height as its content, in most cases, auto in both IE and modern browsers will behave identically to one another. It will only differ in functionality if the height of the grid exceeds the height of the content inside of it. For the best balance between user convenience and browser consistency, the Autoprefixer team made a choice. They decided that supporting a missing grid-template-rows property but not a missing grid-template-columns property was the best way to handle grid-gap support.

No auto-placement! No auto-placement! No auto-placement!

Update: Though IE still doesn’t support autoplacement, in version 9.4.0, limited autoplacement support was introduced to Autoprefixer. When reading this section, keep in mind that it was written at a time when absolutely no autoplacement support was available.

I really can’t say this enough. The most important thing to remember when using CSS grid in IE is that everything must be placed manually. The instant you start thinking about using auto-placement is the instant your site blows up in IE. I have a method for dealing with grids that have an unknown number of cells in them. I’m covering that in Part 3 of this series. The main thing to know right now is that if you want to use CSS grid in an IE-friendly way, you should only ever use it if there are a known number of cells for a known number of rows and columns.

It’s this lack of auto-placement in IE that makes having access to grid areas through Autoprefixer such a blessing. Instead of having to calculate all of the coordinates manually, you can instead name each cell and then let Autoprefixer do the math for you. When using media queries, you only need to redefine the grid template and autoprefixer will recalculate all of the coordinates for you. You rarely have to do any column or row counting. It’s great!

Area names must be unique

Update: This section is now out of date. Autoprefixer 9.3.1 supports duplicate area names. Read more about it in Part 4 of this series.

The ability to use grid-template-areas is one of Autoprefixer’s greatest strengths, though it has its limits. Autoprefixer does not have any access to the DOM. Due to this, Autoprefixer is entirely dependent on using the area name for understanding where each cell needs to be placed in the grid. This can cause clashes if you use the same area name twice in the same stylesheet.

Here is a small example. Early in the stylesheet, Grid Alpha has grid-template-areas: "delta echo". Later in the stylesheet, Grid Beta has grid-template-areas: "echo delta". We say that our grid cell belongs to area echo… so does it go in column 1 or 2?

.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;
}

Modern browsers know exactly what column to place the grid cell in because they have access to the DOM. If the cell is placed inside Grid Alpha, it will go in the first column. If it is placed in Grid Beta it will go in the second column. Autoprefixer can only read your CSS. It has no idea if the grid cell is placed in Grid Alpha or Grid Beta. All it knows is that the grid cell needs to be placed in the “echo” area. Autoprefixer resolves this conundrum by going with whichever one came last in the stylesheet. In this case, Autoprefixer will honor Grid Beta’s "echo delta" areas definition since it occurs last. If you placed the grid cell inside Grid Alpha, it would look great in modern browsers but it would be placed in the wrong column in IE.

This also means that you can’t really use the strategy of giving a component a single designated area name that is repeatedly referenced. Referencing that area name in more than one grid-template-areas property will break IE for sure. Every single area name across every single grid in your style sheet needs to be unique or Autoprefixer will freak the heck out.

The easiest way to ensure that each area name is unique is adopting a BEM style approach to naming the areas.

.grid-alpha {
  grid-template-areas: "grid-alpha__delta  grid-alpha__echo";
}

.grid-beta {
  grid-template-areas: "grid-beta__echo  grid-beta__delta";
}

.grid-cell {
  /* No more conflict :) */
  -ms-grid-column: 2;
  grid-area: grid-alpha__echo;
}

This can be pretty verbose if there are lots of columns and rows. You might want to shorten it down to an abbreviation instead. Of course, the less verbose your area names, the more chance there is of a conflict.

.grid-alpha {
  grid-template-areas: "ga_delta  ga_echo";
}

.grid-beta {
  grid-template-areas: "gb_echo  gb_delta";
}

.grid-cell {
  -ms-grid-column: 2;
  arid-area: ga_echo;
}

There is one major exception. Grid areas in media queries are allowed to be duplicates of other areas as long as the area names defined in the media query are targeted at the same element. Without this exception, it would be impossible to change the grid areas based on screen size. The main thing to remember is that each grid has to have its own set of unique area names that must not be shared with any other grids.

@media (min-width: 600px) {
  .grid-one {
    grid-template-areas:
      "alpha bravo"
      "alpha charlie";
   }
}

@media (min-width: 900px) {
  .grid-one {
    /* This is fine */
    /* It is targeting the same element */
    grid-template-areas:
      "alpha bravo charlie";
  }
}

@media (min-width: 900px) {
  /* NOT FINE! */
  /* The "alpha" area is being reused on a different element! */
  .grid-two {
    grid-template-areas:
      "alpha delta";
  }
}

Autoprefixer has limited column and row spanning support

There are only two properties in IE’s implementation of CSS grid that will help you span multiple columns. The main one being -ms-grid-column/row-span which tells IE how many columns/rows to span. The other being -ms-grid-column/row which tells IE where to start counting from.

.grid-cell {
  -ms-grid-column-span: 2; /* number of cells to span */
  -ms-grid-column: 1; /* starting cell */
}

In modern browsers, you have access to far more options.

Autoprefixer friendly

Out of the modern ways to span multiple cells, Autoprefixer fully supports the following. Feel free to use any of these methods as much as you like:

Specify a starting line and the number of lines to span (similar to IE):

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

Specify an end line, then span backwards:

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

Specify a starting line and an end line directly:

.grid-cell {
  grid-column: 1 / 3;
}

Specify only a number of cells to span using grid-column/row-end. Remember that IE can’t do auto-placement though. A starting line will still need to be specified elsewhere in the style sheet:

.grid-cell {
  /* Make sure you specify a starting line elsewhere */
  grid-column-end: span 2;
}

Specify only a starting line:

.grid-cell {
  /* The short way */
  grid-column: 1;

  /* The more verbose way */
  grid-column-start: 1;
}
Autoprefixer unfriendly. Here be dragons!

Now this is where Autoprefixer reaches its limit. The following methods are supported in modern browsers but are not supported by Autoprefixer. This is mainly due to Autoprefixer having no idea what grid the grid cell belongs to since it can only base its decisions on what is in the stylesheet.

Specify a starting line and how many lines from the end of the explicit grid to span:

.grid-cell {
  grid-column: 1 / -1;
}

Specify both the start and end line from the end of the explicit grid:

.grid-cell {
  grid-column: -3 / -1;
}

Specify only how many lines to span using the shorthand syntax (issue in GitHub):

.grid-cell {
  /* Update: This is now supported as of Autoprefixer version 9.1.1 */
  grid-column: span 2;
}

Specify only a backwards span (IE can’t span backwards):

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

Specify only an end line (IE doesn’t understand end and Autoprefixer doesn’t know where the start is):

.grid-cell {
  grid-column-end: 3;
}

So, basically avoid counting backwards from the end of the grid and you’ll be fine. 😊

Avoid using line names for now

One of the cool features of modern grids is giving grid templates line names. Instead of using numbers to reference a line, you can give the line a name and reference that instead. Since Autoprefixer supports grid areas, you would think that they would also support line names. Unfortunately, that isn’t the case. As of version 8.6.4, Autoprefixer does not support line names (at the time of writing). Don’t worry though! It’s not that it is impossible to support (at least not entirely), it just hasn’t been a high priority for them. If you love using line names in your grids then let them know about it in the GitHub issue for it. Post your use cases and it will surely increase the priority of the feature. In the meantime, see if you can use grid areas instead for now.

Keep in mind that, if it is going to be implemented, then every line name across your stylesheet would need to be unique. Like with grid areas, Autoprefixer wouldn’t know what line name belongs to what grid. The only way it can tell is if every line name in the stylesheet is unique (excluding media queries).

Update: Since duplicate area names are now supported, there is no reason why duplicate line names couldn’t be supported as well.

You still need to test!

IE will behave itself most of the time as long as you follow all of the rules we’ve covered so far. That said, IE can still be a bit unpredictable. Just recently, I made a grid item a vertical flowing flex container and encountered an odd bug in IE11. The column width was set to minmax(min-content, 350px) but IE seemed to treat min-content like max-content in this circumstance. This completely broke the layout. Changing it to minmax(0, 350px) fixed the issue. Just goes to show that IE’s grid implementation isn’t quite perfect.

There are also times when you might accidentally forget to explicitly place grid cells. We build our grids in modern browsers because they have the nice grid development tools (especially Firefox). Modern browsers have auto-placement, though. This means that you might be halfway through building your grid then get called away for a moment. When you return to your desk, you see the layout looking beautiful in your browser, but you completely forget that you haven’t explicitly placed any grid cells yet. You move onto the next thing, blissfully unaware that you have left IE in a completely broken state.

Neither of those issues will reveal themselves until you test your site in IE11. Any time that you get a grid looking good in a modern browser, open it up in IE and double-check that it still looks the way you expect it to.

Autoprefixer control comments

Despite Autoprefixer’s best efforts, there are still some rare occasions when it seems like Autoprefixer is hindering you more than it is helping you. If you ever find yourself in a situation where it would be easier to not have Autoprefixer translating your code, you can turn Autoprefixer off using something called a “control comment.”

Update: As of Autoprefixer 9.3.1, it is now recommended that you use the new Grid control comments to enable and disable CSS Grid translations. Learn more about these control comments in Part 4.

autoprefixer: off

This control comment will turn off Autoprefixer translations for the entire CSS block. It will not prefix any styles before or after the point where the comment is made.

/* Input CSS */

.normal-behavior {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
}

.off-behavior {
  display: grid;
  /* autoprefixer: off */
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
}

.normal-behavior-again {
  display: grid;
}
/* Output CSS */

.normal-behavior {
  display: -ms-grid;
  display: grid;
  -ms-grid-columns: 1fr 1fr;
  grid-template-columns: 1fr 1fr;
  -ms-grid-rows: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
}

.off-behavior {
  display: grid;
  /* autoprefixer: off */
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
}

.normal-behavior-again {
  display: -ms-grid;
  display: grid;
}

autoprefixer: ignore next

If you are after more of a scalpel than a sledge hammer, the /* autoprefixer: ignore next */ control comment is more for you. “Ignore next” will skip the next CSS declaration rather than ignoring the entire CSS block.

/* Input CSS */

.normal-behavior {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
}

.ignore-next-behavior {
  display: grid;
  /* autoprefixer: ignore next */
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
}
/* Output CSS */

.normal-behavior {
  display: -ms-grid;
  display: grid;
  -ms-grid-columns: 1fr 1fr;
  grid-template-columns: 1fr 1fr;
  -ms-grid-rows: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
}

.ignore-next-behavior {
  display: -ms-grid;
  display: grid;
  /* autoprefixer: ignore next */
  grid-template-columns: 1fr 1fr;
  -ms-grid-rows: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
}

Pre-processor comments

If you are using Sass (or another CSS pre-processor) in your project, do not use the double slash (//) method for writing these comments. Sass will strip those comments out before Autoprefixer has a chance to translate the code.

/* Input SCSS */

.normal-behavior {
  display: grid;
}

.failed-off-behavior {
  // autoprefixer: off
  display: grid;
}

.successful-off-behavior {
  /* autoprefixer: off */
  display: grid;
}
/* Output CSS */

.normal-behavior {
  display: -ms-grid;
  display: grid;
}

.failed-off-behavior {
  display: -ms-grid;
  display: grid;
}

.successful-off-behavior {
  /* autoprefixer: off */
  display: grid;
}

Let’s recap!

So, after all that, here is the list of all the things you need to remember to be an IE11 CSS grid master:

  • Use the latest version of Autoprefixer (npm i autoprefixer@latest -D).
  • Turn on the Autoprefixer grid setting.
  • Do not attempt to use auto-placement, every cell must be placed manually.
  • Use grid-template-areas as your primary method for placing grid cells into position.
  • Use grid-template (not grid) as a shortcut.
  • Don’t use duplicate area names unless they are inside a media query targeting the same element. (not an issue in 9.3.1)
  • You can use grid-gap as long as you define both grid-template-areas and grid-template-columns.
  • When spanning multiple columns and rows, avoid counting backwards from the end of the grid. Autoprefixer doesn’t understand your grids well enough to supports this.
  • Avoid using line names for now. Give this GitHub issue a thumbs up if you want to start using them.
  • Use control comments /* autoprefixer: off */ and /* autoprefixer: ignore next */ to prevent Autoprefixer from translating certain parts of your style-sheet that are causing problems.
  • Don’t forget to test!

…and from Part 1:

  • IE does have an implicit grid.
  • IE supports repeat functionality.
  • minmax(), min-content and max-content are all natively supported.
  • fit-content() isn’t natively supported but you can work around this with auto and max-width settings.
  • IE auto is not equal to auto in modern browsers.

If you have any questions, or if this really helped you out, let me know in the comments! I’m also @Daniel_Tonon on Twitter. 😁

Up next…

In Part 3, I will be covering how to make a fully responsive flexbox-based grid in IE11. This flexbox technique even replicates grid-gap functionality!

Article Series:

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

CSS Grid in IE: CSS Grid and the New Autoprefixer originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-grid-in-ie-css-grid-and-the-new-autoprefixer/feed/ 8 273125
CSS Grid in IE: Debunking Common IE Grid Misconceptions https://css-tricks.com/css-grid-in-ie-debunking-common-ie-grid-misconceptions/ https://css-tricks.com/css-grid-in-ie-debunking-common-ie-grid-misconceptions/#comments Mon, 02 Jul 2018 13:53:19 +0000 http://css-tricks.com/?p=273108 This is the first in a three-part series all about how to use CSS grid in a way that will work not only in modern browsers but also in Internet Explorer (IE). Imagine writing CSS grid code without having to …


CSS Grid in IE: Debunking Common IE Grid Misconceptions originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
This is the first in a three-part series all about how to use CSS grid in a way that will work not only in modern browsers but also in Internet Explorer (IE). Imagine writing CSS grid code without having to write a fallback layout! Many of us think that this is some far off future that is many years away. If the only thing holding you back from that reality is IE11 (check caniuse.com), then you’re in luck! That day is today! Or at least it will be when you finish reading this series. 😉

Article Series:

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

To start off, this first part is going to debunk some common misconceptions around IE11’s native grid implementation.

In Part 2, I’m going to blow the lid off the myth that using CSS grid in IE11 is super hard. No more crappy fallback layouts for IE11!

In Part 3, I’ll show you a cool flexbox technique that I use for creating simple auto-placement grids. These fake auto-placement grids have fixed pixel-based grid gaps that line up perfectly with ones created using real CSS grid. They work perfectly in IE11, are fully responsive, and updating their column count based on screen width only requires changing a single width value in a media query.

That’s enough intro’s lets get started on these misconceptions!

IE does have an implicit grid

In CSS grid, the explicit grid is the one that you manually define with all of the grid-template-* properties. The implicit grid is how the browser handles the placement of cells that are placed outside of the explicit grid.

When using CSS grid in modern browsers, the implicit grid is pretty obvious since the grid will continue to create and place grid cells automatically on new rows without having to define them. IE doesn’t have auto-placement, so it’s easy to assume that IE doesn’t have an implicit grid. It does though — you just need to be a bit more explicit when it comes to using it.

See the Pen Explicit vs implicit grid by Daniel Tonon (@daniel-tonon) on CodePen.

The most common use case for using IE’s implicit grid is using it to generate your rows for you. If you want all the rows in the grid to have their height dictated by the height of their content, you do not need to bother defining -ms-grid-rows (the IE version of grid-template-rows). The rows will automatically be generated for you in IE when placing your grid cells.

An important thing to note though is that modern browsers have access to the properties grid-auto-rows and grid-auto-columns. These properties allow you to control how the browser handles the size of rows and columns in the implicit grid. IE has no concept of these properties. IE’s implicit rows and columns can only ever be sized as auto.

IE has repeat functionality

Do not fear! There is no need to write out 1fr 20px 12 times for your precious 12-column grid (IE doesn’t have native grid-gap support). IE comes pre-packaged with full repeat() functionality. It’s just hiding behind a different syntax.

The modern syntax for repeating values in columns and rows is repeat(12, 1fr 20px) meaning, “repeat the 1fr 20px pattern 12 times.” The IE version of the syntax is (1fr 20px)[12]. The IE version has identical functionality, just a different syntax.

/* This grid... */
.grid-one {
  display: -ms-grid;
  display: grid;
  -ms-grid-columns: 1fr 20px 1fr 20px 1fr 20px 1fr;
  grid-template-columns: 1fr 20px 1fr 20px 1fr 20px 1fr;
}

/* ...is exactly the same as this grid: */
.grid-two {
  display: -ms-grid;
  display: grid;

  /* IE repeat syntax */
  -ms-grid-columns: 1fr (20px 1fr)[3];

  /* Modern repeat syntax */
  grid-template-columns: 1fr repeat(3, 20px 1fr);
}

Other than syntax, there is only one difference between the way modern browsers and IE implement the repeat() function. Modern browsers added the auto-fit and auto-fill keywords to the function. Since IE doesn’t support auto-placement, those keywords are pretty meaningless to IE so avoid using them.

minmax() is natively supported in IE

minmax() is a CSS sizing function that is exclusive to CSS grid (at the moment, anyway). Its functionality is pretty self explanatory. You provide it a minimum value in the first parameter and a maximum value in the second parameter. The column or row that this is applied to is then able to shrink and grow between those two values as the space available to it increases and decreases. Try resizing the codepen below to see it in action.

See the Pen minmax() demo by Daniel Tonon (@daniel-tonon) on CodePen.

People often think that this awesome little feature isn’t supported in IE but it is in fact natively supported. I’m guessing this is due to at least one of these two reasons:

  1. It’s cool functionality and they think IE can’t have cool things like this because IE sucks.
  2. Everyone has seen this magical code snippet and had their dreams shattered when they realised that it wouldn’t work in IE: grid-template-columns: repeat( auto-fit, minmax(250px, 1fr) );.

(If you have never seen that snippet before, watch this short video and prepare to have your mind blown.)

Since the magic snippet doesn’t work in IE, people probably assume that nothing in the snippet is IE-friendly. In reality, the only reason why the code snippet isn’t replicable in IE is because IE doesn’t support the auto-fit keyword and auto-placement.

min-content and max-content are both 100% IE-friendly

IE has full native support for both the min-content and max-content values.

min-content is a keyword value that will shrink the column/row down to the minimum possible width that the content can shrink down to. If applied to a column, this essentially means that the column will be the width of the longest word.

See the Pen min-content demo by Daniel Tonon (@daniel-tonon) on CodePen.

max-content, on the other hand, will grow the column/row up to the maximum possible width that its content takes up, and no further. If you have ever set white-space: nowrap on a large section of text, this will appear very similar.

See the Pen max-content demo by Daniel Tonon (@daniel-tonon) on CodePen.

I wasn’t expecting IE to support these values mainly because of reason one under minmax(). I was happily surprised when I was proven wrong while researching IE11 grid support. In combination with minmax(), there aren’t many grids that a modern browser can make that IE can’t handle (as long as the grids don’t involve auto-placement).

fit-content() is not IE friendly but

fit-content() doesn’t work natively in IE. 😢

Fortunately, fit-content() is kind of a shorthand syntax and when you write it out the long way, IE does support it! 🎉

The long way to write it is by applying auto (or more specifically, minmax(min-content, max-content)) to the column/row in the grid template, then setting max-width: [value] on the child element.

Here is an example of how you might use the fit-content() function in a modern browser:

/* This is not IE-friendly */
.grid {
  display: grid;
  grid-template-columns: 100px fit-content(300px) 1fr;
}

.cell {
  grid-column: 2;
}

What fit-content() is basically saying here is, “make the middle column take up the maximum possible width of its content (i.e. its max-content value) up until it reaches 300px at which point, don’t grow any larger unless forced to.”

See the Pen fit-content() modern example 1 by Daniel Tonon (@daniel-tonon) on CodePen.

If you are reading this on mobile, view this sections Codepen demos in landscape orientation for a better understanding.

In order to make IE behave in kind of the same way, you would need to write the code like this:

/* IE-friendly `fit-content()` alternative */
.grid {
  display: -ms-grid;
  display: grid;
  -ms-grid-columns: 100px auto 1fr;
  grid-template-columns: 100px auto 1fr;
}

.cell {
  -ms-grid-column: 2;
  grid-column: 2;
  max-width: 300px;
}

See the Pen fit-content IE hack by Daniel Tonon (@daniel-tonon) on CodePen.

Note that this is not a fool-proof method of replicating fit-content() in IE. If there is another grid cell that has content that takes up more width than the max-width setting of the other cell, the grid column will grow to fit the larger cell. It will not be clamped at 300px like it would with fit-content().

See the Pen Broken fit-content hack by Daniel Tonon (@daniel-tonon) on CodePen.

Compare that to the modern fit-content() function which clamps the entire column:

See the Pen fit-content() modern example by Daniel Tonon (@daniel-tonon) on CodePen.

While I’m on the subject, I should clear up a common misconception around the fit-content() function itself. The misconception is that the column (or row) that it is applied to can never exceed the value that you provided the function. This is not the case. If a column is set to a width of fit-content(300px), and a grid cell inside that column is set to a width of 400px, the column width will be 400px, not 300px as many people might expect.

See the Pen Broken modern fit-content by Daniel Tonon (@daniel-tonon) on CodePen.

IE auto != Modern auto

The auto value in IE behaves a bit differently than auto in modern browsers. In IE, auto strictly equals minmax(min-content, max-content). A column or row set to auto in IE can never shrink any smaller than min-content. It also can never grow any larger than max-content.

Modern browses treat the auto value a bit differently. For the most part, when the value is used on its own, they still treat auto like minmax(min-content, max-content). There is one small but critical difference though: auto in modern browsers is able to be stretched by the align-content and justify-content properties. IE doesn’t allow that.

When auto is used for sizing columns in a modern browser, auto behaves more like 1fr if there isn’t enough content to fill the column. IE does not. IE will always shrink the column down to the size of max-content.

So, if you have this code defining your grid:

.grid {
  display: -ms-grid;
  display: grid;
  -ms-grid-columns: auto auto;
  grid-template-columns: auto auto;
}

You will end up with this in modern browsers:

Modern browser auto width columns with little content. Columns fill the grid.

…and this in IE:

IE auto width columns with little content. Columns shrink to the content.

Ok, the modern browser one looks a lot like what we get when we use 1fr. Can we use minmax(min-content, 1fr) for IE then? That will stretch it out like it does in modern browsers won’t it?

Yes, but then we run into this issue:

.grid {
  display: -ms-grid;
  display: grid;
  -ms-grid-columns: minmax(min-content, 1fr) minmax(min-content, 1fr);
  grid-template-columns: auto auto;
}

Modern browsers:

Modern browser auto width columns with lots of content in one cell. Columns are different widths.

IE:

IE minmax(min-content, 1fr) columns with lots of content in one cell. Columns are equal widths.

FYI, minmax(min-content, 1fr) is essentially 1fr and 1fr and does not equal auto. I also tried minmax(min-content, 100%) in IE but that just resulted in the same looking grid as using 1fr. As far as I can tell, it’s not possible to replicate the modern browser auto functionality in IE.

There is another critical difference between IE and modern versions of the auto value though. Since the IE version of auto explicitly equals minmax(min-content, max-content), auto cannot be used in minmax() expressions. minmax() cannot be used inside another minmax() function so auto is disqualified.

Modern browsers are a bit more nuanced. They recognize that auto can mean different things in different contexts. If used on its own, it essentially equals minmax(min-content, max-content) but with the added ability to stretch. When used as a max value, auto is identical to max-content. When used as a min value it essentially equals min-content.

If, right now, you are vowing to never use auto in your grid templates ever again, you may want to rethink that. There are only three circumstances where auto will behave differently between modern browsers and IE. Those are:

  1. auto is being used in grid-template-columns without an fr unit in the same declaration.
  2. auto is being used in grid-template-rows without an fr unit in the same declaration and the height of the grid is greater than the height of its grid cells.
  3. auto is being used in a minmax() function.

That’s it! Those are the only times when auto will behave differently in IE to how it behaves in modern browsers. auto is much easier to write than minmax(min-content, max-content) so it’s not really worth condemning it over a few little browser inconsistencies that are easily avoidable.

(Update) IE Grid does not work on display: inline elements

If you are using <span> or <a> elements as grid cells in your grid, you will be surprised when you go to test in IE that your grid does not look as expected. All of your block level elements (eg. <p>, <div>, <ul> etc.) will appear to work in the grid as expected but your inline elements ( eg. <span>, <a>, <label> etc.) will not.

Span element works correctly in modern browsers but not in IE if using default CSS display settings.

Unfortunately IE Grid is not able to place display: inline elements into a grid correctly. Both <span> and <a> elements are set to display: inline by default in browsers. Don’t worry though, you can easily fix this issue in IE by explicitly setting the inline element to display: block.

Span element works correctly in both modern browsers and in IE if the span is set to display block.

See the Pen
inline grid items don’t work in IE
by Daniel Tonon (@daniel-tonon)
on CodePen.

By default, HTML 5 elements like <main>, <header>, and <footer> are normally set to display: block in browsers. However since these elements were released after IE11 came out, IE11 does not recognise them. Unrecognized elements revert to being the equivalent of <span> elements in nonsupporting browsers. This means that if they become grid items, they will by default not work in IE.

You can make these modern elements work as expected by explicitly setting them to display: block. Alternatively, you can add normalize.css to your project which will do this for you. It will also do a bunch of other little things that make your development experience more enjoyable.

So what have we learned?

  • IE does have an implicit grid.
  • IE supports repeat functionality.
  • minmax(), min-content and max-content are all natively supported.
  • fit-content() isn’t supported but you can work around it with auto and max-width settings.
  • IE auto is not equal to auto in modern browsers.
  • IE Grid does not work on display: inline elements.

Don’t get the grids out just yet though. There are still a lot of things that you need to know before you can start safely implementing IE grids into your websites. Make sure to stick around for Part 2! I will be showing you everything you need to know to safely build sites for both IE and modern browsers using only CSS grid. Best of all, there will be no more need for crappy fallback layouts!

Article Series:

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

CSS Grid in IE: Debunking Common IE Grid Misconceptions originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-grid-in-ie-debunking-common-ie-grid-misconceptions/feed/ 19 273108
ABEM. A more useful adaptation of BEM. https://css-tricks.com/abem-useful-adaptation-bem/ https://css-tricks.com/abem-useful-adaptation-bem/#comments Wed, 13 Dec 2017 17:58:43 +0000 http://css-tricks.com/?p=263223 BEM (Block Element Modifier) is a popular CSS class naming convention that makes CSS easier to maintain. This article assumes that you are already familiar with the naming convention. If not you can learn more about it at getbem.com to …


ABEM. A more useful adaptation of BEM. originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
BEM (Block Element Modifier) is a popular CSS class naming convention that makes CSS easier to maintain. This article assumes that you are already familiar with the naming convention. If not you can learn more about it at getbem.com to catch up on the basics.

The standard syntax for BEM is:

block-name__element-name--modifier-name

I’m personally a massive fan of the methodology behind the naming convention. Separating your styles into small components is far easier to maintain than having a sea of high specificity spread all throughout your stylesheet. However, there are a few problems I have with the syntax that can cause issues in production as well as cause confusion for developers. I prefer to use a slightly tweaked version of the syntax instead. I call it ABEM (Atomic Block Element Modifier):

[a/m/o]-blockName__elementName -modifierName

An Atomic Design Prefix

The a/m/o is an Atomic Design prefix. Not to be confused with Atomic CSS which is a completely different thing. Atomic design is a methodology for organizing your components that maximizes the ability to reuse code. It splits your components into three folders: atoms, molecules, and organisms. Atoms are super simple components that generally consist of just a single element (e.g. a button component). Molecules are small groups of elements and/or components (e.g. a single form field showing a label and an input field). Organisms are large complex components made up of many molecule and atom components (e.g. a full registration form).

An Atomic Design Diagram showing atoms inside molecules inside an organism

The difficulty of using atomic design with classic BEM is that there is no indicator saying what type of component a block is. This can make it difficult to know where the code for that component is since you may have to search in 3 separate folders in order to find it. Adding the atomic prefix to the start makes it immediately obvious what folder the component is stored in.

camelCase

It allows for custom grouping

Classic BEM separates each individual word within a section with a single dash. Notice that the atomic prefix in the example above is also separated from the rest of the class name by a dash. Take a look at what happens now when you add an atomic prefix to BEM classic vs camelCase:

/* classic + atomic prefix */
.o-subscribe-form__field-item {}

/* camelCase + atomic prefix */
.o-subscribeForm__fieldItem {}

At a glance, the component name when reading the classic method looks like it’s called “o subscribe form”. The significance of the “o” is completely lost. When you apply the “o-” to the camelCase version though, it is clear that it was intentionally written to be a separate piece of information to the component name.

Now you could apply the atomic prefix to classic BEM by capitalizing the “o” like this:

/* classic + capitalized atomic prefix */
.O-subscribe-form__field-item {}

That would solve the issue of the “o” getting lost amongst the rest of the class name however it doesn’t solve the core underlying issue in the classic BEM syntax. By separating the words with dashes, the dash character is no longer available for you to use as a grouping mechanism. By using camelCase, it frees you up to use the dash character for additional grouping, even if that grouping is just adding a number to the end of a class name.

Your mind will process the groupings faster

camelCase also has the added benefit of making the grouping of the class names easier to mentally process. With camelCase, every gap you see in the class name represents a grouping of some sort. In classic BEM, every gap could be either a grouping or a space between two words in the same group.

Take a look at this silhouette of a classic BEM class (plus atomic prefix) and try to figure out where the prefix, block, element and modifier sections start and end:

classic BEM silhouette

Ok, now try this one. It is the exact same class as the one above except this time it is using camelCase to separate each word instead of dashes:

camel case BEM silhouette

That was much easier wasn’t it? Those silhouettes are essentially what your mind sees when it is scanning through your code. Having all those extra dashes in the class name make the groupings far less clear. As you read through your code, your brain tries to process whether the gaps it encounters are new groupings or just new words. This lack of clarity causes cognitive load to weigh on your mind as you work.

classic BEM + atomic prefix
classic BEM silhouette revealed
camelCase BEM + atomic prefix
camel case BEM silhouette revealed

Use multi class selectors (responsibly)

One of the golden rules in BEM is that every selector is only supposed to contain a single class. The idea is that it keeps CSS maintainable by keeping the specificity of selectors low and manageable. On the one hand, I agree that low specificity is preferable over having specificity run rampant. On the other, I strongly disagree that a strict one class per selector rule is the best thing for projects. Using some multi-class selectors in your styles can actually improve maintainability rather than diminish it.

“But it leads to higher specificity! Don’t you know that specificity is inherently evil?!?”

Specificity != bad.

Uncontrolled specificity that has run wild = bad.

Having some higher specificity declarations doesn’t instantly mean that your CSS is more difficult to maintain. If used in the right way, giving certain rules higher specificity can actually make CSS easier to maintain. The key to writing maintainable CSS with uneven specificity is to add specificity purposefully and not just because a list item happens to be inside a list element.

Besides, don’t we actually want our modifier styles to have greater power over elements than default styles? Bending over backwards to keep modifier styles at the same specificity level as normal styles seems silly to me. When do you actually want your regular default styles to override your specifically designated modifier styles?

Separating the modifier leads to cleaner HTML

This is the biggest change to the syntax that ABEM introduces. Instead of connecting the modifier to the element class, you apply it as a separate class.

One of the things that practically everyone complains about when they first start learning BEM is how ugly it is. It is especially bad when it comes to modifiers. Take a look at this atrocity. It only has three modifiers applied to it and yet it looks like a train wreck:

B__E–M:
<button class="block-name__element-name block-name__element-name--small block-name__element-name--green block-name__element-name--active">
  Submit
</button>

Look at all that repetition! That repetition makes it pretty difficult to read what it’s actually trying to do. Now take a look at this ABEM example that has all the same modifiers as the previous example:

A-B__E -M:
<button class="a-blockName__elementName -small -green -active">
  Submit
</button>

Much cleaner isn’t it? It is far easier to see what those modifier classes are trying to say without all that repetitive gunk getting in the way.

When inspecting an element with browser DevTools, you still see the full rule in the styling panel so it retains the connection to the original component in that way:

.a-blockName__elementName.-green {
  background: green;
  color: white;
}

It’s not much different to the BEM equivalent

.block-name__element-name--green {
  background: green;
  color: white;
}

Managing state becomes easy

One large advantage that ABEM has over classic BEM is that it becomes immensely easier to manage the state of a component. Let’s use a basic accordion as an example. When a section of this accordion is open, let’s say that we want to apply these changes to the styling:

  • Change the background colour of the section heading
  • Display the content area
  • Make a down arrow point up

We are going to stick to the classic B__E–M syntax for this example and strictly adhere to the one class per css selector rule. This is what we end up with (note, that for the sake of brevity, this accordion is not accessible):

See the Pen Accordion 1 – Pure BEM by Daniel Tonon (@daniel-tonon) on CodePen.

The SCSS looks pretty clean but take a look at all the extra classes that we have to add to the HTML for just a single change in state!

HTML while a segment is closed using BEM:

<div class="revealer accordion__section">
  <div class="revealer__trigger">
    <h2 class="revealer__heading">Three</h2>
    <div class="revealer__icon"></div>
  </div>
  <div class="revealer__content">
    Lorem ipsum dolor sit amet...
  </div>
</div>

HTML while a segment is open using BEM:

<div class="revealer accordion__section">
  <div class="revealer__trigger revealer__trigger--open">
    <h2 class="revealer__heading">One</h2>
    <div class="revealer__icon revealer__icon--open"></div>
  </div>
  <div class="revealer__content revealer__content--open">
    Lorem ipsum dolor sit amet...
  </div>
</div>

Now let’s take a look at what happens when we switch over to using this fancy new A-B__E -M method:

See the Pen Accordion 2 – ABEM alternative by Daniel Tonon (@daniel-tonon) on CodePen.

A single class now controls the state-specific styling for the entire component now instead of having to apply a separate class to each element individually.

HTML while a segment is open using ABEM:

<div class="m-revealer o-accordion__section -open">
  <div class="m-revealer__trigger">
    <h2 class="m-revealer__heading">One</h2>
    <div class="m-revealer__icon"></div>
  </div>
  <div class="m-revealer__content">
    Lorem ipsum dolor sit amet...
  </div>
</div>

Also, take a look at how much simpler the javascript has become. I wrote the JavaScript as cleanly as I could and this was the result:

JavaScript when using pure BEM:

class revealer {
  constructor(el){
    Object.assign(this, {
      $wrapper: el,
      targets: ['trigger', 'icon', 'content'],
      isOpen: false,
    });
    this.gather_elements();
    this.$trigger.onclick = ()=> this.toggle();
  }

  gather_elements(){
    const keys = this.targets.map(selector => `$${selector}`);
    const elements = this.targets.map(selector => {
      return this.$wrapper.querySelector(`.revealer__${selector}`);
    });
    let elObject = {};
    keys.forEach((key, i) => {
      elObject[key] = elements[i];
    });
    Object.assign(this,	elObject);
  }
  
  toggle(){
    if (this.isOpen) {
      this.close();
    } else {
      this.open();
    }
  }

  open(){
    this.targets.forEach(target => {
      this[`$${target}`].classList.add(`revealer__${target}--open`);
    })
    this.isOpen = true;
  }

  close(){
    this.targets.forEach(target => {
      this[`$${target}`].classList.remove(`revealer__${target}--open`);
    })
    this.isOpen = false;
  }
}

document.querySelectorAll('.revealer').forEach(el => {
  new revealer(el);
})

JavaScript when using ABEM:

class revealer {
  constructor(el){
    Object.assign(this, {
      $wrapper: el,
      isOpen: false,
    });
    this.$trigger = this.$wrapper.querySelector('.m-revealer__trigger');
    this.$trigger.onclick = ()=> this.toggle();
  }
  
  toggle(){
    if (this.isOpen) {
      this.close();
    } else {
      this.open();
    }
  }

  open(){
    this.$wrapper.classList.add(`-open`);
    this.isOpen = true;
  }

  close(){
    this.$wrapper.classList.remove(`-open`);
    this.isOpen = false;
  }
}

document.querySelectorAll('.m-revealer').forEach(el => {
  new revealer(el);
})

This was just a very simple accordion example. Think about what happens when you extrapolate this out to something like a sticky header that changes when sticky. A sticky header might need to tell 5 different components when the header is sticky. Then in each of those 5 components, 5 elements might need to react to that header being sticky. That’s 25 element.classList.add("[componentName]__[elementName]--sticky") rules we would need to write in our js to strictly adhere to the BEM naming convention. What makes more sense? 25 unique classes that are added to every element that is affected, or just one -sticky class added to the header that all 5 elements in all 5 components are able to access and read easily?

The BEM “solution” is completely impractical. Applying modifier styling to large complex components ends up turning into a bit of a grey area. A grey area that causes confusion for any developers trying to strictly adhere to the BEM naming convention as closely as possible.

ABEM modifier issues

Separating the modifier isn’t without its flaws. However, there are some simple ways to work around those flaws.

Issue 1: Nesting

So we have our accordion and it’s all working perfectly. Later down the line, the client wants to nest a second accordion inside the first one. So you go ahead and do that… this happens:

See the Pen Accordion 3 – ABEM nesting bug by Daniel Tonon (@daniel-tonon) on CodePen.

Nesting a second accordion inside the first one causes a rather problematic bug. Opening the parent accordion also applies the open state styling to all of the child accordions in that segment.

This is something that you obviously don’t want to happen. There is a good way to avoid this though.

To explain it, let’s play a little game. Assuming that both of these CSS rules are active on the same element, what color do you think that element’s background would be?

.-green > * > * > * > * > * > .element {
  background: green;
}

.element.-blue {
  background: blue;
}

If you said green due to the first rule having a higher specificity than the second rule, you would actually be wrong. Its background would be blue.

Fun fact: * is the lowest specificity selector in CSS. It basically means “anything” in CSS. It actually has no specificy, meaning it doesn’t add any specificity to a selector you add it to. That means that even if you used a rule that consisted of a single class and 5 stars (.element > * > * > * > * > *) it could still be easily overwritten by just a single class on the next line of CSS!

We can take advantage of this little CSS quirk to create a more targeted approach to the accordion SCSS code. This will allow us to safely nest our accordions.

See the Pen Accordion 4 – ABEM nesting bug fix by Daniel Tonon (@daniel-tonon) on CodePen.

By using the .-modifierName > * > & pattern, you can target direct descendants that are multiple levels deep without causing your specificity to get out of control.

I only use this direct targeting technique as it becomes necessary though. By default, when I’m writing ABEM, I’ll write it how I did in that original ABEM accordion example. The non-targeted method is generally all that is needed in most cases. The problem with the targeted approach is that adding a single wrapper around something can potentially break the whole system. The non-targeted approach doesn’t suffer from this problem. It is much more lenient and prevents the styles from breaking if you ever need to alter the HTML later down the line.

Issue 2: Naming collisions

An issue that you can run into using the non-targeted modifier technique is naming collisions. Let’s say that you need to create a set of tabs and each tab has an accordion in it. While writing this code, you have made both the accordion and the tabs respond to the -active class. This leads to a name collision. All accordions in the active tab will have their active styles applied. This is because all of the accordions are children of the tab container elements. It is the tab container elements that have the actual -active class applied to them. (Neither the tabs nor the accordion in the following example are accessible for the sake of brevity.)

See the Pen Accordion in tabs 1 – broken by Daniel Tonon (@daniel-tonon) on CodePen.

Now one way to resolve this conflict would be to simply change the accordion to respond to an -open class instead of an -active class. I would actually recommend that approach. For the sake of an example though, let’s say that isn’t an option. You could use the direct targeting technique mentioned above, but that makes your styles very brittle. Instead what you can do is add the component name to the front of the modifier like this:

.o-componentName {
  &__elementName {
    .-componentName--modifierName & {
      /* modifier styles go here */
    }
  }
}

The dash at the front of the name still signifies that it is a modifier class. The component name prevents namespace collisions with other components that should not be getting affected. The double dash is mainly just a nod to the classic BEM modifier syntax to double reinforce that it is a modifier class.

Here is the accordion and tabs example again but this time with the namespace fix applied:

See the Pen Accordion in tabs 2 – fixed by Daniel Tonon (@daniel-tonon) on CodePen.

I recommend not using this technique by default though mainly for the sake of keeping the HTML clean and also to prevent confusion when multiple components need to share the same modifier.

The majority of the time, a modifier class is being used to signify a change in state like in the accordion example above. When an element changes state, all child elements, no matter what component they belong to, should be able to read that state change and respond to it easily. When a modifier class is intended to affect multiple components at once, confusion can arise around what component that modifier specifically belongs to. In those cases, name-spacing the modifier does more harm than good.

ABEM modifier technique summary

So to make the best use of the ABEM modifier, use .-modifierName & or &.-modifierName syntax by default (depends on what element has the class on it)

.o-componentName {
  &.-modifierName {
    /* componentName modifier styles go here */
  }

&__elementName {
    .-modifierName & {
      /* elementName modifier styles go here */
    }
  }
}

Use direct targeting if nesting a component inside itself is causing an issue.

.o-componentName {
  &__elementName {
    .-nestedModifierName > * > & {
      /* modifier styles go here */
    }
  }
}

Use the component name in the modifier if you run into shared modifier name collisions. Only do this if you can’t think of a different modifier name that still makes sense.

.o-componentName {
  &__elementName {
    .-componentName--sharedModifierName & {
      /* modifier styles go here */
    }
  }
}

Context sensitive styles

Another issue with strictly adhering to the BEM one class per selector methodology is that it doesn’t allow you to write context sensitive styles.

Context sensitive styles are basically “if this element is inside this parent, apply these styles to it”.

With context sensitive styles, there is a parent component and a child component. The parent component should be the one that applies layout related styles such as margin and position to the child component (.parent .child { margin: 20px }). The child component should always by default not have any margin around the outside of the component. This allows the child components to be used in more contexts since it is the parent in charge of it’s own layout rather than its children.

Just like with real parenting, the parents are the ones who should be in charge. You shouldn’t let their naughty clueless children call the shots when it comes to the parents layout.

To dig further into this concept, let’s pretend that we are building a fresh new website and right now we are building the subscribe form component for the site.

See the Pen Context sensitive 1 – IE unfriendly by Daniel Tonon (@daniel-tonon) on CodePen.

This is the first time we have had to put a form on this awesome new site that we are building. We want to be like all the cool kids so we used CSS grid to do the layout. We’re smart though. We know that the button styling is going to be used in a lot more places throughout the site. To prepare for this, we separate the subscribe button styles into its own separate component like good little developers.

A while later we start cross-browser testing. We open up IE11 only to see this ugly thing staring us in the face:

IE11 does kind of support CSS grid but it doesn’t support grid-gap or auto placement. After some cathartic swearing and wishing people would update their browsers, you adjust the styles to look more like this:

See the Pen Context sensitive 2 – what not to do by Daniel Tonon (@daniel-tonon) on CodePen.

Now it looks perfect in IE. All is right with the world. What could possibly go wrong?

A couple of hours later you are putting this button component into a different component on the site. This other component also uses css-grid to layout its children.

You write the following code:

See the Pen Context sensitive 3 – the other component by Daniel Tonon (@daniel-tonon) on CodePen.

You expect to see a layout that looks like this even in IE11:

But instead, because of the grid-column: 3; code you wrote earlier, it ends up looking like this:

Yikes! So what do we do about this grid-column: 3; CSS we wrote earlier? We need to restrict it to the parent component but how should we go about doing that?

Well the classic BEM method of dealing with this is to add a new parent component element class to the button like this:

See the Pen Context sensitive 4 – classic BEM solution by Daniel Tonon (@daniel-tonon) on CodePen.

On the surface this solution looks pretty good:

  • It keeps specificity low
  • The parent component is controlling its own layout
  • The styling isn’t likely to bleed into other components we don’t want it to bleed into

Everything is awesome and all is right with the world… right?

The downside of this approach is mainly due to the fact that we had to add an extra class to the button component. Since the subscribe-form__submit class doesn’t exist in the base button component, it means that we need to add extra logic to whatever we are using as our templating engine for it to receive the correct styles.

I love using Pug to generate my page templates. I’ll show you what I mean using Pug mixins as an example.

First, here is the original IE unfriendly code re-written in mixin format:

See the Pen Context sensitive 5 – IE unfriendly with mixins by Daniel Tonon (@daniel-tonon) on CodePen.

Now lets add that IE 11 subscribe-form__submit class to it:

See the Pen Context sensitive 6 – IE safe BEM solution with mixins by Daniel Tonon (@daniel-tonon) on CodePen.

That wasn’t so hard, so what am I complaining about? Well now let’s say that we sometimes want this module to be placed inside a sidebar. When it is, we want the email input and the button to be stacked on top of one another. Remember that in order to strictly adhere to BEM, we are not allowed to use anything higher in specificity than a single class in our styles.

See the Pen Context sensitive 7 – IE safe BEM with mixins in sidebar by Daniel Tonon (@daniel-tonon) on CodePen.

That Pug code isn’t looking so easy now is it? There are a few things contributing to this mess.

  1. Container queries would make this far less of a problem but they don’t exist yet natively in any browser
  2. The problems around the BEM modifier syntax are rearing their ugly heads.

Now lets try doing it again but this time using context sensitive styles:

See the Pen Context sensitive 8 – IE safe Context Sensitive with mixins in sidebar by Daniel Tonon (@daniel-tonon) on CodePen.

Look at how much simpler the Pug markup has become. There is no “if this then that” logic to worry about in the pug markup. All of that parental logic is passed off to the css which is much better at understanding what elements are parents of other elements anyway.

You may have noticed that I used a selector that was three classes deep in that last example. It was used to apply 100% width to the button. Yes a three class selector is ok if you can justify it.

I didn’t want 100% width to be applied to the button every time it was:

  • used at all anywhere
  • placed inside the subscribe form
  • placed inside the side-bar

I only wanted 100% width to be applied when it was both inside the subscribe form and inside the sidebar. The best way to handle that was with a three class selector.

Ok, in reality, I would more likely use an ABEM style -verticalStack modifier class on the subscribe-form element to apply the vertical stack styles or maybe even do it through element queries using EQCSS. This would mean that I could apply the vertical stack styles in more situations than just when it’s in the sidebar. For the sake of an example though, I’ve done it as context sensitive styles.

Now that we understand context sensitive styles, let’s go back to that original example I had and use some context sensitive styles to apply that troublesome grid-column: 3 rule:

See the Pen Context sensitive 9 – context sensitive method with mixins by Daniel Tonon (@daniel-tonon) on CodePen.

Context sensitive styles lead to simpler HTML and templating logic whilst still retaining the reusability of child components. BEM’s one class per selector philosophy doesn’t allow for this to happen though.

Since context sensitive styles are primarily concerned with layout, depending on circumstances, you should generally use them whenever you are dealing with these CSS properties:

  • Anything CSS grid related that is applied to the child element (grid-column, grid-row etc.)
  • Anything flexbox related that is applied to the child element (flex-grow, flex-shrink, align-self etc.)
  • margin values greater than 0
  • position values other than relative (along with the top, left, bottom, and right properties)
  • transform if it is used for positioning like translateY

You may also want to place these properties into context-sensitive styles but they aren’t as often needed in a context sensitive way.

  • width
  • height
  • padding
  • border

To be absolutely clear though, context sensitive styles are not nesting for the sake of nesting. You need to think of them as if you were writing an if statement in JavaScript.

So for a CSS rule like this:

.parent .element {
  /* context sensitive styles */
}

You should think of it like you are writing this sort of logic:

if (.element in .parent) {
  .element { /* context sensitive styles */ }
}

Also understand that writing a rule that is three levels deep like this:

.grandparent .parent .element {
  /* context sensitive styles */
}

Should be thought of like you are writing logic like this:

if (
    (.element in .parent) &&
    (.element in .grandparent) &&
    (.parent in .grandparent)
  ) {
  .element { /* context sensitive styles */ }
}

So by all means, write a css selector that is three levels deep if you really think you need that level of specificity. Please understand the underlying logic of the css that you are writing though. Only use a level of specificity that makes sense for the particular styling that you are trying to achieve.

And again, one more time, just to be super clear, do not nest for the sake of nesting!

Summing Up

The methodology behind the BEM naming convention is something that I wholeheartedly endorse. It allows css to be broken down into small easily manageable components rather than leaving css in an unwieldy mess of high specificity that is difficult to maintain. The official syntax for BEM has a lot to be desired though.

The official BEM syntax:

  • Doesn’t support Atomic Design
  • Is unable to be extended easily
  • Takes longer for your mind to process the grouping of the class names
  • Is horribly incompetent when it comes to managing state on large components
  • Tries to encourage you to use single class selectors when double class selectors lead to easier maintainability
  • Tries to name-space everything even when namespacing causes more problems than it solves.
  • Makes HTML extremly bloated when done properly

My unofficial ABEM approach:

  • Makes working with Atomic Design easier
  • Frees up the dash character as an extra method that can be used for grouping
  • Allows your mind to process the grouping of the class names faster
  • Is excellent at handling state on any sized component no matter how many sub components it has
  • Encourages controlled specificity rather than just outright low specificity to mitigate team confusion and improve site maintainability
  • Avoids namespacing when it isn’t needed
  • Keeps HTML quite clean with minimal extra classes applied to modules while still retaining all of BEM’s advantages

Disclaimer

I didn’t invent the -modifier (single dash before the modifier name) idea. I discovered it in 2016 from reading an article. I can’t remember who originally conceptualised the idea. I’m happy to credit them if anyone knows the article though.

Update: 21st of January 2018 (comments response)

No one was able to link to the exact article where I learnt about the -modifier syntax. What I can say is that I learnt it from reading an article about BEVM (block__element–variation -modifier).

Here are some other people that came up with the -modifier syntax before me:

BEVM can still work with ABEM if you like that methodology (making it ABEVM). After using the -modifier syntax for a while though I eventually stopped using the &--modifier syntax altogether. I couldn’t really see any benefit in keeping the double dash around when the single dash was easier to use in my CSS and was making my HTML much cleaner.

There were a few people who referenced BEMIT as being quite similar. They’re right, it does share some similarities with BEMIT but it also has some differences.

You could merge ABEM and BEMIT together to an extent. I had people mention that they prefer the explicit “is” of the state based classes in BEMIT (eg. .is-active). That is perfectly fine, if you want to add the “is” to ABEM I would recommend writing the modifier like this .-is-modifierName. See what I mean by camelCase allowing for custom grouping?

The utilities can be carried across from BEMIT as well pretty easily, it would still be written as .u-utilityName. The “utilities” folder/file should maybe be placed in the same directory as the atoms, molecules and organisms folders. I think that might make it easier to find. The “object” and “component” name spaces in BEMIT wouldn’t carry across. They would be replaced with the Atomic Design name spaces in ABEM.

An interesting discussion in the comments was about using the @extend functionality in Sass. For example using <button class='subscribe-form__submit'></button> and .subscribe-form__submit { @extend .button; grid-column: 3; }. I think context sensitive styles are the better way to go. I pretty strongly disagreed with that implementation of @extend unless the CMS is forcing you down that path. You can see the full comment and my response here: https://css-tricks.com/abem-useful-adaptation-bem/#comment-1613824.

A thing many people had issue with was that I didn’t go into much depth around what Atomic Design is and how to use it. It was out of scope for this article. If I tried to go in depth on how to categorize components using Atomic Design principles, then it would have easily doubled the length of the article (and this article is already very long as it is). I gave enough of a summary to introduce the concept of Atomic Design and I linked to a resource that goes much more in depth on the topic. That was about as much attention as I wanted to give to explaining Atomic Design.

Since Atomic Design categorization is such a confusing subject, I have plans to write an article that will be all about how to go about categorizing Atomic Design components. I will attempt to create a set of clear guidelines to follow to help figure out what Atomic Design category a particular component belongs to. Don’t expect it any time soon though. It’s going to be a while before it gets published.

Update: 2nd of April 2019 (Atomic Categorization tool)

So those plans to write an Atomic Design article never came to fruition. I started planning one out, but I began to realize that writing an article to try and explain all the little things that you need to consider when categorizing modules was the wrong way to go about it. A tool that gave you a recommendation at the end would be far more useful.

So I got to work and I am happy to announce the launch of my brand new Atomic Categorizer tool!

It is a simple quiz that gives you points toward each category as you answer the questions. The questions and points might not be 100% perfect but I think it does a pretty good job of categorizing the components most of the time. Give it a try, it’s way more fun than reading another couple thousand words worth of text. 😊


ABEM. A more useful adaptation of BEM. originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/abem-useful-adaptation-bem/feed/ 37 263223