Search code examples
cssflexboxinternet-explorer-11word-wrap

Flex-Basis: 0% causes incorrect overflow in IE11


The problem I have involves flex-basis: 0%; and how IE11 handles it when the user reduces the window's width. Could be related to the bug with box-sizing.

I have a variable number of flex-children, and each child is of unknown width. Each child has arbitrary dynamically-generated content. However, some of this content must have the ability to wrap text.

The flex-container itself must have 100% width and must be able to wrap its children (i.e., flex-wrap: wrap).

Let's assume three flex-children are present, with the last one requiring text-wrapping:

<div class="flex-container">
  <div class="flex-child">
    <div>This text should not overlay other flex-children.</div>
  </div>
  <div class="flex-child">
    <div>This text should not overlay other flex-children.</div>
  </div>
  <div class="flex-child">
    <div class="text-break">This text should break on new lines when the container shrinks down</div>
  </div>
</div>

The CSS can be defined as follows:

.flex-container {
  display: flex;
  flex-wrap: wrap;
  width: 100%;
}

.flex-child {
  flex-grow: 1;
  flex-shrink: 0;
  flex-basis: 0%;
  white-space: nowrap;
  padding: 5px;
}

.text-break {
  white-space: pre-wrap;
}

When the window is wide enough, each flex-child should be side-by-side, and the text not collapsed: Correct display - Max-width Window

When the window is shrunken horizontally, the text in the right-most box should begin to break to new lines: Correct Display - Mid-width Window

And when the text can no longer break into more lines, the flex-boxes themselves should begin to break lines: Correct Display - Min-width Window

This works great in most modern browsers but not our friend, IE11. In IE11, when changing the screen size, the text wraps fine, but the flex-children do not wrap at all. Because the flex-children do not wrap, their content overflows into one another: Incorrect IE11 Display - flex-basis: 0%

Changing the flex basis to flex-basis: auto; has the opposite effect: the text will not wrap but the flex-children do, but no content overflows (in this screenshot, the text in the green box should be breaking lines rather than the flex-box breaking into a new line): Incorrect IE11 Display - flex-basis: auto

Most solutions I have seen require having fixed-length flex-children which I cannot afford to have here because they are dynamically generated. I intentionally did not use the flex shortcut property because of some other non-related issues with it. This answer recommends using _:-ms-fullscreen, :root .IE11-only-class { /* IE11 specific properties */ } which might work if I could get around the text-wrapping issue, but I cannot figure that out.

I should also say that I only need this to work on the latest browsers (technically only on certain versions of Chrome and IE11, but having other browsers and versions work as well is a plus).

Here is a code pen showing the problem in full (view in IE11 and Chrome to see the difference).

Does anyone have any ideas as to how to get around this issue?

Remember, the requirements are:

  • unknown number of flex-children of unspecified width
  • certain text must wrap before the flex-boxes wrap
  • flex-children must wrap once the text can no longer wrap more
  • Chrome (I am using 59.0.3071.109) and IE11 (I am using 11.0.9600.18816CO) should behave in the same way.
  • CSS-only solutions are preferred

Thank you.

Update:

A coworker of mine recommended using a separate class for flex-children that do not contain wrappable text. The following HTML and CSS were what he used:

<!--HTML-->
<div class="flex-container">
  <div class="flex-child child-1">
    <div>This text should not overlay other flex-children.</div>
  </div>
  <div class="flex-child flex-child-without-textwrap child-2">
    <div>This text should not overlay other flex-children.</div>
  </div>
  <div class="flex-child flex-child-with-textwrap child-3">
    <div class="text-break">This text should break on new lines when the container shrinks down</div>
  </div>
</div>

And

/*CSS*/
.flex-container {
  display: flex;
  flex-wrap: wrap;
}

.flex-child {
  flex-grow: 1;
  flex-shrink: 1;
  padding: 5px;
  white-space: nowrap;
}

.flex-child-without-textwrap {
  flex-basis: auto;
}

.flex-child-with-textwrap {
  min-width: 200px;
  flex-basis: 0%;
}

.text-break {
  white-space: pre-wrap;
}

His solution technically fixed the problem as I presented it here, but I forgot to mention another requirement:

  • Within a flex-child, there can be any combination of wrappable text and unwrappable text

His solution does not succeed when changing the third flex-child to:

<div class="flex-child flex-child-with-textwrap child-3">
  <div class="text-break">This text should break on new lines when the container shrinks down</div>
  <div>This text should not break on new lines</div>
</div>

In this case, the text in the second div in the third flex-child flows out of its container, and the text begins wrapping too early.

A codepen showing this almost-working solution can be seen here (note that min-width was removed because it caused further issues on Chrome).

Update 2: I don't think I was clear enough earlier: any, all, or none of the flex-children may have wrappable text. It all depends on the dynamically-generated content. In the example I gave, only the third flex-child has wrappable text, but that might not always be the case.


Solution

  • This is NOT a preferred solution because it uses Javascript. However, it works.

    After creating all flex-children, and whenever the screen size changes, check if the width of each flex-child is less than the width of its content (for this to work, the content must be wrapped with something with display: table-cell). If so, add a minimum width to the flex-child equal to the width of its content. Once a min-width is added, the calculation need not be done again, so no longer check when screen size changes. Only do this on IE11.

    I used jquery to implement this solution.

    HTML:

    <div class="flex-container">
        <div id="1" class="flex-child child-1">
            <div class="content-container">This text should not overlay other flex-children.</div>
        </div>
        <div id="2" class="flex-child child-2">
            <div class="content-container">This text should not overlay other flex-children.</div>
        </div>
        <div id="3" class="flex-child child-3">
            <div class="content-container">
                <div class="text-break">This text should break on new lines when the container shrinks down</div>
                <div>This text should not break on new lines</div>
            </div>
        </div>
    </div>
    

    CSS:

    .flex-container {
        display: flex;
        flex-wrap: wrap;
        width: 100%;
    }
    
    .flex-child {
        flex-grow: 1;
        flex-shrink: 0;
        flex-basis: 0%;
        white-space: nowrap;
        padding: 5px;
    }
    
    .text-break {
        white-space: pre-wrap;
    }
    
    .content-container {
        display: table-cell;
    }
    

    Jquery:

    if (!!window.MSInputMethodContext && !!document.documentMode) {//if IE11
        $(".flex-child").each(function (i, elem) {
            var $flex_child = $(elem);
            var child_id = $flex_child.attr("id");
    
            //add resize event to window
            $(window).on("resize." + child_id, function () {
                var width_of_flex_child = $flex_child.outerWidth();
                var content_width = $flex_child.children(".content-container").outerWidth();
    
                if (width_of_flex_child < content_width) {
                    $flex_child.css("min-width", content_width + "px");
    
                    //remove event
                    $(window).off("resize." + child_id);
                }
            }).trigger("resize." + child_id);
        });
    }
    

    The codepen to view this solution can be found here.

    Unfortunately, this solution requires additional overhead when managing resources because of the window event.