cssflexboxscaledashboardgrid-layout

How to scale an image to fit within a flexbox within a gridbox


I want to create a "dashboard" page that uses all the available space in the browser window. The browser window will be divided into two equal-width sides. The right side space will contain a slideshow. Images that are larger than the available space should be scaled down to fit and be centered within the space.

As illustrated in the snippet, the webpage is styled to prevent body scrolling. The body contains a two-column gridbox whose dimensions are 1fr & 1fr. The righthand griditem <right-panel> is a flexbox. This in turn contains a flexbox <picture-box> containing an <img>.

Be sure to click "Full-page" link for snippet and enlarge browser window if necessary.

I can't figure out what I need to do to allow the <picture-box> to expand to the size of <right-panel>. The image does shrink correctly in both height and width to fit when the picture box dimensions are specified in absolute units.

/* Slideshow generates rectangles of various dimensions */
const duration = 1500; // time (msec) to display each slide
const sleep = ms => new Promise(r => setTimeout(r, ms));
const sizes = [
  [4000, 500, "Oversize horizontal"],
  [1000, 4000, "Oversize vertical"],
  [400, 300, "Should fit"],
  [100, 200, "Very small"],
  [4000, 4000, "Oversize square"]
];

async function show_slide_test(duration, min = 0, max = 0) {
  let n = 0;
  const my_img = document.querySelector('#slide-div-img');
  let my_randomizer;
  while (true) {
    let size_index = n++ % sizes.length;
    let w = sizes[size_index][0];
    let h = sizes[size_index][1];
    let desc = sizes[size_index][2];
    let my_randomizer = `https://placehold.co/${w}x${h}/orange/black/png?text=${desc}\\n${w}+x+${h}+px`;
    try {
      const my_response = await fetch(my_randomizer);
      const my_blob = await my_response.blob();
      URL.revokeObjectURL(my_img.src);
      const my_url = URL.createObjectURL(my_blob);
      my_img.src = my_url;
      await sleep(duration);
    } catch (my_error) {
      console.error('Error: ', my_error);
      break;
    }
  }
}
* {
  box-sizing: border-box;
}

html {
  height: 100%;
  width: 100%;
}

body {
  outline: 4px dashed red;
  outline-offset: -4px;
  height: 100%;
  /* limit to height of html */
  width: 100%;
  margin: 0;
  /* prevent body from shifting */
  padding: 0;
  overflow: hidden;
  /* prevents any body scroll */
}

/* Divide body into a grid of 2 columns */
panels {
  background: blue;
  min-height: 100%;
  display: grid;
  grid-template-columns: 50fr 50fr;
  gap: 2em;
}

/* Left-hand panel */
left-panel {
  background: pink;
}

/* Right-hand panel */
right-panel {
  background: yellow;
  display: flex;
  justify-content: center;
  align-items: center;
}

/* Flex item inside right panel flexbox */
picture-box {
  background: green;
  display: flex;
  justify-content: center;
  align-items: center;
  /* what magic values will make picture box grow to size of right-panel? */
  width: 600px;
  height: 400px;
}

.scaled-picture {
  outline: 8px double black;
  outline-offset: -8px;
  max-width: 100%;
  max-height: 100%;
}
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

<body onload="show_slide_test(duration)">
  <panels>
    <left-panel></left-panel>
    <right-panel>
      <picture-box>
      <!-- This is a placeholder for the image -->
      <img class="scaled-picture"
                 id="slide-div-img">
      </picture-box>
    </right-panel>
  </panels>
</body>

</html>

Update In order to present MRE's in my questions, I tried to break my dashboard issues into two smaller questions. The earlier left panel issues were solved here, and I naively assumed that this question, concerning a slideshow in the right panel, could be solved independently and then added to the other one. Not so. Therefore I'm going to have to provide another snippet that includes the details of the left panel.

The right panel still fails to shrink large images sufficiently for both width and height to fit within the space. Instead, it merely accommodates the width, allowing tall images to extend vertically below the bottom edge of the panel. I don't know which of the two approaches used in the answers is "better"; they both work for my original MRE, but not in the larger context of my dashboard.

/* This function populates the scrollable-container grid with variable
       numbers of rows of dummy data */
let my_limit;
const my_perday = 3;

function load_scrollable_table(limit) {
  my_limit = limit;
  if (my_limit < 1) {
    return;
  }
  const my_div = document.querySelector("#whatsnew");
  for (let day = 1;; day++) {
    if (!insert_row(my_div, `3/${day}/2025`, 'Foobar etc.')) {
      return;
    }
    for (let thisday = 1; thisday < my_perday; ++thisday) {
      let v = "foo ".repeat(thisday * 4);
      if (!insert_row(my_div, '', 'Foobar more etc.' + v)) {
        return;
      }
    }
  }
}

function insert_row(my_div, col1, col2) {
  let my_row = `<scrollable-table-col1>${col1}</scrollable-table-col1>
<scrollable-table-col2>${col2}</scrollable-table-col2>`;
  my_div.innerHTML += my_row;
  return --my_limit;
}

/*
 * Slideshow_test generates a sequence of five images that are
 * inserted into the right panel, scaled proportionally to fit
 * the available space.
 *
 * - 4000x500px exceeds the width of the container
 * - 1000x4000px exceeds the height of the container
 * - 4000x4000px exceeds both the width and the height of the container.
 * - 600x400px should fit within the container without any scaling
 * - 100x200px should also fit. It's included to prove that it isn't scaled up.
 */
const duration = 2000; // time (msec) to display each slide
const min_size = 0; // min file size to select
const max_size = 0; // max file size (0 means no limit)
const sleep = ms => new Promise(r => setTimeout(r, ms));
const sizes = [
  /* wd, ht */
  [4000, 500],
  [1000, 4000],
  [4000, 4000],
  [600, 400],
  [100, 200]
];

async function slideshow_test(duration, min = 0, max = 0) {
  let n = 0;

  const my_img = document.querySelector('#slide-img');
  let my_randomizer;
  while (true) {
    let size_index = n++ % sizes.length;
    let w = sizes[size_index][0];
    let h = sizes[size_index][1];

    let my_randomizer = `https://placehold.co/${w}x${h}/orange/black/png?text=Pre-scaled+size\\n${w}+x+${h}+px`;
    try {
      const my_response = await fetch(my_randomizer);
      const my_blob = await my_response.blob();
      URL.revokeObjectURL(my_img.src);
      const my_url = URL.createObjectURL(my_blob);
      my_img.src = my_url;
      await sleep(duration);
    } catch (my_error) {
      console.error('Error: ', my_error);
      break;
    }
  }
}
:root {
  /*
         * I don't know of any way to give <body> the margins I want without
         * somehow destroying the "dashboard" appearance, so I set variables
         * here and use them in several places to fake it.
         */
  --body-margin-sides: 2em;
  --body-margin-top: 2em;
  --body-margin-bottom: 2em;
  --body-background: #dbd2c3;
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

html {
  background: violet;
}

/* BODY and LEFT PANEL are taken from Brett Donald's solution,
       https://stackoverflow.com/a/79453218/522385 */
body {
  outline: 4px dashed red;
  outline-offset: -4px;
  background: var(--body-background);
  margin: 0 var(--body-margin-sides);
  /* side margins only */
  display: grid;
  overflow: hidden;
  /* prevents any body scroll -- good for dashboards */
  /* height: 100%; */
  /* width: 100% */
  /* padding: 0; */
  grid-template-rows: var(--body-margin-top)
    /* fake top margin */
    auto
    /* header */
    1fr
    /* widget area */
    var(--body-margin-bottom);
  /* fake bottom margin */
}

fake-body-margin {
  background: violet;
}

header {
  background: lightgreen;
  text-align: center;
}

hr {
  display: block;
  margin: var(--body-margin-top) auto;
  width: 100%;
  border: 0;
  border-top: 1px dashed black;
}

panels {
  display: grid;
  gap: 5em;
  grid-template-columns: 45fr 55fr;
}

left-panel {
  outline: 3px dotted green;
  outline-offset: -3px;
  display: grid;
  grid-template-rows: auto 1fr;
}

left-panel-top {
  margin-top: 15px;
  /* Gap above Welcome blurb */
  line-height: 1.4;
  /* line separation is 1.4 x normal */
  margin-bottom: 0px;
  /* Gap beneath Welcome blurb */
}

left-panel-bottom {
  position: relative;
}

left-panel-bottom-content {
  background: orange;
  border: 0;
  border-radius: 5px;
  /* border roundedness */
  column-gap: 10px;
  display: grid;
  grid-template-columns: auto auto;
  max-height: 100%;
  overflow: auto;
  /* scroll the overflow */
  padding: 0 1em;
  position: absolute;
  /* prevents content height from affecting the layout*/
  width: fit-content;
  /* I changed from 100% */
}


/* Hide scrollbar for Webkit browsers */
left-panel-bottom-content::-webkit-scrollbar {
  display: none;
}

/* Hide scrollbar for IE, Edge and Firefox */
left-panel-bottom-content {
  -ms-overflow-style: none;
  /* IE and Edge */
  scrollbar-width: none;
  /* Firefox */
}

scrollable-table-col1 {
  height: fit-content;
}

scrollable-table-col2 {
  height: fit-content;
}

scrollable-table-spacer {
  padding-bottom: 6px;
}

/* RIGHT-PANEL is taken from imh's solution */
right-panel {
  outline: 4px dotted black;
  outline-offset: -4px;
  background: yellow;
  display: flex;
  flex-direction: column;
  /* this appears to have no effect */
  overflow: hidden;
}

picture-box {
  outline: 4px dotted orange;
  outline-offset: -4px;
  display: flex;
  flex: auto;
  min-height: 0;
  justify-content: center;
  /* Centers horizontally */
  align-items: center;
  /* Centers vertically */
}

.scaled-picture {
  outline: 8px double black;
  outline-offset: -8px;
  max-width: 100%;
  /* leave a little breathing room */
  max-height: 100%;
}
<body onload="load_scrollable_table(40);   slideshow_test(duration)">
  <fake-body-margin></fake-body-margin>
  <header>
    <h1>Banner</h1>
    <hr>
  </header>
  <!-- END OF BOILERPLATE -->
  <panels>
    <left-panel>
      <left-panel-top>
        Lorem ipsum odor amet, consectetuer adipiscing elit. Maecenas tempor
        proin amet ad consequat tempor praesent facilisis. Tempus potenti
        torquent nulla nullam elit class malesuada. Ut platea id ornare,
        convallis fusce eros. Fringilla pretium porta phasellus consectetur
        fermentum semper mollis. Est imperdiet euismod placerat et venenatis
        mattis; magna dictumst integer. Turpis fusce malesuada venenatis diam
        nisl quis tempus nostra. In nulla mollis ac nisi turpis consequat arcu
        potenti? Inceptos congue potenti at montes erat ac ultricies vel
        maximus. Ridiculus nunc porttitor tortor vulputate; posuere quisque.
        <hr>
        <h3>Recent site updates:</h3>
      </left-panel-top>
      <left-panel-bottom>
        <left-panel-bottom-content id="whatsnew">
          <!-- Javascript inserts table here -->
        </left-panel-bottom-content>
      </left-panel-bottom>
    </left-panel>

    <!-- <panel-gap></panel-gap> -->

    <right-panel>
      <!-- This div will hold the <img> -->
      <picture-box>
        <!-- This is a placeholder for the image -->
        <img class="scaled-picture"
               id="slide-img">
      </picture-box> <!-- END picture -->
    </right-panel>
  </panels>
  <fake-body-margin></fake-body-margin>
</body>

</html>

Update 2: I wanted to see if I could add a second scrollable list to <left-panel-bottom>, to the right of the first one. I turned <left-panel-bottom> into a grid with two equal-width columns. In the HTML I added a second <left-panel-bottom-content>. But since <left-panel-bottom-content> is defined as position: absolute, the two grid items were superimposed. And of course, position:absolute is essential to getting the items to stay within their containers as I want.

Is what's needed some kind of grid element, say <left-panel-bottom-container>, to sit within left-panel-bottom and contain the two content boxes?

/* This function populates the scrollable-container grid with variable
       numbers of rows of dummy data */
let my_limit;
const my_perday = 3;

function load_scrollable_table(limit, id) {
  my_limit = limit;
  if (my_limit < 1) {
    return;
  }
  const my_div = document.querySelector(`#${id}`);
  for (let day = 1;; day++) {
    if (!insert_row(my_div, `3/${day}/2025`, `${id} etc.`)) {
      return;
    }
    for (let thisday = 1; thisday < my_perday; ++thisday) {
      let v = "foo ".repeat(thisday * 4);
      if (!insert_row(my_div, '', `${id} more etc.` + v)) {
        return;
      }
    }
  }
}

function insert_row(my_div, col1, col2) {
  let my_row =
      `<scrollable-table-col1>${col1}</scrollable-table-col1>`
      + `<scrollable-table-col2>${col2}</scrollable-table-col2>`;
  my_div.innerHTML += my_row;
  return --my_limit;
}

/*
 * Slideshow_test generates a sequence of five images that are
 * inserted into the right panel, scaled proportionally to fit
 * the available space.
 *
 * - 4000x500px exceeds the width of the container
 * - 1000x4000px exceeds the height of the container
 * - 4000x4000px exceeds both the width and the height of the container.
 * - 600x400px should fit within the container without any scaling
 * - 100x200px should also fit. It's included to prove that it isn't scaled up.
 */
const duration = 2000; // time (msec) to display each slide
const min_size = 0; // min file size to select
const max_size = 0; // max file size (0 means no limit)
const sleep = ms => new Promise(r => setTimeout(r, ms));
const sizes = [
  /* wd, ht */
  [4000, 500],
  [1000, 4000],
  [4000, 4000],
  [600, 400],
  [100, 200]
];

async function slideshow_test(duration, min = 0, max = 0) {
  let n = 0;

  const my_img = document.querySelector('#slide-img');
  let my_randomizer;
  while (true) {
    let size_index = n++ % sizes.length;
    let w = sizes[size_index][0];
    let h = sizes[size_index][1];

    let my_randomizer = `https://placehold.co/${w}x${h}/orange/black/png?`
                      + `text=Pre-scaled+size\\n${w}+x+${h}+px`;
    try {
      const my_response = await fetch(my_randomizer);
      const my_blob = await my_response.blob();
      URL.revokeObjectURL(my_img.src);
      const my_url = URL.createObjectURL(my_blob);
      my_img.src = my_url;
      await sleep(duration);
    } catch (my_error) {
      console.error('Error: ', my_error);
      break;
    }
  }
}
:root {
    --body-margin-sides: 2em;
    --body-margin-top: 2em;
    --body-margin-bottom: 2em;
    --body-margin-color: violet;
    --body-background: #dbd2c3;
    --body-fontsize: 80%;
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

html {
  background: var(--body-margin-color);
}

/* BODY and LEFT PANEL are taken from Brett Donald's solution,
       https://stackoverflow.com/a/79453218/522385 */
body {
  outline: 4px dashed red;
  outline-offset: -4px;
  background: var(--body-background);
  /* side margins only */
  margin: 0 var(--body-margin-sides);
  display: grid;
  /* prevents any body scroll -- good for dashboards */
  overflow: hidden;
  font-size: var(--body-fontsize);
  grid-template-rows:
    /* fake top margin */
    var(--body-margin-top)
    /* header */
    auto
    /* widget area */
    1fr
    /* fake bottom margin */
    var(--body-margin-bottom);
}

fake-body-margin {
  background: var(--body-margin-color);
}

header {
  background: lightgreen;
  text-align: center;
}

hr {
  display: block;
  margin: var(--body-margin-top) auto;
  width: 100%;
  border: 0;
  border-top: 1px dashed black;
}

panels {
  display: grid;
  gap: 2em;
  grid-template-columns: 60fr 40fr;
}

left-panel {
  outline: 3px dotted green;
  outline-offset: -3px;
  display: grid;
  grid-template-rows: auto 1fr;
}

left-panel-top {
  /* Gap above Welcome blurb */
  margin-top: 15px;
  /* line separation is 1.4 x normal */
  line-height: 1.4;
  /* Gap beneath Welcome blurb */
  margin-bottom: 0px;
}

left-panel-bottom {
    position: relative;
    display: grid;                    /* new */
    gap: 1em;                         /* new */
    grid-template-columns: 40fr 60fr; /* new */
}

left-panel-bottom-content-1 {
  background: orange;
  border: 0;
  border-radius: 5px;
  column-gap: 10px;
  display: grid;
  grid-template-columns: auto auto;
  max-height: 100%;
  /* scroll the overflow */
  overflow: auto;
  padding: 0 0.5em;
  /* prevents content height from affecting the layout*/
  position: absolute;
  width: fit-content;
}

/* NEW: a second scrollable list next to the first in the left panel */
left-panel-bottom-content-2 {
  background: cyan;
  border: 0;
  border-radius: 5px;
  column-gap: 10px;
  display: grid;
  grid-template-columns: auto auto;
  max-height: 100%;
  /* scroll the overflow */
  overflow: auto;
  padding: 0 0.5em;
  /* prevents content height from affecting the layout*/
  /* absolute is obviously wrong since it overlays content-1 */
  position: absolute;
  width: fit-content;
}

/* Hide scrollbar for Webkit browsers */
left-panel-bottom-content-1::-webkit-scrollbar {
  display: none;
}

/* Hide scrollbar for IE, Edge and Firefox */
left-panel-bottom-content-1 {
  -ms-overflow-style: none;
  /* IE and Edge */
  scrollbar-width: none;
  /* Firefox */
}

scrollable-table-col1 {
  height: fit-content;
}

scrollable-table-col2 {
  height: fit-content;
}

scrollable-table-spacer {
  padding-bottom: 6px;
}

/* Right-hand panel */
right-panel {
  background: yellow;
  border: 4px solid black;
  display: flex;
  justify-content: center;
  align-items: center;
  position: relative;
}

/* Flex item inside right panel flexbox */
figure {
  position: absolute;
  width: 100%;
  height: 100%;
  background: transparent;
  display: flex;
  justify-content: center;
  align-items: center;
}

.scaled-picture {
  outline: 8px double black;
  outline-offset: -8px;
  max-width: 100%;
  max-height: 100%;
}
<html>
<body onload="load_scrollable_table(40, 'whatsnew');
              load_scrollable_table(10, 'tigdh');
              slideshow_test(duration)">
  <fake-body-margin></fake-body-margin>
  <header>
    <h1>Banner</h1>
    <hr>
  </header>
  <!-- END OF BOILERPLATE -->
  <panels>
    <left-panel>
      <left-panel-top>
        Lorem ipsum odor amet, consectetuer adipiscing elit. Maecenas tempor
        proin amet ad consequat tempor praesent facilisis. Tempus potenti
        torquent nulla nullam elit class malesuada. Ut platea id ornare,
        convallis fusce eros. Fringilla pretium porta phasellus consectetur
        fermentum semper mollis. Est imperdiet euismod placerat et venenatis
        mattis; magna dictumst integer.
        <hr>
        <h3>Recent site updates:</h3>
      </left-panel-top>
      <left-panel-bottom>
        <left-panel-bottom-content-1 id="whatsnew">
          <!-- Javascript inserts table here -->
        </left-panel-bottom-content-1>
        <left-panel-bottom-content-2 id="tigdh">
          <!-- Javascript inserts table here -->
        </left-panel-bottom-content-2>
      </left-panel-bottom>
    </left-panel>

    <!-- <panel-gap></panel-gap> -->

    <right-panel>
      <!-- This div will hold the <img> -->
      <figure>
        <!-- This is a placeholder for the image -->
        <img class="scaled-picture"
               id="slide-img">
      </figure> <!-- END picture -->
    </right-panel>
  </panels>
  <fake-body-margin></fake-body-margin>
</body>


Solution

  • My solutions work fine as long as the viewport has room for <left-panel-top>. On viewports which are too small for <left-panel-top>, this element will be bigger than the viewport, causing its parent <left-panel> to therefore also <right-panel> to also be bigger than the viewport.

    If you are trying to design for viewports which are too small to accommodate <left-panel-top>, then specify what you would like to happen in this case and we can help you solve it.

    /* This function populates the scrollable-container grid with variable
           numbers of rows of dummy data */
    let my_limit;
    const my_perday = 3;
    
    function load_scrollable_table(limit) {
      my_limit = limit;
      if (my_limit < 1) {
        return;
      }
      const my_div = document.querySelector("#whatsnew");
      for (let day = 1;; day++) {
        if (!insert_row(my_div, `3/${day}/2025`, 'Foobar etc.')) {
          return;
        }
        for (let thisday = 1; thisday < my_perday; ++thisday) {
          let v = "foo ".repeat(thisday * 4);
          if (!insert_row(my_div, '', 'Foobar more etc.' + v)) {
            return;
          }
        }
      }
    }
    
    function insert_row(my_div, col1, col2) {
      let my_row = `<scrollable-table-col1>${col1}</scrollable-table-col1>
    <scrollable-table-col2>${col2}</scrollable-table-col2>`;
      my_div.innerHTML += my_row;
      return --my_limit;
    }
    
    /*
     * Slideshow_test generates a sequence of five images that are
     * inserted into the right panel, scaled proportionally to fit
     * the available space.
     *
     * - 4000x500px exceeds the width of the container
     * - 1000x4000px exceeds the height of the container
     * - 4000x4000px exceeds both the width and the height of the container.
     * - 600x400px should fit within the container without any scaling
     * - 100x200px should also fit. It's included to prove that it isn't scaled up.
     */
    const duration = 2000; // time (msec) to display each slide
    const min_size = 0; // min file size to select
    const max_size = 0; // max file size (0 means no limit)
    const sleep = ms => new Promise(r => setTimeout(r, ms));
    const sizes = [
      /* wd, ht */
      [4000, 500],
      [1000, 4000],
      [4000, 4000],
      [600, 400],
      [100, 200]
    ];
    
    async function slideshow_test(duration, min = 0, max = 0) {
      let n = 0;
    
      const my_img = document.querySelector('#slide-img');
      let my_randomizer;
      while (true) {
        let size_index = n++ % sizes.length;
        let w = sizes[size_index][0];
        let h = sizes[size_index][1];
    
        let my_randomizer = `https://placehold.co/${w}x${h}/orange/black/png?text=Pre-scaled+size\\n${w}+x+${h}+px`;
        try {
          const my_response = await fetch(my_randomizer);
          const my_blob = await my_response.blob();
          URL.revokeObjectURL(my_img.src);
          const my_url = URL.createObjectURL(my_blob);
          my_img.src = my_url;
          await sleep(duration);
        } catch (my_error) {
          console.error('Error: ', my_error);
          break;
        }
      }
    }
    :root {
      /*
             * I don't know of any way to give <body> the margins I want without
             * somehow destroying the "dashboard" appearance, so I set variables
             * here and use them in several places to fake it.
             */
      --body-margin-sides: 2em;
      --body-margin-top: 2em;
      --body-margin-bottom: 2em;
      --body-background: #dbd2c3;
    }
    
    * {
      box-sizing: border-box;
    }
    
    html,
    body {
      height: 100%;
    }
    
    html {
      background: violet;
    }
    
    /* BODY and LEFT PANEL are taken from Brett Donald's solution,
           https://stackoverflow.com/a/79453218/522385 */
    body {
      outline: 4px dashed red;
      outline-offset: -4px;
      background: var(--body-background);
      margin: 0 var(--body-margin-sides);
      /* side margins only */
      display: grid;
      overflow: hidden;
      /* prevents any body scroll -- good for dashboards */
      /* height: 100%; */
      /* width: 100% */
      /* padding: 0; */
      grid-template-rows: var(--body-margin-top)
        /* fake top margin */
        auto
        /* header */
        1fr
        /* widget area */
        var(--body-margin-bottom);
      /* fake bottom margin */
    }
    
    fake-body-margin {
      background: violet;
    }
    
    header {
      background: lightgreen;
      text-align: center;
    }
    
    hr {
      display: block;
      margin: var(--body-margin-top) auto;
      width: 100%;
      border: 0;
      border-top: 1px dashed black;
    }
    
    panels {
      display: grid;
      gap: 5em;
      grid-template-columns: 45fr 55fr;
    }
    
    left-panel {
      outline: 3px dotted green;
      outline-offset: -3px;
      display: grid;
      grid-template-rows: auto 1fr;
    }
    
    left-panel-top {
      margin-top: 15px;
      /* Gap above Welcome blurb */
      line-height: 1.4;
      /* line separation is 1.4 x normal */
      margin-bottom: 0px;
      /* Gap beneath Welcome blurb */
    }
    
    left-panel-bottom {
      position: relative;
    }
    
    left-panel-bottom-content {
      background: orange;
      border: 0;
      border-radius: 5px;
      /* border roundedness */
      column-gap: 10px;
      display: grid;
      grid-template-columns: auto auto;
      max-height: 100%;
      overflow: auto;
      /* scroll the overflow */
      padding: 0 1em;
      position: absolute;
      /* prevents content height from affecting the layout*/
      width: fit-content;
      /* I changed from 100% */
    }
    
    
    /* Hide scrollbar for Webkit browsers */
    left-panel-bottom-content::-webkit-scrollbar {
      display: none;
    }
    
    /* Hide scrollbar for IE, Edge and Firefox */
    left-panel-bottom-content {
      -ms-overflow-style: none;
      /* IE and Edge */
      scrollbar-width: none;
      /* Firefox */
    }
    
    scrollable-table-col1 {
      height: fit-content;
    }
    
    scrollable-table-col2 {
      height: fit-content;
    }
    
    scrollable-table-spacer {
      padding-bottom: 6px;
    }
    
    /* Right-hand panel */
    right-panel {
      background: yellow;
      display: flex;
      justify-content: center;
      align-items: center;
      position: relative;
      border: 4px solid black;
    }
    
    /* Flex item inside right panel flexbox */
    figure {
      position: absolute;
      width: 100%;
      height: 100%;
      background: transparent;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    
    .scaled-picture {
      outline: 8px double black;
      outline-offset: -8px;
      max-width: 100%;
      max-height: 100%;
    }
    <body onload="load_scrollable_table(40);   slideshow_test(duration)">
      <fake-body-margin></fake-body-margin>
      <header>
        <h1>Banner</h1>
        <hr>
      </header>
      <!-- END OF BOILERPLATE -->
      <panels>
        <left-panel>
          <left-panel-top>
            Lorem ipsum odor amet, consectetuer adipiscing elit. Maecenas tempor
            proin amet ad consequat tempor praesent facilisis. Tempus potenti
            torquent nulla nullam elit class malesuada. Ut platea id ornare,
            convallis fusce eros. Fringilla pretium porta phasellus consectetur
            fermentum semper mollis. Est imperdiet euismod placerat et venenatis
            mattis; magna dictumst integer.
            <hr>
            <h3>Recent site updates:</h3>
          </left-panel-top>
          <left-panel-bottom>
            <left-panel-bottom-content id="whatsnew">
              <!-- Javascript inserts table here -->
            </left-panel-bottom-content>
          </left-panel-bottom>
        </left-panel>
    
        <!-- <panel-gap></panel-gap> -->
    
        <right-panel>
          <!-- This div will hold the <img> -->
          <figure>
            <!-- This is a placeholder for the image -->
            <img class="scaled-picture"
                   id="slide-img">
          </figure> <!-- END picture -->
        </right-panel>
      </panels>
      <fake-body-margin></fake-body-margin>
    </body>
    
    </html>

    Regarding a solution to Update 2 of your question ... I wouldn’t use a grid here, I would build on our initial good work and use two absolutely-positioned panels.

    * {
      box-sizing: border-box;
    }
    
    html, body {
      height: 100%;
    }
    
    body {
      background: #f2f2f2;
      margin: 0 1em;            /* side margins only */
      overflow: hidden;         /* prevents any body scroll -- good for dashboards */
      display: grid;
    /*
      GRID TEMPLATE ROWS
      - first row is auto -- this allows the header to take as much space as it needs
      - last row is 1em -- we will leave this empty to simulate a bottom margin
      - middle row is 1fr -- whatever space is left over 
    */  
      grid-template-rows: auto 1fr 1em;  
    }
    
    header {
      text-align: center;
    }
    
    panels {
      display: grid;
    /*
      GRID TEMPLATE COLUMNS
      - two equal width columns
    */
      grid-template-columns: 1fr 1fr;
      gap: 1em;                   /* with a gap between */
    }
    
    left-panel {
      display: grid;
    /*
      GRID TEMPLATE ROWS
      - first row is auto -- this allows the welcome, the hr and the h2 to take as much space as they need
      - last row is 1fr -- whatever space is left over 
    */  
      grid-template-rows: auto 1fr;
      background: pink;
    }
    
    hr {
      display: block;
      margin: 1em auto;
      width: 80%;
      border: 0;
      border-top: 1px dashed black;
    }
    
    left-panel-bottom {
      background: tan;
      position: relative;
    }
    
    left-panel-bottom-left-content {
      position: absolute;             /* prevents the height of the content from affecting the layout*/
      width: 47%;
      max-height: 100%;
      overflow: auto;                 /* scroll the overflow */
      border: 3px solid purple;
      padding: 0 1em;
    }
    
    left-panel-bottom-right-content {
      position: absolute;             /* prevents the height of the content from affecting the layout*/
      left: 53%;
      width: 47%;
      max-height: 100%;
      overflow: auto;                 /* scroll the overflow */
      border: 3px solid navy;
      padding: 0 1em;
    }
    
    right-panel {
      display: flex;
      align-items: center;
      justify-content: center;
      background: cyan;
    }
    <header>
      <h1>This is the page header with logo etc.</h1>
    </header>
    
    <panels>
    
      <left-panel>
      
        <left-panel-top>
        
          <welcome>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
            eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
            ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
            aliquip ex ea commodo consequat.
          </welcome>
    
          <hr>
    
          <h2 style="margin-top: 0;">Recent site updates:</h2>
    
        </left-panel-top>
    
        <left-panel-bottom>
          <left-panel-bottom-left-content>
            <p>
              Lorem ipsum dolor sit amet, consectetur adipiscing elit.
            </p>
            <p>
              Maecenas tempor nunc mauris, sit amet placerat tortor lobortis dapibus.
            </p>
            <p>
              Nam lectus eros, maximus ac magna vel, congue consequat eros.
            </p>
            <p>
              Fusce id pretium diam. Cras sit amet pharetra ante.
            </p>
            <p>
              Sed quis commodo quam, vel facilisis ipsum.
            </p>
            <p>
              Vestibulum sodales iaculis arcu, et fringilla nisi ullamcorper sed.
            </p>
            <p>
              Donec interdum sit amet est non accumsan.
            </p>
            <p>
              Donec non augue feugiat, fermentum nunc non, convallis est.
            </p>
            <p>
              Cras vel ligula nec odio faucibus ultricies.
            </p>
            <p>
              Sed vulputate tortor eget pretium convallis.
            </p>
            <p>
              Cras interdum elit eget mi porta suscipit. Morbi ut velit diam.
            </p>
            <p>
              Etiam finibus eros et efficitur rutrum.
            </p>
            <p>
              Quisque viverra metus ac eleifend imperdiet.
            </p>
            <p>
              Quisque pretium ut purus vitae tempus.
            </p>
            <p>
              Duis varius risus congue velit faucibus, sed interdum purus consectetur.
            </p>
          </left-panel-bottom-left-content>
          <left-panel-bottom-right-content>
            <p>
              Lorem ipsum dolor sit amet, consectetur adipiscing elit.
            </p>
            <p>
              Maecenas tempor nunc mauris, sit amet placerat tortor lobortis dapibus.
            </p>
            <p>
              Nam lectus eros, maximus ac magna vel, congue consequat eros.
            </p>
            <p>
              Fusce id pretium diam. Cras sit amet pharetra ante.
            </p>
            <p>
              Sed quis commodo quam, vel facilisis ipsum.
            </p>
            <p>
              Vestibulum sodales iaculis arcu, et fringilla nisi ullamcorper sed.
            </p>
            <p>
              Donec interdum sit amet est non accumsan.
            </p>
            <p>
              Donec non augue feugiat, fermentum nunc non, convallis est.
            </p>
            <p>
              Cras vel ligula nec odio faucibus ultricies.
            </p>
            <p>
              Sed vulputate tortor eget pretium convallis.
            </p>
            <p>
              Cras interdum elit eget mi porta suscipit. Morbi ut velit diam.
            </p>
            <p>
              Etiam finibus eros et efficitur rutrum.
            </p>
          </left-panel-bottom-right-content>
        </left-panel-bottom>
      </left-panel>
    
      <right-panel>
        Slideshow
      </right-panel>
    </panels>