How can I insert column break in a CSS multi-column layout?

To solve this I decided to go with js approach. It is not fully elegant because in the js code you need to assume you know the height of a single menu item. But this solves the issue and maybe it will fit your project. The idea is that I changed the display of the menu into a flexbox that places items in a column but wraps into the next column when there is no space. Now, to be able to run out of space and wrap we need two things: a fixed menu height (so that is why I calculate it based on the items provided) and also an invisible element that would stretch to 100% height (it does not fit to the current nor the next column so it creates a 0px-wide column on its own thus acting as a column separator). Take a look at this solution:

let items = [
  {title: 'Category 1', type: 'menu-item'},
  {title: 'Category 2', type: 'menu-item'},
  {title: '---cb---', type: 'column-break'},
  {title: 'Category 3', type: 'menu-item'},
  {title: 'Category 4', type: 'menu-item'},
  {title: 'Category 5', type: 'menu-item'},
  {title: 'Category 6', type: 'menu-item'},
  {title: 'Category 7', type: 'menu-item'},
  {title: 'Category 8', type: 'menu-item'},
  {title: 'Category 9', type: 'menu-item'},
  {title: '---cb---', type: 'column-break'},
  {title: 'Category 10', type: 'menu-item'},
  {title: 'Category 11', type: 'menu-item'},
  {title: 'Category 12', type: 'menu-item'},
  {title: 'Category 13', type: 'menu-item'},
  {title: 'Category 14', type: 'menu-item'},
  {title: 'Category 15', type: 'menu-item'},
  {title: 'Category 16', type: 'menu-item'},
  {title: 'Category 17', type: 'menu-item'},
  {title: 'Category 18', type: 'menu-item'},
  {title: 'Category 19', type: 'menu-item'},
  {title: 'Category 20', type: 'menu-item'},
  {title: 'Category 21', type: 'menu-item'},
];

const $menu = document.querySelector('.menu');

console.log( $menu );
var longestColumnLength = 0;
var currentColumnLength = 0;
var numberOfBreaks = 0;

items.forEach((item) => {
    currentColumnLength++;
    let nodeItem = document.createElement("div");
    nodeItem.classList.add('menu-item');
    let nodeItemText = document.createTextNode(item.title);
    nodeItem.appendChild(nodeItemText);
    if (item.type === 'column-break') {
        nodeItem.classList.add('menu-item--column-break');
        let breaker = document.createElement("div");
  	    breaker.classList.add('menu-item--column-break-line');
        $menu.appendChild(nodeItem); 
        $menu.appendChild(breaker); 
        longestColumnLength = Math.max(longestColumnLength, 
           currentColumnLength);
        currentColumnLength = 0;
        numberOfBreaks++;
    } else {
        $menu.appendChild(nodeItem); 
    } 
});

var availableNaturalColumnsAtTheEnd = Math.max(1, 4 - numberOfBreaks);
var maxLengthOfRemainingItems = currentColumnLength / 
      availableNaturalColumnsAtTheEnd;
var actualLongestColumn = Math.max(longestColumnLength, 
      maxLengthOfRemainingItems)

$menu.setAttribute("style", "height: " + actualLongestColumn*20 + "px")
.menu {
  position: relative;
  padding: 0 16px;
  display: flex;
  flex-direction: column;
  flex-wrap: wrap;
  height: 200px;
}

.menu-item{
  height: 20px;
}

.menu-item--column-break {
    display: block;
    color: red;
}
.menu-item--column-break-line {
  height: 100%;
  width: 0;
  overflow: hidden;
}
<div class="container">
  <div class="menu">
  </div>
</div>

Oh, one more thing. I was not sure if you actually want to display the "---cb---" items or not. I left them displayed in the solution but if you want to get rid of them, you can easily modify the code by deleting my extra column dividers, and instead make your "---cb---" items work as the column dividers.