Skip to content

Instantly share code, notes, and snippets.

@hardiksondagar
Created January 28, 2026 18:31
Show Gist options
  • Select an option

  • Save hardiksondagar/9d232a9746ed09d5946c652b314f7593 to your computer and use it in GitHub Desktop.

Select an option

Save hardiksondagar/9d232a9746ed09d5946c652b314f7593 to your computer and use it in GitHub Desktop.
Auto-Responsive Multi-Row Table
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Auto-Responsive Multi-Row Table</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #0f0f0f;
color: #e0e0e0;
padding: 30px 20px;
line-height: 1.4;
}
h1 {
text-align: center;
margin-bottom: 20px;
color: #fff;
font-size: 1.3rem;
}
/* Controls */
.controls {
max-width: 900px;
margin: 0 auto 20px;
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
justify-content: center;
padding: 16px;
background: #1a1a1a;
border-radius: 10px;
border: 1px solid #333;
}
.control-group {
display: flex;
align-items: center;
gap: 8px;
}
.control-group label {
font-size: 0.75rem;
color: #888;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.control-group input[type="number"] {
width: 60px;
padding: 6px 8px;
border: 1px solid #444;
border-radius: 4px;
background: #252525;
color: #fff;
font-size: 0.85rem;
text-align: center;
}
.control-group input[type="range"] {
width: 160px;
accent-color: #60a5fa;
}
.btn {
padding: 8px 14px;
border: none;
border-radius: 5px;
font-size: 0.75rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary {
background: #60a5fa;
color: #000;
}
.btn-primary:hover {
background: #93c5fd;
}
.btn-secondary {
background: #333;
color: #ccc;
}
.btn-secondary:hover {
background: #444;
}
/* Resizable container */
.resize-wrapper {
max-width: 900px;
margin: 0 auto;
padding: 4px;
background: linear-gradient(135deg, #333, #222);
border-radius: 12px;
}
.container {
background: #1a1a1a;
border-radius: 10px;
overflow: hidden;
resize: horizontal;
min-width: 240px;
max-width: 100%;
width: 900px;
border: 1px solid #333;
}
.resize-hint {
text-align: center;
font-size: 0.65rem;
color: #555;
padding: 6px;
background: #1a1a1a;
border-radius: 0 0 10px 10px;
}
/* Header rows */
.table-header {
display: flex;
flex-direction: column;
background: #252525;
border-bottom: 2px solid #444;
position: sticky;
top: 0;
z-index: 10;
}
.header-row {
display: grid;
padding: 8px 10px;
gap: 6px;
}
.header-row:not(:last-child) {
border-bottom: 1px solid #333;
}
.header-row span {
font-size: 0.58rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.3px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Color gradient for row depth */
.header-row:nth-child(1) span { color: #60a5fa; }
.header-row:nth-child(2) span { color: #818cf8; }
.header-row:nth-child(3) span { color: #a78bfa; }
.header-row:nth-child(4) span { color: #c084fc; }
.header-row:nth-child(5) span { color: #e879f9; }
.header-row:nth-child(6) span { color: #f472b6; }
.header-row:nth-child(7) span { color: #fb7185; }
.header-row .row-num-label {
color: #555 !important;
font-size: 0.5rem;
}
/* Data container */
.data-container {
max-height: 500px;
overflow-y: auto;
}
/* Data records */
.record {
display: flex;
flex-direction: column;
border-bottom: 1px solid #2a2a2a;
}
.record:nth-child(odd) {
background: #1e1e1e;
}
.record:nth-child(even) {
background: #242424;
}
.record:last-child {
border-bottom: none;
}
.record-row {
display: grid;
padding: 6px 10px;
gap: 6px;
align-items: center;
}
/* Row depth styling - progressively lighter/smaller */
.record-row:nth-child(1) .cell { font-size: 0.76rem; color: #fff; font-weight: 500; }
.record-row:nth-child(2) .cell { font-size: 0.7rem; color: #aaa; font-weight: 400; }
.record-row:nth-child(3) .cell { font-size: 0.66rem; color: #888; font-weight: 400; }
.record-row:nth-child(4) .cell { font-size: 0.62rem; color: #777; font-weight: 400; }
.record-row:nth-child(5) .cell { font-size: 0.58rem; color: #666; font-weight: 400; }
.record-row:nth-child(6) .cell { font-size: 0.56rem; color: #606060; font-weight: 400; }
.record-row:nth-child(7) .cell { font-size: 0.54rem; color: #585858; font-weight: 400; }
.record-row:first-child {
padding-top: 8px;
}
.record-row:last-child {
padding-bottom: 8px;
}
/* Row number */
.row-num {
font-family: 'SF Mono', Monaco, monospace;
font-size: 0.7rem;
color: #555;
text-align: center;
}
.record-row:not(:first-child) .row-num {
visibility: hidden;
}
/* Cell text handling */
.cell {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Hidden measuring element */
#measureCanvas {
position: absolute;
visibility: hidden;
height: 0;
overflow: hidden;
}
</style>
</head>
<body>
<h1>Sales Pipeline Report</h1>
<!-- Controls -->
<div class="controls">
<div class="control-group">
<label>Rows</label>
<button class="btn btn-secondary" onclick="changeRows(-1)">−</button>
<input type="number" id="rowCount" value="10" min="1" max="20" onchange="render()">
<button class="btn btn-primary" onclick="changeRows(1)">+</button>
</div>
<div class="control-group">
<label>Columns</label>
<button class="btn btn-secondary" onclick="changeCols(-1)">−</button>
<input type="number" id="colCount" value="4" min="2" max="15" onchange="render()">
<button class="btn btn-primary" onclick="changeCols(1)">+</button>
</div>
<div class="control-group">
<label>Cell Spacing</label>
<input type="range" id="spacingSlider" min="1" max="2" step="0.1" value="1.3" oninput="updateSpacing()">
<span id="spacingValue" style="font-size:0.7rem;color:#888;width:40px;">1.3x</span>
</div>
</div>
<!-- Resizable Table Container -->
<div class="resize-wrapper">
<div class="container" id="tableContainer">
<div class="table-header" id="tableHeader"></div>
<div class="data-container" id="dataContainer"></div>
</div>
<div class="resize-hint">↔ Drag right edge to resize — columns per row adjusts automatically based on content width</div>
</div>
<!-- Hidden element to measure text -->
<div id="measureCanvas"></div>
<script>
// Large sample dataset: 20 rows × 15 columns
const columns = [
'Opp ID', 'Account', 'Contact', 'Amount', 'Stage', 'Close Date',
'Owner', 'Region', 'Product', 'Source', 'Probability', 'Created',
'Industry', 'Employees', 'Notes'
];
const data = [
['OPP-1042', 'Acme Corp', 'John Miller', '$45,000', 'Negotiation', '2024-03-15', 'Sarah Chen', 'N. America', 'Enterprise', 'Webinar', '75%', '2024-01-10', 'Technology', '500-1000', 'Hot lead'],
['OPP-1043', 'TechStart Inc', 'Emily Wong', '$28,500', 'Proposal', '2024-03-22', 'Mike Torres', 'EMEA', 'Pro Plan', 'Referral', '60%', '2024-01-15', 'Software', '50-100', 'Fast mover'],
['OPP-1044', 'Global Dynamics', 'Robert Park', '$125,000', 'Discovery', '2024-04-10', 'Lisa Wang', 'APAC', 'Enterprise', 'Outbound', '40%', '2024-02-01', 'Manufacturing', '1000+', 'Enterprise deal'],
['OPP-1045', 'Innovate Labs', 'Anna Schmidt', '$67,000', 'Closed Won', '2024-02-28', 'Sarah Chen', 'EMEA', 'Pro Plan', 'Partner', '100%', '2023-12-05', 'Biotech', '100-500', 'Renewal Q2'],
['OPP-1046', 'CloudScale', 'David Johnson', '$89,000', 'Qualification', '2024-04-05', 'Mike Torres', 'N. America', 'Enterprise', 'Inbound', '30%', '2024-02-10', 'Cloud Services', '500-1000', 'Competitor switch'],
['OPP-1047', 'DataStream', 'Maria Garcia', '$156,000', 'Negotiation', '2024-03-30', 'Lisa Wang', 'LATAM', 'Enterprise', 'Conference', '70%', '2024-01-20', 'Analytics', '100-500', 'Multi-year'],
['OPP-1048', 'NetSecure', 'James Wilson', '$34,000', 'Proposal', '2024-04-15', 'Sarah Chen', 'N. America', 'Pro Plan', 'Webinar', '55%', '2024-02-05', 'Security', '50-100', 'Security focus'],
['OPP-1049', 'FinanceHub', 'Linda Chen', '$210,000', 'Discovery', '2024-05-01', 'Mike Torres', 'APAC', 'Enterprise', 'Referral', '35%', '2024-02-15', 'Finance', '1000+', 'Compliance req'],
['OPP-1050', 'MediaWorks', 'Tom Brown', '$52,000', 'Closed Won', '2024-02-15', 'Lisa Wang', 'EMEA', 'Pro Plan', 'Inbound', '100%', '2023-11-20', 'Media', '100-500', 'Upsell opportunity'],
['OPP-1051', 'HealthFirst', 'Susan Lee', '$145,000', 'Negotiation', '2024-04-20', 'Sarah Chen', 'N. America', 'Enterprise', 'Partner', '65%', '2024-01-25', 'Healthcare', '500-1000', 'HIPAA compliant'],
['OPP-1052', 'EduLearn', 'Michael Adams', '$23,000', 'Proposal', '2024-03-28', 'Mike Torres', 'EMEA', 'Starter', 'Outbound', '50%', '2024-02-08', 'Education', '50-100', 'Pilot program'],
['OPP-1053', 'RetailMax', 'Jennifer White', '$78,000', 'Qualification', '2024-04-25', 'Lisa Wang', 'N. America', 'Pro Plan', 'Conference', '25%', '2024-02-20', 'Retail', '500-1000', 'Seasonal push'],
['OPP-1054', 'LogiTech', 'Kevin Zhang', '$92,000', 'Discovery', '2024-05-10', 'Sarah Chen', 'APAC', 'Enterprise', 'Webinar', '30%', '2024-02-12', 'Logistics', '100-500', 'Integration needed'],
['OPP-1055', 'GreenEnergy', 'Patricia Moore', '$118,000', 'Proposal', '2024-04-08', 'Mike Torres', 'EMEA', 'Enterprise', 'Inbound', '60%', '2024-01-30', 'Energy', '1000+', 'Sustainability'],
['OPP-1056', 'AutoDrive', 'Richard Taylor', '$205,000', 'Negotiation', '2024-04-30', 'Lisa Wang', 'N. America', 'Enterprise', 'Partner', '70%', '2024-02-18', 'Automotive', '1000+', 'Multi-location'],
['OPP-1057', 'FoodChain', 'Nancy Davis', '$41,000', 'Closed Won', '2024-03-01', 'Sarah Chen', 'LATAM', 'Pro Plan', 'Referral', '100%', '2023-12-15', 'Food & Bev', '100-500', 'Expansion'],
['OPP-1058', 'TravelEase', 'Daniel Martinez', '$63,000', 'Qualification', '2024-05-15', 'Mike Torres', 'EMEA', 'Pro Plan', 'Outbound', '20%', '2024-02-22', 'Travel', '50-100', 'New vertical'],
['OPP-1059', 'BuildCorp', 'Laura Anderson', '$175,000', 'Discovery', '2024-05-20', 'Lisa Wang', 'N. America', 'Enterprise', 'Conference', '35%', '2024-02-25', 'Construction', '500-1000', 'Project-based'],
['OPP-1060', 'TeleComm', 'Chris Robinson', '$88,000', 'Proposal', '2024-04-12', 'Sarah Chen', 'APAC', 'Enterprise', 'Inbound', '55%', '2024-02-01', 'Telecom', '1000+', 'Integration'],
['OPP-1061', 'InsureSafe', 'Amanda Clark', '$134,000', 'Negotiation', '2024-04-18', 'Mike Torres', 'N. America', 'Enterprise', 'Webinar', '65%', '2024-01-28', 'Insurance', '500-1000', 'Compliance']
];
// Cache for measured column widths
let columnWidths = [];
let spacingMultiplier = 1.3;
function updateSpacing() {
spacingMultiplier = parseFloat(document.getElementById('spacingSlider').value);
document.getElementById('spacingValue').textContent = spacingMultiplier.toFixed(1) + 'x';
calculateColumnWidths();
render();
}
function measureText(text, fontSize = '12px', fontFamily = '-apple-system, BlinkMacSystemFont, sans-serif') {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.font = `${fontSize} ${fontFamily}`;
return ctx.measureText(text).width;
}
function calculateColumnWidths() {
const colCount = getColCount();
const rowCount = getRowCount();
columnWidths = [];
for (let c = 0; c < colCount; c++) {
// Measure header
let maxWidth = measureText(columns[c], '10px') + 24; // header font + padding
// Measure data cells (sample first few rows)
const sampleRows = Math.min(rowCount, 10);
for (let r = 0; r < sampleRows; r++) {
const cellWidth = measureText(data[r][c], '12px') + 24;
maxWidth = Math.max(maxWidth, cellWidth);
}
// Add minimum and cap maximum - spacing multiplier controls breathing room
columnWidths.push(Math.max(70 * spacingMultiplier, Math.min(maxWidth * spacingMultiplier, 180)));
}
return columnWidths;
}
function calculateColsPerRow(containerWidth) {
const colCount = getColCount();
const availableWidth = containerWidth - 60; // subtract row number column + padding
// Calculate how many columns fit
let totalWidth = 0;
let colsPerRow = 0;
for (let i = 0; i < colCount; i++) {
const colWidth = columnWidths[i] || 80;
if (totalWidth + colWidth <= availableWidth) {
totalWidth += colWidth;
colsPerRow++;
} else {
break;
}
}
// Ensure at least 2 columns per row, max = all columns
return Math.max(2, Math.min(colsPerRow, colCount));
}
function getRowCount() {
return Math.min(20, Math.max(1, parseInt(document.getElementById('rowCount').value) || 6));
}
function getColCount() {
return Math.min(15, Math.max(2, parseInt(document.getElementById('colCount').value) || 12));
}
function changeRows(delta) {
const input = document.getElementById('rowCount');
input.value = Math.min(20, Math.max(1, parseInt(input.value) + delta));
render();
}
function changeCols(delta) {
const input = document.getElementById('colCount');
input.value = Math.min(15, Math.max(2, parseInt(input.value) + delta));
calculateColumnWidths();
render();
}
function chunkArray(arr, size) {
const chunks = [];
for (let i = 0; i < arr.length; i += size) {
chunks.push(arr.slice(i, i + size));
}
return chunks;
}
function render() {
const container = document.getElementById('tableContainer');
const containerWidth = container.offsetWidth;
const rowCount = getRowCount();
const colCount = getColCount();
// Auto-calculate columns per row based on width and data
const colsPerRow = calculateColsPerRow(containerWidth);
// Get active columns
const activeColumns = columns.slice(0, colCount);
// Split columns into chunks based on calculated colsPerRow
const columnChunks = chunkArray(activeColumns, colsPerRow);
const numSubRows = columnChunks.length;
// Grid template: row number + columns per row
const gridTemplate = `40px repeat(${colsPerRow}, 1fr)`;
// Build header - one row per chunk
let headerHTML = '';
columnChunks.forEach((chunk, i) => {
headerHTML += `
<div class="header-row" style="grid-template-columns: ${gridTemplate}">
<span class="row-num-label">${i === 0 ? '#' : ''}</span>
${chunk.map(col => `<span>${col}</span>`).join('')}
${Array(colsPerRow - chunk.length).fill('<span></span>').join('')}
</div>
`;
});
document.getElementById('tableHeader').innerHTML = headerHTML;
// Build data rows
let dataHTML = '';
for (let i = 0; i < rowCount; i++) {
const row = data[i].slice(0, colCount);
const rowChunks = chunkArray(row, colsPerRow);
dataHTML += `<div class="record">`;
rowChunks.forEach((chunk, j) => {
dataHTML += `
<div class="record-row" style="grid-template-columns: ${gridTemplate}">
<span class="row-num">${i + 1}</span>
${chunk.map(val => `<span class="cell">${val}</span>`).join('')}
${Array(colsPerRow - chunk.length).fill('<span class="cell"></span>').join('')}
</div>
`;
});
dataHTML += `</div>`;
}
document.getElementById('dataContainer').innerHTML = dataHTML;
}
// Watch for container resize (manual drag)
const resizeObserver = new ResizeObserver(entries => {
for (let entry of entries) {
render();
}
});
// Initial setup
calculateColumnWidths();
resizeObserver.observe(document.getElementById('tableContainer'));
render();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment