SO-72132063

by David Thomas

HTML

<main>
  <h2>Custom columns:</h2>
  <label>
    <input type="number" min="1" step="1" value="1">
    <span>Set number of columns</span>
  </label>
  <button>Add new element</button>
  <div class="multicol" style="--columns: 3">
    <span></span>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
    <span></span>
  </div>
</main>

CSS

/* custom properties used through the stylesheet: */
:root {
  --columns: 1;
  --colGap: 0.5em;
  --rowGap: 0.25em;
}


/* a hugely naive, simple CSS reset/normalisation to
   ensure consistent base-styles for cross-browser
   normalisation: */
*,
::before,
::after {
  box-sizing: border-box;
  font-family: Roboto, Montserrat, system-ui;
  font-size: 16px;
  font-weight: 400;
  margin: 0;
  padding: 0;
}

main {
  display: grid;
  gap: 1em;
  margin-block: 1em;
  margin-inline: auto;
  /* setting an ideal width of 60vw (60 percent of the viewport-width,
     with a minimum size of 20em and a maximum size of 800px: */
  width: clamp(20em, 60vw, 800px);
}

h2 {
  font-size: 1.2em;
  font-weight: 600;
  text-align: start;
}

label,
div {
  border: 1px solid #000;
  padding: 0.5em;
}

label {
  /* a habit from positioning the label-text after the associated <input>
     in order to style that text based on status of the <input>, but not
     needed in this instance: */
  display: flex;
  flex-direction: row-reverse;
  gap: var(--colGap);
  align-items: center;
  justify-content: center;
}


/* garish, but it does ensure that people can see when the <input> is focused: */
label:focus-within {
  background-image: linear-gradient(45deg, hsl(0 100% 50% / 0.4), hsl(60 100% 50% / 0.6));
}

label input {
  padding-block: var(--rowGap);
  padding-inline-start: var(--colGap);
}

label span::after {
  content: ':';
}

main>div {
  margin: auto;
}

.multicol {
  counter-reset: spanCount;
  /* setting the number of columns in the element to be equal
     to the --columns custom-property, or a default-value of 1: */
  column-count: var(--columns, 1);
  column-gap: var(--colGap);
  max-width: 100%;
  width: 100%;
}

.multicol span {
  display: block;
  padding-block: var(--rowGap);
}

.multicol span:nth-child(odd) {
  background-color: palegreen;
}

.multicol span::before {
  counter-increment: spanCount;
  content: counter(spanCount);
}

JavaScript

// retrieves the first/only <input> element in the document of "type=number":
const input = document.querySelector('input[type=number]'),
  // retrieves the first/only element in the document with the class of "multicol":
  multicol = document.querySelector('.multicol');

// we set the value of the <input> to the value held in the current `--columns`
// custom property:
input.value = multicol.style.getPropertyValue('--columns');

// we select all <button> elements in the document, and iterate over that
// NodeList using NodeList.prototype.forEach():
document.querySelectorAll('button').forEach(
  // we use EventTarget.addEventListener() to bind an anonymous Arrow
  // function as the event-handler for the 'click' event on the <button>
  // element(s):
  (btn) => btn.addEventListener('click', () => {
    // creating a <span> element:
    let span = document.createElement('span'),
      // from the btn (<button>) element passed into the function-body
      // of the Arrow function we navigate to the parent-node of that
      // <button> element, and from there we find the '.multicol'
      // element:
      spanParent = btn.parentNode.querySelector('.multicol');
    // we then append the new <span> to that element:
    spanParent.append(span);
  })
);

// here we bind an anoymous Arrow function as the event-handler for a
// 'change' event on the <input>:
input.addEventListener('change', (e) => {
  // we get a reference to that <input>, using the Event.currentTarget()
  // property:
  let changed = e.currentTarget,
    // from there we navigate from the <input> to the closest <main>
    // element, and retrieve the first/only element with a class of
    // "multicol":
    columnElement = changed.closest('main').querySelector('.multicol');

  // we then use the CSSStyleDeclaration.setProperty() method, to update
  // the value of '--columns' custom CSS property to the current value
  // of the <input>:
  columnElement.style.setProperty('--columns', changed.value);
});