skip to Main Content

I have a CSS grid element and one of its cells contains some preformatted code (<pre><code>...</code></pre>). This code can have some long lines and smaller screen widths may not be able to fit the whole line. However, I don’t want to line break because this could mess up certain code like:

console.log('test'); // Log the me
ssage

<pre> and <code> do this automatically, however they do not apply a scrollbar. Instead the content overflows the parent. To address this, I tried adding pre { overflow-x: auto; }, which can show a scrollbar. However this still stretches any parent grid cell to fit the underlying long <code> block, even if it is contained in a fixed width <pre> tag which makes no sense to me.

See this minimal example where I reproduced this issue.

#grid {
  display: grid;
  /* Should split the page in half. */
  grid-template: 'left right' 100% / 1fr 1fr;
}

#left {
  grid-area: left;
}

#right {
  grid-area: right;
}

pre {
  /* For long content, horizontal scrollbar should appear. */
  overflow-x: auto;

  /* If we forcibly shorten `pre` width, the scrollbar appears, but grid is still stretched by the `code` element. */
  /* width: 50%; */
}

#tall {
    border: 1px solid red;
    height: 1000px;
}
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Home</title>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />
    <link rel="stylesheet" href="styles.css" />
  </head>
  <body>
    <div id="grid">
      <div id="left">
        <div>Some content on the left.</div>
        <div>This should scroll with the single vertical scrollbar. </div>
      </div>
      <div id="right">
        <pre>
          <code>Some very very very very very very very very very very very very very very very very very very very long code content.</code>
        </pre>
        
        <div id="tall">Some very tall content</div>
      </div>
    </div>
  </body>
</html>

I tried a minimal example of a grid with two columns of equal sizing. One of those columns contains a long <code> tag. This is fine for screens large enough to fit the content, but for smaller screen sizes the <code> tag stretches its cell beyond its half of the screen and the grid grows beyond its container as well and forces a horizontal scrollbar on the grid. Even if I force overflow-x: auto; on the <pre>, the <code> tag still stretches the grid as long as it needs. The other column is shrunk to compensate and almost the entire width is dominated by this one <code> tag. This is very undesirable but I can’t find a way to fix it. What I want in this example is for the grid to always be split 50/50 between the two columns. If the screen is too small to fit the long <code> text, then a horizontal scrollbar should appear on the <pre> or <code> tag and the grid should always match the viewport width and never have a scrollbar.

This image hopefully demonstrates what happens when the viewport width is too small. The two column layout is no longer split down the middle and a scrollbar appears at the bottom of the grid (the two red marks). Instead I’m trying to keep the split in the middle of the grid and have the scrollbar only under the code content in the right column.

Inspect element shows me that the <pre> tag is sized correctly. If I apply width: 50%; to it, I can see that it is smaller than its containing grid cell. However the <code> tag always extends to its full line length. I think that’s kind of expected, however I would think the overflow-x: auto; on the <pre> tag should avoid that length pushing on the grid cell. Am I missing something here? I’ve tried fiddling with width, max-width, overflow-x, and white-space in as many ways as I can figure out.

I’ve also managed to reproduce this with flexbox, so I don’t think it’s specific to grid. Is this some quirk with <code>, <pre>, or white-space: pre;?

2

Answers


  1. Chosen as BEST ANSWER

    While updating the example to address Brett's answer, I managed to accidentally solve this problem, though I still don't understand how.

    The trick is to add #right { overflow-y: auto; }.

    #grid {
      display: grid;
      /* Should split the page in half. */
      grid-template: 'left right' 100% / 1fr 1fr;
    }
    
    #left {
      grid-area: left;
    }
    
    #right {
      grid-area: right;
      
      /**
       * IMPORTANT for some reason?
       * #right isn't (and shouldn't be) scrollable, but this is needed for `pre` to get its scrollbar.
       */
      overflow-y: auto;
    }
    
    pre {
      /* For long content, horizontal scrollbar should appear. */
      overflow-x: auto;
    
      /* If we forcibly shorten `pre` width, the scrollbar appears, but grid is still stretched by the `code` element. */
      /* width: 50%; */
    }
    
    #tall {
        border: 1px solid red;
        height: 1000px;
    }
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>Home</title>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width" />
        <link rel="stylesheet" href="styles.css" />
      </head>
      <body>
        <div id="grid">
          <div id="left">
            <div>Some content on the left.</div>
            <div>This should scroll with the single vertical scrollbar. </div>
          </div>
          <div id="right">
            <pre>
              <code>Some very very very very very very very very very very very very very very very very very very very long code content.</code>
            </pre>
            
            <div id="tall">Some very tall content</div>
          </div>
        </div>
      </body>
    </html>

    This doesn't really make sense to me though because #right isn't scrollable (and shouldn't be). No matter how small the viewport, or how big its content, #right never gets a scrollbar. Instead the <html> tag gets the scrollbar. I was really trying to put the scrollbar on #grid, but in this particular case the grid is the whole page so it really doesn't matter for me even if it wasn't exactly what I was trying to do. This also updates the pre tag to respect its overflow-x: auto; and get its own horizontal scrollbar.

    Interestingly, #grid { overflow-y: auto; } actually has no effect. overflow must be set on #right, even though it triggers a scrollbar on <html>. I've never seen overflow on a child element either:

    1. Put a scrollbar on a parent.
    2. Allow a scrollbar on a child.

    I feel like #right is the exact wrong place to put overflow-y: auto;, but it appears to be necessary. If anyone can help me understand why this is the case, I would be eternally grateful.

    Aside: I considered that adding overflow to #right might be necessary to create a new block formatting context (BFC) to get the pre tag to layout independent of #grid. However, I don't think this is the case because MDN calls out display: flow-root; as a non-side effectful way of creating a new BFC. However, attempting to use #right { display: flow-root; } does not have the intended effect and the pre tag still don't get its own scrollbar. So I have to assume that means #right { overflow-y: auto; } is doing something else to fix the problem, I just have no idea what that something is.


  2. You need the grid cell to scroll any overflow, not the code. Instead of styling the <pre> tag with overflow: auto, style the #right cell with it instead.

    #grid {
      display: grid;
      grid-template-columns: 1fr 1fr;
    }
    
    #left {
      border: 1px solid red;
    }
    
    #right {
      border: 1px solid lime;
      overflow: auto;
    }
    <div id="grid">
      <div id="left">Some content on the left.</div>
      <div id="right">
        <pre>
          <code>Scroll me sideways. Some very very very very very very very very very very very very very very very very very very very long code content.</code>
        </pre>
      </div>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search