Im having trouble making a valid HTML table with vertical spacing between rows and shadow below each row.
The shadow always goes on top of other table data.
I have positioned the elements and set a z-index.
table {
border-collapse: separate;
border-spacing: 0;
}
td,
th {
min-width: 170px;
}
.shadow {
position: relative;
z-index: 1;
margin: 2px 0 2px 0;
}
.shadow:before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: -1;
box-shadow: 0 0 10px 10px #000;
}
<main>
<table>
<thead>
<tr>
<td>head</td>
<td>head</td>
<td>head</td>
</tr>
</thead>
<tbody>
<tr>
<td><div class="shadow">div</div></td>
<td><div class="shadow">div</div></td>
<td><div class="shadow">div</div></td>
</tr>
<tr>
<td><div class="shadow">div</div></td>
<td><div class="shadow">div</div></td>
<td><div class="shadow">div</div></td>
</tr>
<tr>
<td><div class="shadow">div</div></td>
<td><div class="shadow">div</div></td>
<td><div class="shadow">div</div></td>
</tr>
</tbody>
<tfoot>
<tr>
<td>foot</td>
<td>foot</td>
<td>foot</td>
</tr>
</tfoot>
</table>
</main>
One approach is as below, with explanatory comments in the code itself:
/* removing all default padding and margins, and ensuring
that all elements are sized to include their padding
and border-widths: */
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
table {
/* CSS custom properties for various properties to ensure
common styling where appropriate;
--tr-space-between is the size of the percieved/visible
gap between adjacent rows: */
--tr-space-between: 0.5rem;
/* --td-padding-block is derived from the previous variable,
using calc() so that the space between 'rows' is inserted
as padding (padding-block) to create that row-spacing: */
--td-padding-block: calc(var(--tr-space-between)/2);
--shadow-color: lightgray;
--row-color: #fff;
/* the desired radius of the 'rows': */
--row-radius: 0.5rem;
/* to ensure page background can be seen (if required)
in the row-gaps: */
background-color: transparent;
/* collapsing the borders between cells in order to allow the
content to be contiguous, and using a different means to
achieve row-"separation": */
border-collapse: collapse;
border-spacing: 0;
/* using a CSS filter, drop-shadow(), to create the shadows: */
filter: drop-shadow(0 0 0.5rem var(--shadow-color));
/* centering the <table> */
margin-inline: auto;
}
td,
th {
min-width: 170px;
}
td,
th {
/* again to ensure that the page background is - where
appropriate - visible through the visual gaps: */
background-color: transparent;
}
td {
/* setting the cell padding on the block axis, to "separate"
the "rows", while no padding is applied on the inline
axis, so that the rows are visually contiguous: */
padding-block: var(--td-padding-block);
}
/* using logical properties to set the border radii: */
td:first-child .content {
border-start-start-radius: var(--row-radius);
border-end-start-radius: var(--row-radius);
}
td:last-child .content {
border-start-end-radius: var(--row-radius);
border-end-end-radius: var(--row-radius);
}
.content {
/* setting the background-color of the "row": */
background-color: var(--row-color);
/* applying padding on all axes, to move the content
away from the edges of the 'row': */
padding: 0.5rem;
}
<main>
<table>
<thead>
<tr>
<th>head</th>
<th>head</th>
<th>head</th>
</tr>
</thead>
<tbody>
<!-- I've changed the class-name of the <div> from 'shadow' to
'content' to reflect what the "purpose" of the element: -->
<tr>
<td>
<div class="content">div</div>
</td>
<td>
<div class="content">div</div>
</td>
<td>
<div class="content">div</div>
</td>
</tr>
<tr>
<td>
<div class="content">div</div>
</td>
<td>
<div class="content">div</div>
</td>
<td>
<div class="content">div</div>
</td>
</tr>
<tr>
<td>
<div class="content">div</div>
</td>
<td>
<div class="content">div</div>
</td>
<td>
<div class="content">div</div>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>foot</td>
<td>foot</td>
<td>foot</td>
</tr>
</tfoot>
</table>
</main>
There are, of course, other approaches; I've shown a couple more in the following example, again: explanatory comments are in the code:
const changeBackground = (evt) => {
let {
currentTarget
} = evt, {
value
} = currentTarget,
table = document.querySelector('table');
table.dataset.shadow = value;
}
document.querySelectorAll('input[type=radio]').forEach(
(el) => el.addEventListener('change', changeBackground)
);
document.querySelector('input:checked').dispatchEvent(new Event('change'));
/* removing all default padding and margins, and ensuring
that all elements are sized to include their padding
and border-widths: */
*,
::before,
::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
/* a relatively simple background to show how the transparencies
might be beneficial; otherwise irrelevant to the demo: */
background-image:
repeating-linear-gradient(
to bottom left,
transparent,
hsl(140deg 90% 80% / 0.7)
), radial-gradient(
at 0 0,
hsl(300deg 95% 85% / 1),
hsl(180deg 95% 85% / 1)
);
block-size: 100vh;
padding: 1rem;
}
form {
inline-size: 70%;
margin-block: 1rem;
margin-inline: auto;
}
fieldset {
border: 0 none transparent;
display: flex;
flex-flow: row wrap;
gap: 1rem;
justify-content: space-between;
padding: 1rem;
}
legend {
font-size: 1.2rem;
position: relative;
}
label {
flex-grow: 1;
}
.labelText {
/* using a CSS custom property to simplify the CSS: */
--active-indication: hsl(0deg 75% 70% / 0.55);
background-color: #cccd;
/* using a background-image to give show whether a
selection has, or has not, been made; this is the
default 'inactive' state: */
background-image:
linear-gradient(
270deg,
var(--active-indication) 0 1.5rem,
/* gives the illusion of a border separating the
background-color of the element, and the
'activation'/checked state: */
#0009 1.5rem calc(1.5rem + 1px),
/* transparent, to allow the background to show through: */
transparent calc(1.5rem + 1px)
);
border: 1px solid #0009;
border-radius: 1rem;
display: block;
padding: 0.75rem;
padding-inline-end: 2rem;
}
/* moving the radio inputs off screen to hide them: */
input[type=radio] {
position: absolute;
left: -2000px;
}
/* this selector matches the .labelText that immediately
follows a checked <input>; despite being hidden off-screen
the <input> still precedes the .labelText element in
the DOM: */
input:checked + .labelText {
/* here we update the custom property, which updates
the background-image/linear-gradient: */
--active-indication: hsl(160deg 95% 75% / 1);
}
table {
/* CSS custom properties for various properties to ensure
common styling where appropriate;
--tr-space-between is the size of the percieved/visible
gap between adjacent rows: */
--tr-space-between: 0.5rem;
/* --td-padding-block is derived from the previous variable,
using calc() so that the space between 'rows' is inserted
as padding (padding-block) to create that row-spacing: */
--td-padding-block: calc(var(--tr-space-between)/2);
--shadow-color: #339a;
--row-color: #fff;
--row-inset: 1rem;
/* the desired radius of the 'rows': */
--row-radius: 0.5rem;
/* to ensure page background can be seen (if required)
in the row-gaps: */
background-color: transparent;
/* collapsing the borders between cells in order to allow the
content to be contiguous, and using a different means to
achieve row-"separation": */
border-collapse: collapse;
border-spacing: 0;
margin-block: 1rem;
/* centering the <table> */
margin-inline: auto;
}
/* using attribute-selectors along wtih custom data-* attributes
to appropriately style the <table> based on the choice made as
to the approach: */
table[data-shadow="drop-shadow"] {
/* using a CSS filter, drop-shadow(), to create the shadows: */
filter: drop-shadow(0 0 2rem var(--shadow-color));
}
table[data-shadow="box-shadow"] {
/* using a box-shadow, note that this provides a shadow *around*
the element, but not between the 'rows' (this is why I didn't
use box-shadow in the original code, and this is simply to
illustrate that point): */
box-shadow: 0.5rem 0.5rem 2rem var(--shadow-color);
}
table[data-shadow="backdrop-filter"] {
/* this allows a number of different functions to be used to
to style the view of whatever is visible "through" the
background of the <table> element: */
backdrop-filter: hue-rotate(245deg) opacity(0.4);
}
td,
th {
min-width: 170px;
}
td,
th {
/* again to ensure that the page background is - where
appropriate - visible through the visual gaps: */
background-color: transparent;
}
td {
/* setting the cell padding on the block axis, to "separate"
the "rows", while no padding is applied on the inline
axis, so that the rows are visually contiguous: */
padding-block: var(--td-padding-block);
}
/* using logical properties to set the border radii: */
td:first-child .content {
border-start-start-radius: var(--row-radius);
border-end-start-radius: var(--row-radius);
}
td:last-child .content {
border-start-end-radius: var(--row-radius);
border-end-end-radius: var(--row-radius);
}
td:first-child {
padding-inline-start: var(--row-inset);
}
td:last-child {
padding-inline-end: var(--row-inset);
}
.content {
/* setting the background-color of the "row": */
background-color: var(--row-color);
/* applying padding on all axes, to move the content
away from the edges of the 'row': */
padding: 0.5rem;
}
<main>
<form action="#" id="choices" method="post">
<fieldset>
<legend>Choose approach</legend>
<label>
<input type="radio" name="shadowType" value="drop-shadow" checked>
<span class="labelText"><code>filter: drop-shadow()</code></span>
</label>
<label>
<input type="radio" name="shadowType" value="box-shadow" >
<span class="labelText"><code>box-shadow</code></span>
</label>
<label>
<input type="radio" name="shadowType" value="backdrop-filter" >
<span class="labelText"><code>backdrop-filter</code></span>
</label>
</fieldset>
</form>
<table>
<thead>
<tr>
<th>head</th>
<th>head</th>
<th>head</th>
</tr>
</thead>
<tbody>
<!-- I've changed the class-name of the <div> from 'shadow' to
'content' to reflect what the "purpose" of the element: -->
<tr>
<td>
<div class="content">div</div>
</td>
<td>
<div class="content">div</div>
</td>
<td>
<div class="content">div</div>
</td>
</tr>
<tr>
<td>
<div class="content">div</div>
</td>
<td>
<div class="content">div</div>
</td>
<td>
<div class="content">div</div>
</td>
</tr>
<tr>
<td>
<div class="content">div</div>
</td>
<td>
<div class="content">div</div>
</td>
<td>
<div class="content">div</div>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td>foot</td>
<td>foot</td>
<td>foot</td>
</tr>
</tfoot>
</table>
</main>