<h2>Resizable Columns using <code><input type="range"></code> and CSS Custom Properties</h2>
<div>
<table>
<thead>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
<th>City</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Apu</td>
<td>Nahasapeemapetilon</td>
<td>Springfield</td>
</tr>
<tr>
<td>2</td>
<td>Bruce</td>
<td>Wayne</td>
<td>Gotham City</td>
</tr>
<tr>
<td>3</td>
<td>Clark</td>
<td>Kent</td>
<td>Metropolis</td>
</tr>
<tr>
<td>3</td>
<td>Donald</td>
<td>Duck</td>
<td>Duckburg</td>
</tr>
</tbody>
</table>
</div>
<p><a href="https://dev.to/madsstoumann/accessible-resizable-table-columns-5eof">Article and discussion at dev.to</a></p>
/* Chrome, Edge, Safari */
[data-table-col] {
--rng-h: 1px;
--rng-thumb-bgc: transparent;
--rng-thumb-h: var(--th, 6rem);
--rng-thumb-ico: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M15.5 8l-4.5 4.5v-3.5h-6v3.5l-4.5-4.5 4.5-4.5v3.5h6v-3.5l4.5 4.5z"></path></svg>');
--rng-thumb-w: 2rem;
background: transparent;
box-sizing: border-box;
font-family: inherit;
height: 1px;
margin: 0;
outline: none;
position: absolute;
top: 0;
width: 100%;
}
[data-table-col]::-webkit-slider-thumb {
background-color: var(--rng-thumb-bgc);
background-position: 50% 0;
background-repeat: no-repeat;
background-size: 80%;
border: 0;
cursor: ew-resize;
height: var(--rng-thumb-h);
width: var(--rng-thumb-w);
}
[data-table-col]:focus-visible::-webkit-slider-thumb {
--rng-thumb-bgc: rgb(0, 0, 0, 0.1);
background-image: var(--rng-thumb-ico);
outline: 2px solid rgb(0, 0, 0, 0.8)
}
[data-table-col]::-webkit-slider-runnable-track {
background: transparent;
height: var(--rng-h);
}
[data-table-col],
[data-table-col]::-webkit-slider-runnable-track,
[data-table-col]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
}
/* Firefox */
@-moz-document url-prefix() {
[data-table-col] {
top: calc(var(--rng-thumb-h) / 2);
}
}
[data-table-col]::-moz-range-thumb {
background-color: var(--rng-thumb-bgc);
background-position: 50% 0;
background-repeat: no-repeat;
background-size: 80%;
border: 0;
cursor: ew-resize;
height: var(--rng-thumb-h);
width: var(--rng-thumb-w);
}
[data-table-col]:focus-visible::-moz-range-thumb {
--rng-thumb-bgc: rgb(0, 0, 0, 0.1);
background-image: var(--rng-thumb-ico);
outline: 2px solid rgb(0, 0, 0, 0.8);
}
[data-table-parent] {
position: relative;
}
/* For this demo */
body {
background-color: #cfe3e8;
font-family: ui-sans-serif, system-ui, sans-serif;
margin: 0 auto;
max-width: 950px;
padding: 1rem;
}
code {
background: #e3fffd;
}
table {
border-collapse: collapse;
table-layout: fixed;
width: 100%;
}
td, th {
border: 1px solid silver;
padding: 0.5em 1em;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
td {
background-color: #fff;
}
th {
background-color: #96a5b2;
}
function resizeTable(table, selector = 'thead tr th', minWidth = 5) {
if (!table) return;
const cols = table.querySelectorAll(selector);
const parent = table.parentNode;
const tableWidth = table.offsetWidth;
let value = 0;
parent.dataset.tableParent = '';
parent.style.setProperty(`--th`, `${table.offsetHeight}px`);
cols.forEach((col, index) => {
const colWidth = parseInt(100 / (tableWidth / col.offsetWidth));
col.style.width = `calc(1% * var(--c${index}))`;
table.style.setProperty(`--c${index}`, colWidth);
if (index > 0) {
const input = document.createElement('input');
input.dataset.tableCol = index;
input.setAttribute('aria-hidden', true);
input.type = 'range';
input.value = value;
parent.appendChild(input);
input.addEventListener('input', () => {
if (input.value < minWidth) input.value = minWidth;
if (input.value > 100 - minWidth) input.value = 100 - minWidth;
const next = input.nextElementSibling;
const prev = input.previousElementSibling;
if (next?.nodeName === 'INPUT' && (input.valueAsNumber > (next.valueAsNumber - minWidth))) {
input.value = next.valueAsNumber - minWidth;
return;
}
if (prev?.nodeName === 'INPUT' && (input.valueAsNumber < (prev.valueAsNumber + minWidth))) {
input.value = prev.valueAsNumber + minWidth;
return;
}
table.style.setProperty(`--c${index-1}`, prev?.nodeName === 'INPUT' ? input.valueAsNumber - prev.valueAsNumber : input.valueAsNumber);
table.style.setProperty(`--c${index}`, next?.nodeName === 'INPUT' ? next.valueAsNumber - input.valueAsNumber : 100 - input.valueAsNumber);
});
}
value += colWidth;
});
/* HACK TO INIT FIREFOX: Trigger input event on last range to re-position sliders */
const lastRange = table.parentNode.lastChild;
if (lastRange?.nodeName === 'INPUT') {
lastRange.dispatchEvent(new Event('input', {
bubbles: true,
cancelable: true,
}));
}
}
/* Init Demo */
const table = document.querySelector('table');
resizeTable(table);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.