I have an html form that includes a wide table along with the expected label/input fields. The requirement is that the window/frame has the ability to scroll horizontally, scrolling the table as expected, but leaving the fields in place during that scroll.
Trying to place the table in a div that can scroll horizontally is not good enough, as this results in the need to scroll down to the bottom of the table to find the scrollbar - hence the h scrollbar needs to sit on the whole frame.
I have tried the following code - and it's close, but when (1) I'd prefer to have the red div's to be 100vw instead of 50vw and (2) it only seems to work when the screen is wider ... thinner screens seem to keep the inputs sticky for a while - and then they scroll off
.form-container > div {
position:sticky;
left:10px;
color:red;
border:solid 1px red;
width:50vw;
}
Demo - also on JSFiddle
function createRowCopies(tr, num) {
if (!tr || num <= 0) return;
// Ensure the `tr` element is valid
if (!(tr instanceof HTMLTableRowElement)) {
console.error("Invalid row element passed to createRowCopies.");
return;
}
// Loop to create and append copies
for (let i = 0; i < num; i++) {
// Clone the row
const clone = tr.cloneNode(true);
// Append the cloned row right after the original
tr.parentNode.insertBefore(clone, tr.nextSibling);
// Update `tr` reference to the last inserted row
tr = clone;
tr.children[0].innerHTML = i + 2;
}
}
createRowCopies(document.querySelector('tbody tr'), 30);
.form-container {
padding: 10px;
border: 1px solid #ccc;
display: unset;
max-width: 100%;
/* Ensures it doesn't exceed the viewport width */
}
.form-container>div {
position: sticky;
left: 10px;
color: red;
border: solid 1px red;
width: 50vw;
}
label {
display: block;
padding: 10px;
}
table {
border-collapse: collapse;
}
th,
td {
border: 1px solid #ccc;
padding: 8px;
white-space: nowrap;
}
thead th {
background-color: #f8f8f8;
position: sticky;
}
<div class="form-container">
<div>
<label>
Name:
<input type="text" name="name" />
</label>
</div>
<div>
<label>
Email:
<input type="email" name="email" />
</label>
</div>
<table>
<thead>
<tr>
<th>#</th>
<th>Column 1</th>
<th style="min-width: 200px">Column 2</th>
<th style="min-width: 100px">Column 3</th>
<th>Column 4</th>
<th>Column 5</th>
<th>Column 6</th>
<th>Column 7</th>
<th>Column 8</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>Data 1</td>
<td>Data 2</td>
<td>Data 3</td>
<td>Data 4</td>
<td>Data 5</td>
<td>Data 6</td>
<td>Data 7</td>
<td>Data 8</td>
</tr>
</tbody>
</table>
<div>
<label>
Comments:
<textarea name="comments" rows="4"></textarea>
</label>
</div>
<div>
<button type="submit">Submit</button>
</div>
</div>
.... or if you want to play with the code (or change the screen width) then use https://jsfiddle.net/ntsf7ajp
sticky
elements stop being sticky during the scrolling due to the fact that they reach the end of .form-container
width, which happens to be much narrower than its content size. This occurs for several reasons:
By default, a <div>
has the CSS property overflow
set to visible
. This means that any content larger than the <div>
will overflow and be displayed outside of it without expanding the container to fit the content.
A <table>
inherently displays all its content without shrinking. If the table's content is too wide for the container, it will overflow rather than adjust its width or force the container to grow.
You've specified a max-width
of 100%
, which restricts the <div>
container to 100% of its parent’s width, regardless of whether its content exceeds that width.
The use of display: unset
in the context of JSFiddle resets the display
property to its default value (inline
in this case). This behavior is incompatible with explicitly forcing a width, as shown below.
To achieve the desired behavior, you'll just have to set width: max-content
to ensure the container adjusts to its content's width.
.form-container {
padding: 10px;
border: 1px solid #ccc;
width: max-content;
}