How can I implement a table that is both horizontally and vertically scrollable with fixed header using css?
I found this Scrolling a div from an outer div, but it is implemented by using Javascript/Jquery. Any way to implement it by using only CSS?
The updated code:
#div-table-content {
width: 100%;
overflow-x: auto;
}
table {
font-size: 12px;
white-space:nowrap;
overflow-x: scroll;
}
tbody {
height: 400px;
overflow-y: auto;
width: 100%;
display: block;
}
thead tr {
display: block;
width: 100%;
}
For a start divide your <table> semantically to headers inside <thead> and content inside <tbody>.
Then, for vertical scrolling, give a fixed height to your <tbody> and set overflow-y: auto and display: block.
For horizontal scrolling, I belive you have to wrap your entire table with a container (lets say <div> and give it a fixed width and overflow-x: auto.
jsFiddle Demo
You can fake table with css-grid (if you don't mind).
.table {
display: grid;
width: 100px;
height: 70px;
overflow: auto;
grid-auto-columns: max-content;
}
.head {
position: sticky;
top: 0;
grid-row: 1;
background-color: #fff;
font-weight: bold;
}
Than you put all cells into single element
<div class="table">
<div class="head">column 1</div>
<div class="head">column 2</div>
<div class="head">column 3</div>
<div>data - column 1 - row 1</div>
<div>data - column 2 - row 1</div>
<div>data - column 3 - row 1</div>
<div>data - column 1 - row 2</div>
<div>data - column 2 - row 2</div>
<div>data - column 3 - row 2</div>
<div>data - column 1 - row 3</div>
<div>data - column 2 - row 3</div>
<div>data - column 3 - row 3</div>
</div>
Now you can see both scrollbars all the time; header scrolls horizontally but not vertically.
Horizontal and vertical scrolling with sticky column headers using css only can be done with the following.
A container div with overflow: scroll
A thead with position: sticky and inset-block-start: 0;
Full example below:
.container {
overflow: scroll;
height: 180px;
width: 300px;
}
table {
border-collapse: collapse;
}
table th,
table td {
max-width: 300px;
padding: 8px 16px;
border: 1px solid #ddd;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
table thead {
position: sticky;
inset-block-start: 0;
background-color: #ddd;
}
<div class="container">
<table>
<thead>
<tr>
<th>Fruits</th>
<th>Nuts</th>
<th>Vegetables</th>
<th>Meats</th>
</tr>
</thead>
<tbody>
<tr>
<td>Apple</td>
<td>Peanut</td>
<td>Carrot</td>
<td>Chicken</td>
</tr>
<tr>
<td>Banana</td>
<td>Pecan</td>
<td>Potato</td>
<td>Pork</td>
</tr>
<tr>
<td>Cherry</td>
<td>Cashew</td>
<td>Tomato</td>
<td>Beef</td>
</tr>
<tr>
<td>Grape</td>
<td>Almond</td>
<td>Cabbage</td>
<td>Lamb</td>
</tr>
<tr>
<td>Kiwi</td>
<td>Brazil Nut</td>
<td>Onion</td>
<td>Chicken</td>
</tr>
<tr>
<td>Lemon</td>
<td>Hazelnut</td>
<td>Cucumber</td>
<td>Fish</td>
</tr>
</tbody>
</table>
</div>
Related
Assume I have a gridbox with 4 columns and 1 row. Parent container 1 & 2 are children of this gridbox. Parent container1 has a colspan of 3 and container2 has a colspan of 1 column. Both container have multiple child components, that are responsive and change size on window resize. Child 5 components has a table inside, that contains over 250+ records. The goal is to make this table scrollable, with perfectly equal content height of gridbox and without knowing any dimensions of parent or child containers.
So parent container2 has to be same height as parent container1, therefore something has to be done with child5
JSFiddle example
Expected layout: (Same row height and scrollable table)
Current layout: (Unequal content height, table is not scrollable)
I tried implementing flexbox for this scenario, but it seemed to make things harder. I also tried adding display: flex and flex: flex-shrink to the child5 container but with no results whatsoever.
I know I can programmatically take the height of container1 and calculate the needed height of child5 but I feel like there's more elegant solution using CSS + I don't wanna add too much event listeners on window resize.
Would love to take a look at your suggestions.
It is possible to apply display: flex for your .container1 class and use flex property:
*, html, body {
box-sizing: border-box;
margin: 0;
}
body {
font-family: sans-serif;
font-weight: 600;
}
.grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 5px;
border: 1px solid black;
padding: 5px;
}
.container1 {
grid-column: 1/4;
background: blue;
display: flex;
flex-direction: column;
height: 100%;
flex: 1;
}
.container1 div {
flex: 1;
}
.container2 {
background: red;
}
.child, .child1, .child3, .child5 {
border: 1px solid black;
padding: 15px;
background: gray
}
.container1, .container2 {
border: 1px solid black;
padding: 5px;
}
.table, th, td {
border: 1px solid black;
width: 100%;
}
.child1, .child3 {
padding: 40px 15px 40px 15px;
}
.child5 {
overflow-Y: scroll;
}
<div class="grid">
<div class="container1">
parent container 1
<div class="child1">
child 1
</div>
<div class="child">
child 2
</div>
<div class="child3">
child 3
</div>
</div>
<div class="container2">
parent container 2
<div class="child">
child 4
</div>
<div class="child5">
child 5
<table>
<tr>
<th>prop</th>
<th>value</th>
</tr>
<tr>
<th>prop</th>
<th>value</th>
</tr>
<tr>
<th>prop</th>
<th>value</th>
</tr>
<tr>
<th>prop</th>
<th>value</th>
</tr>
<tr>
<th>prop</th>
<th>value</th>
</tr>
<tr>
<th>prop</th>
<th>value</th>
</tr>
<tr>
<th>prop</th>
<th>value</th>
</tr>
<tr>
<th>prop</th>
<th>value</th>
</tr>
<tr>
<th>prop</th>
<th>value</th>
</tr>
<tr>
<th>prop</th>
<th>value</th>
</tr>
<tr>
<th>prop</th>
<th>value</th>
</tr>
<tr>
<th>prop</th>
<th>value</th>
</tr>
</table>
</div>
</div>
</div>
I need a two column table that has only one row. The data in the first column should always display in full, but the second column should resize and the data in it should ellipse if it can't fit in the cell. If the first column expands to fit its data, the second column should contract, all while keeping the table width the same.
I tried to fix width of table and all kinds of ways to achieve this with CSS, but I couldn't figure out. It does seem like it's something that should be achievable.
This is how the table should behave with different data in the first column:
.ellipsis {
width: 190px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
}
<h3>table width always the same</h3>
<table border="1">
<tr>
<th>
Column1
</th>
<th>
Column2
</th>
</tr>
<tr>
<td>
<label>Display in full</label>
</td>
<td>
<label class="ellipsis">Ellipsis this if length is too long</label>
</td>
</tr>
</table>
Like I mention in the comments, it can be achieved using flex-box.
.parent has display: flexbox
Column 1 has white-space: nowrap to make it fit its content.
Column 2 has flex-grow so it will take all the remain space.
.parent {
display: flex;
width: 350px;
border: 1px solid;
}
.header {
text-align: center;
font-weight: bold;
border-bottom: 1px solid;
}
.parent > div:first-child {
white-space: nowrap;
border-right: 1px solid;
}
.parent > div:last-child {
flex-grow: 1;
}
.parent div:last-child {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.parent > div > div {
padding: 1px;
}
<div class="parent">
<div>
<div class="header">Column1</div>
<div>Display in full</div>
</div>
<div>
<div class="header">Column 2</div>
<div>Ellipsis this if length is too long</div>
</div>
</div>
https://jsbin.com/ferixiq/3/edit?html,css,output
I am placing a table inside a td. By setting width:100%, I can get the table to stretch horizontally, but I have not been able to do the same vertically. No matter what I've tried, the table will have the minimum height necessary to contain its contents.
Below is the structure of the table
table {
border-collapse: collapse;
border-spacing: 2px;
border-color: black;
}
td {
border: 1px solid #ddd;
background: #f2f5f7;
}
<table >
<tbody>
<tr>
<td rowspan="2" style="background-color:#ff0000"/>
<table>
<tr>
<td>A</td>
<td>B</td>
</tr>
<tr>
<td>C</td>
<td>D</td>
</tr>
</table>
<td><br>Extra<br></td>
</tr>
<tr>
<td>Space<br><br></td>
</tr>
</table>
I am trying to get rid of the red areas above and below the ABCD table. I've tried solutions from dozens of various questions, and I thought this would be straightfoward, but nothing has worked.
I have tried (in various permutations):
height:100% on any and all componentns
display:flex or display:block on the nested table and/or parent td
explicitly setting padding:0px !important on all components
None of these have affected the height of the table.
The most promising result so far: setting the height to a pixel value (like height:100px does change the height of the table. Unfortunately, the necessary height of the table will change based on factors outside of my knowledge/control. I'm looking for a programmatic solution to this issue.
(NOTE: I know how to use JS to grab the height of the neighboring cells and set the height of the trs in the nested table after the page loads. However, I don't have a guarantee that JS will be executable, so this is my last resort.)
Here is a solution that I've created using CSS Grid.
.grid-table {
display: grid;
grid-template-columns: repeat(3, minmax(50px, 1fr));
background-color: #fff;
color: #444;
max-width: 800px;
background-color: #ccc;
}
.grid-table > .row:not(:last-of-type) {
border-bottom: 1px solid #ddd
}
.row {
grid-column: 1 / -1;
display: grid;
grid-template-columns: repeat(3, minmax(50px, 1fr));
align-items: center;
}
.row > .box:not(:last-of-type) {
border-right: 1px solid #ddd;
}
.box {
height: 100%;
display: flex;
align-items: center;
}
.box-item {
padding: 5px;
}
<div class="grid-table">
<!-- Table Row -->
<div class="row">
<div class="box">
<div class="box-item">Column 1</div>
</div>
<div class="box">
<div class="box-item">Column 2</div>
</div>
<div class="box">
<div class="box-item">Column 3</div>
</div>
</div>
<!-- Table Row -->
<div class="row">
<div class="box">
<div class="box-item">A</div>
</div>
<div class="box">
<div class="box-item">B</div>
</div>
<div class="box">
<div class="box-item">Extra<br>Space</div>
</div>
</div>
<!-- Table Row -->
<div class="row">
<div class="box">
<div class="box-item">C</div>
</div>
<div class="box">
<div class="box-item">D</div>
</div>
<div class="box">
<div class="box-item">Extra<br>Space</div>
</div>
</div>
</div>
With this solution, the height of the individual columns will always be the height of the tallest column. You can control whether you want the rows to be centered or not by modifying the align-items attribute within the .box class.
JSFiddle
My top-level layout is n columns, all of which are of fixed width (sidebars) except for the central one (mainbar) that should automatically fill up the remaining space.
So, I have this tricky wide table in the mainbar. It has a wrapper with overflow-x: auto, however instead of triggering the scrolling on the wrapper, it prefers to stretch everything up to the top-level flex container. This can be solved by adding width: calc(100% - {sum of widths of other columns}px) to the mainbar, but I'm looking for a more flexible solution that would allow adding more columns and resizing the existing ones without touching this calc rule.
Any ideas? Is there any way to say to a flex item: fill up the remaining space, but don't allow your content to stretch you?
UPD: Managed to do it by wrapping the content of the mainbar in a table with table-layout: fixed (the code is here), but I feel bad about it. Does anyone know of a more flexboxy solution? Or is this one okay?
// this JS generates placeholder text, ignore it
for (const el of document.querySelectorAll(".lorem")) {
el.innerHTML = Array(Number(el.getAttribute("p")) || 1)
.fill()
.map(() => `<p>${chance.paragraph()}</p>`)
.join("");
}
body {
margin: 0;
}
.outer {
display: flex;
}
.sidebar {
flex: 0 0 300px;
background: #eef;
}
.mainbar {
background: #ffe;
/* width: calc(100% - 500px); */
}
.rightbar {
flex: 0 0 200px;
background: #fef;
}
.table-wrapper {
width: 100%;
overflow-x: auto;
background: pink;
}
.table-wrapper td {
min-width: 400px;
}
<script src="https://unpkg.com/chance#1.0.16/chance.js"></script>
<div class="outer">
<div class="sidebar">
<div class="lorem" p="4"></div>
</div>
<div class="mainbar">
<div class="table-wrapper">
<table>
<tr>
<td class="lorem"></td>
<td class="lorem"></td>
<td class="lorem"></td>
<td class="lorem"></td>
</tr>
</table>
</div>
<div class="lorem" p="10"></div>
</div>
<div class="rightbar">
<div class="lorem" p="3"></div>
</div>
</div>
If I understand you correct, add flex: 1; min-width: 0; to your .mainbar rule and it should behave.
The flex: 1 will make it take available space and min-width: 0 will allow a flex item to be smaller than its content, which you can read more about here:
Why don't flex items shrink past content size?
Stack snippet
// this JS generates placeholder text, ignore it
for (const el of document.querySelectorAll(".lorem")) {
el.innerHTML = Array(Number(el.getAttribute("p")) || 1)
.fill()
.map(() => `<p>${chance.paragraph()}</p>`)
.join("");
}
body {
margin: 0;
}
.outer {
display: flex;
}
.sidebar {
flex: 0 0 300px;
background: #eef;
}
.mainbar {
background: #ffe;
flex: 1;
min-width: 0;
/* width: calc(100% - 500px); */
}
.rightbar {
flex: 0 0 200px;
background: #fef;
}
.table-wrapper {
width: 100%;
overflow-x: auto;
background: pink;
}
.table-wrapper td {
min-width: 400px;
}
<script src="https://unpkg.com/chance#1.0.16/chance.js"></script>
<div class="outer">
<div class="sidebar">
<div class="lorem" p="4"></div>
</div>
<div class="mainbar">
<div class="table-wrapper">
<table>
<tr>
<td class="lorem"></td>
<td class="lorem"></td>
<td class="lorem"></td>
<td class="lorem"></td>
</tr>
</table>
</div>
<div class="lorem" p="10"></div>
</div>
<div class="rightbar">
<div class="lorem" p="3"></div>
</div>
</div>
.block {
width: 445px;
height: 544px;
border-bottom: 1px solid rgba(24, 24, 28, 0.45);
}
.table {
width: 100%;
height: 100%;
border-spacing: 3px;
border-collapse: separate;
}
.row {
height: calc(100% / 7);
width: 100%;
}
.cell {
width: calc(100% / 7);
height: auto;
text-align: center;
font-size: 24px;
font-weight: bold;
}
<div class="block">
<table class="table">
<tr class="row">
<td class="cell">A</td>
<td class="cell">B</td>
<td class="cell">C</td>
<td class="cell">D</td>
<td class="cell">E</td>
<td class="cell">F</td>
<td class="cell">G</td>
</tr>
<tr class="row">
<td class="cell">1</td>
<td class="cell">2</td>
<td class="cell">3</td>
<td class="cell">4</td>
<td class="cell">5</td>
<td class="cell">6</td>
<td class="cell">7</td>
</tr>
</table>
</div>
I have a problem with height of the row element - I want it to be height of table (100%) divided by 7 (and I have calc(100% / 7)), but it doesn't work. As you see on the screen - it is 267px and should be much less. I want this rows to start on the top of the table with normal height (so there should be two rows, one under the other and a lot of blank space underneath, because the table have 100% height of block element). I am creating rows dynamically and they will fill all the table (so I will have 7 rows which will have the same height and which will fill all the table). Where I made a mistake? Thanks for your help!
Desired output with two rows:
Desired output with seven rows:
You just need to define a display property on the row for the height to take effect.
See this example: https://jsfiddle.net/dieguezz/q2j6me6h/
.row {
height: calc(100% / 7);
width: 100%;
display: flex;
align-items: center;
}