CSS main page layout with resizing and scroll overflow sections
Tags: webdev, css, svelte
Published: 2023-09-12 01:56:04

Here is a picture of the layout I want:

```
┌─────────┬───────────────────────┐
│ Sidebar │ Editor                │
│         │                       │
│         │                       │
│       ◄─┼─►                 ▲   │
│         │                   │   │
│         ├───────────────────┼───┤
│         │ Preview           │   │
│         │                   ▼   │
│         │  Table                │
│         │  <overflow-scroll>    │
│         │                       │
└─────────┴───────────────────────┘
```

- The `whole page` should `not be scrollable` (_more like a desktop app, 100vw +100vh_).
- The `Sidebar` should be `vertically resizable`
- The `Editor` should be `horizontally resizable`
- The `Preview` should take up the rest of the space. `Overflow` should be managed via `vertically + horizontally scrolling`.

# Initial Problem

I was using flex box instruction here and there. While that worked in general for putting things where I want them to be, ultimately it lead to parts of the table being rendered outside of the visible and reachable area.

Naively I thought the browser knows what space is left for the table (because of `100vw +100vh` on `html`) but that isn't true.

So i implemented the same layout in `css grid` and `flex box` (but this time a _wholesome_ flex box which wraps the entire screen vertically and horizontally).

# Flexbox approach

_The code is in `Svelte+Tailwind`. Didn't take the time to unwind that further._

```
<script lang="ts">
  import Resizer from './Resizer.svelte';

  let sidebarWidth = 256;
  let topHeight = 312;
</script>

<svelte:head>
	<script src="https://cdn.tailwindcss.com"></script>
</svelte:head>

<div class="wrapper">
  <!-- SIDEBAR -->
  <aside id="sidebar p-2" aria-label="Sidebar" class="sidebar p-2" style:--sidebar-width={sidebarWidth}>
    <span class="font-bold text-xl">Sidebar:</span>
    <span>
      Lorem ipsum!
    </span>
  </aside>

  <!-- SIDEBAR/MAIN DIVIDER -->
  <div class="shrink-0 px-1">
    <Resizer
      size={sidebarWidth}
      min={128}
      max={512}
      orientation="vertical"
      onResize={(newSize) => (sidebarWidth = newSize)}
      onResizeEnd={() => {}}
    />
  </div>

  <!-- MAIN -->
  <div id="main" class="main">
    <!-- TOP -->
    <div id="top p-2 bg-green-100" class="mainTop" style:--top-height={topHeight}>
      <span class="font-bold text-xl">Top:</span>
      Lorem ipsum?
    </div>

    <!-- TOP/BOTTOM DIVIDER -->
    <div class="shrink-0 py-2">
      <Resizer
        size={topHeight}
        min={64}
        max={5120}
        orientation="horizontal"
        onResize={(newSize) => (topHeight = newSize)}
        onResizeEnd={() => {}}
      />
    </div>

    <!-- BOTTOM -->
    <div id="Bottom" class="mainBottom">
      <span class="font-bold text-xl">Bottom:</span>
      Lorem ipsum.
    </div>
  </div>
</div>

<style>
  :global(.html) {
    height: 100vh;
    width: 100vw;
  }
  .wrapper {
    height: 100%;
    width: 100%;
    display: flex;
  }

  .sidebar {
    width: calc(var(--sidebar-width) * 1px);
    overflow: scroll;
    flex-shrink: 0;
    border: 1px solid yellow;
  }

  .main {
    display: flex;
    flex-direction: column; 
    min-width: 0;
  }

  .mainTop {
    height: calc(var(--top-height) * 1px);
    overflow: scroll;
    border: 1px solid yellow;
  }

  .mainBottom {
    flex: 1 1 0%;
    overflow: scroll;
    border: 1px solid yellow;
  }
</style>
```

Play around with it at https://svelte.dev/repl/674dcfad17aa4b628e5c2fef4fda100f?version=4.2.0

# CSS Grid approach

_The code is in `Svelte+Tailwind`. Didn't take the time to unwind that further._

```
<script lang="ts">
  import Resizer from './Resizer.svelte';

  let sidebarWidth = 256;
  let topHeight = 312;
</script>

<svelte:head>
	<script src="https://cdn.tailwindcss.com"></script>
</svelte:head>

<div class="wrapper">
  <!-- SIDEBAR -->
  <aside id="sidebar" aria-label="Sidebar" class="sidebar p-2" style:--sidebar-width={sidebarWidth}>
    <span class="font-bold text-xl">Sidebar:</span>
    <span>
      Lorem ipsum!
    </span>
  </aside>

  <!-- SIDEBAR/MAIN DIVIDER -->
  <div style="grid-area: sidebarResizer;">
    <Resizer
      size={sidebarWidth}
      min={128}
      max={512}
      orientation="vertical"
      onResize={(newSize) => (sidebarWidth = newSize)}
      onResizeEnd={() => {}}
    />
  </div>

  <!-- TOP -->
  <div id="top" class="mainTop p-2" style:--top-height={topHeight}>
    <span class="font-bold text-xl">Top:</span>
    Lorem ipsum?
  </div>

  <!-- TOP/BOTTOM DIVIDER -->
  <div class="" style="grid-area: mainResizer;">
    <Resizer
      size={topHeight}
      min={128}
      max={640}
      orientation="horizontal"
      onResize={(newSize) => (topHeight = newSize)}
      onResizeEnd={() => {}}
    />
  </div>

  <!-- BOTTOM -->
  <div id="Bottom" class="mainBottom p-2">
    <span class="font-bold text-xl">Bottom:</span>
    Lorem ipsum.
  </div>
</div>

<style>
  .wrapper {
    display: grid;
    grid-template-columns: auto auto 1fr;
    grid-template-areas:
      'sidebar sidebarResizer mainTop'
      'sidebar sidebarResizer mainResizer'
      'sidebar sidebarResizer mainBottom';
    gap: 5px;
    height: 100vh;
    width: 100vw;
  }

  .sidebar {
    grid-area: sidebar;
    width: calc(var(--sidebar-width) * 1px);
    overflow: scroll;
    border: 1px solid yellow;
  }

  .mainTop {
    grid-area: mainTop;
    height: calc(var(--top-height) * 1px);
    overflow: scroll;
    border: 1px solid yellow;
  }
  .mainBottom {
    grid-area: mainBottom;
    overflow: scroll;
    border: 1px solid yellow;
  }
</style>
```
 
Play around with it at https://svelte.dev/repl/ed4679d507c04c34a512db05cf6de601?version=4.2.0

# Conclusions

Think the `grid` variant is far more comprehensible.
`Flexbox` is cluttered with `not so obvious tricks` to make it work 
(_try removing `min-width: 0` or some of the `flex: 1 1 0%` statements_).

Hope there is something for somebody in here! 🙏