CSS Grid with an additional wrapper in the middle - css

I want to use CSS Grid's grid-template-areas.
But the problem is that the CMS I'm using is adding lots of additional wrappers.
Is there a way to ignore extra wrappers? since it is messing up the nice grid areas...
I'm trying to override css grid's auto-placement mechanism.
So any div that is in the middle, and wasn't assigned a specific grid-area, will appear at the end of the grid, and wouldn't mess with the grid itself.
I created an example of the problem here -
https://codepen.io/shaal/pen/qPvQWW
You can see that because of the extra wrapper, the 'sidebar' element is not assigned to the areas I wanted it to be.
HTML
.container {
display: grid;
grid-gap: 5px;
grid-template-areas: "header header header" "sidebar content content" "sidebar content content" "footer footer footer";
}
.header {
grid-area: header;
}
.sidebar {
grid-area: sidebar;
}
.content {
grid-area: content;
height: 180px;
}
.footer {
grid-area: footer;
}
<h1>CSS GRID</h1>
<div class="container">
<div class="item header">Header</div>
<div class="cms-annoying-wrapper">
<div class="item sidebar">Sidebar</div>
<div class="item content">Content</div>
</div>
<div class="item footer">Footer</div>
</div>

Grid Layout Module Level 2 - Subgrids are supposed to solve this problem.
In the meantime, there is a workaround:
display: contents (caniuse)
From Caniuse:
display: contents causes an element's children to appear as if they
were direct children of the element's parent, ignoring the element
itself. This can be useful when a wrapper element should be ignored
when using CSS grid or similar layout techniques.
So in your scenario you could simply add the rule:
.cms-annoying-wrapper {
display: contents;
}
body {
margin: 10px;
text-align: center;
width: 580px;
}
.cms-annoying-wrapper {
display: contents;
}
.container {
display: grid;
grid-gap: 5px;
grid-template-areas:
"header header header"
"sidebar content content"
"sidebar content content"
"footer footer footer";
}
.item {
color: white;
padding: 1.5em 0;
font-size: 2em;
}
.header {
background: #0d6;
}
.sidebar {
background: #f00;
}
.content {
background: #d60;
}
.footer {
background: #60d;
}
.header {
grid-area: header;
}
.sidebar {
grid-area: sidebar;
}
.content {
grid-area: content;
height: 180px;
}
.footer {
grid-area: footer;
}
<h1>CSS GRID</h1>
<div class="container">
<div class="item header">Header</div>
<div class="cms-annoying-wrapper">
<div class="item sidebar">Sidebar</div>
<div class="item content">Content</div>
</div>
<div class="item footer">Footer</div>
</div>
Codepen Demo (firefox)

If using the unwrap jQuery function to remove this div is not a problem for you I would use it.
$(".sidebar").unwrap(".cms-annoying-wrapper");
Otherwise in plain JS :
https://plainjs.com/javascript/manipulation/unwrap-a-dom-element-35/

Related

Struggling to place 2 Divs on the same line with Grid Template

Basically I am trying to recreate this image:
https://imgur.com/a/OcgGfmn
But struggling on the LightBlue and Red section on putting them on the same line:
.sidebar {
background-color: lightblue;
width:20%;
min-height:100px;
}
.content {
background-color: red;
width:80%;
min-height:100px;
}
So far I have tried Flex, inline-block and changing the widths, but cannot seem to get them from not appearing under each other
Inmy container I have:
.container {
display:grid;
grid-template-areas:
"navbar navbar navbar navbar"
"sidebar content"
"footer footer footer footer"
}
See below. Using grid-area for all relevant classes and width removed for sidebar and content.
.navbar {
grid-area: navbar;
background-color: lightgreen;
}
.sidebar {
background-color: lightblue;
min-height: 100px;
grid-area: sidebar;
}
.content {
background-color: red;
min-height: 100px;
grid-area: content;
}
.footer {
grid-area: footer;
background-color: lightgreen;
}
.container {
display: grid;
grid-template-areas: "navbar navbar navbar navbar" "sidebar content content content" "footer footer footer footer"
}
<div class="container">
<div class="navbar">navbar</div>
<div class="sidebar">sidebar</div>
<div class="content">content</div>
<div class="footer">footer</div>
</div>

How to stack unknown number of generic content items in a CSS Grid template area

I haven't quite got my head around grid-template-areas, I suspect. Or maybe I do, but it is the behaviour of direct Grid descendant nodes that do not have a grid-area assigned.
Here is visually what I am trying to achieve:
Codepen is here: https://codepen.io/davewallace/pen/abYxWxE
Similar code example:
html, body , .container {
height: 100%;
margin: 0;
}
.container {
display: grid;
grid-template-rows: auto 1fr;
grid-template-columns: 1fr max-content;
gap: 40px 40px;
grid-template-areas:
"header header"
". graphic";
}
.header { grid-area: header; }
.graphic { grid-area: graphic; }
/* For presentation only, no need to copy the code below */
.container * {
border: 1px solid red;
position: relative;
}
<div class="container">
<div class="header">Full-width title</div>
<div class="graphic">Graphic</div>
<h4>Random element here</h4>
<p>Random element here</p>
<p>Random element here</p>
</div>
I seem to be able to only achieve two things currently:
Everything is in the right place, but all "generic content items" overlap on top of each other, where instead I want them stacked.
The title is in the right place, the image is sort of in the right place, but columns and/or rows are added automatically and do things like wrap under the image
The MDN docs for grid-template-area haven't really cleared the mix of behaviours up for me. I also tried using https://grid.layoutit.com/ to visualise the layout, and that seemed to get me off to a good start, but the default behaviour of the generic nodes has me confused.
You can approximate it like below:
body {
margin: 0;
}
.container {
min-height: 100vh;
display: grid;
grid-template-columns: 1fr max-content;
grid-auto-flow: dense;
gap: 0 40px; /* set only column gap */
}
.header {
grid-row: 1;
grid-column: 1/-1;
}
.graphic {
grid-column: 2;
grid-row: 2/ span 100; /* this will do the trick */
}
.container *:not(.graphic,:last-child) {
margin-bottom: 40px; /* replace the row gap */
}
/* For presentation only, no need to copy the code below */
.container * {
border: 1px solid red;
position: relative;
margin: 0;
}
<div class="container">
<div class="header">Full-width title</div>
<div class="graphic">Some Graphic here</div>
<h4>Random element here</h4>
<p>Random element here</p>
<p>Random element here</p>
</div>
I was trying to solve your CSS problem but it looks like there is no straightforward solution to it. According to the CSS spec, You cannot span a grid item to occupy all the rows/columns in an implicit grid.
Refer to this answer for more info - span grid item to all rows/columns
But I've come to a solution that could fix your grid by introducing a parent container for the random elements. In that way, you will only need two rows and two columns.
.container {
display: grid;
grid-template-columns: 1fr max-content;
gap: 40px 40px;
grid-template-areas: "header header" ". graphic";
}
.header {
grid-area: header;
}
.graphic {
grid-area: graphic;
}
/* Just to highlight */
.container * {
border: 1px solid;
}
<div class="container">
<div class="header">Full-width title</div>
<div class="graphic">Graphic</div>
<div class="random">
<h4>Random element here1</h4>
<p>Random element here2</p>
<p>Random element here3</p>
<p>Random element here4</p>
<p>Random element here4</p>
<p>Random element here4</p>
</div>
</div>

why does the height of the last item in my grid affect the width of the top level grid items?

https://jsfiddle.net/nqfzks6m/1/
above is the jsfiddle for my grid. As you can see, if you change the height of card-4-content to 50px, card 1 becomes slightly wider and card 2 becomes less wide. What is causing this? The grid template columns are set to "auto auto auto auto". I can't see why the height of the bottom row would affect the width of the top level row.
.base-card {
border: 1px solid black;
}
.card-1 {
grid-area: card-1;
background-color: red;
}
.card-2 {
grid-area: card-2;
background-color: blue;
}
.card-3 {
grid-area: card-3;
background-color: purple;
}
.card-4 {
grid-area: card-4;
background-color: pink;
}
.card-4-content {
height: 50px;
}
.container {
display: grid;
grid-template-columns: auto auto auto auto;
grid-gap: 25px;
grid-template-rows: 155px 160px auto auto;
grid-template-areas: "card-1 card-1 card-2 card-2" "card-1 card-1 card-2 card-2" "card-3 card-3 card-3 card-3" "card-4 card-4 card-4 card-4";
}
<div class="container">
<div class="card-1">
<div class="base-card">
card-1
</div>
</div>
<div class="card-2">
<div class="base-card">
card-2
</div>
</div>
<div class="card-3">
<div class="base-card">
card-3
</div>
</div>
<div class="card-4">
<div class="base-card card-4-content">
card-4
</div>
</div>
</div>

How to arrange items in css grid with auto heights

I want to make a fairy simple looking layout with css grid.
As, shown in above image, i want to place 3 items in a grid in the exact same order as shown in the image, I don't want to set specific height to any of the item, since each of the item have different height depending upon the content in it. I want to give 70% of grid horizontal space to item1 and 30% to item2 & item3, item3 should be placed beneath item2. Again, heights of items should be auto. I am trying to achieve this for many hours but failed to do so.
Markup of the problem:
<div class="container">
<div class="item1">some content in it...</div>
<div class="item2">some Content in it...</div>
<div class="item3">some Content in it...</div>
</div>
Solution as requested:
You could use grid-template-areas to span the first item across multiple rows. And use a spacer at the end of the right column to make the items just as big as needed.
We can use calc to account for the grip-gap.
.grid {
display: grid;
grid-gap: 20px;
grid-template-columns: calc(70% - 10px) calc(30% - 10px);
grid-template-areas: "item-1 item-2" "item-1 item-3" "item-1 spacer";
border: 1px dashed #000;
align-items: start;
}
.item {
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
font-weight: bold;
}
#media screen and (max-width: 600px) {
.grid {
grid-template-areas: "item-2" "item-1" "item-3";
grid-template-columns: 100%;
}
}
.item-1 {
grid-area: item-1;
}
.item-2 {
grid-area: item-2;
}
.item-3 {
grid-area: item-3;
}
.purple {
background-color: #5B00FF;
}
.red {
background-color: #FF0000;
}
.pink {
background-color: #FF00FD
}
.h-500 {
height: 500px;
}
.h-100 {
height: 100px;
}
.h-200 {
height: 200px;
}
<div class="grid">
<div class="item item-1 purple h-500">Item 1</div>
<div class="item item-2 red h-100">Item 2</div>
<div class="item item-3 pink h-200">Item 3</div>
</div>
Alternative solution with different columns:
You could use grid-gap along with grid-template-columns. You have to take the grid-gap into account for the width of template-column That's why there is this calc.
.grid {
display: grid;
grid-gap: 20px;
grid-template-columns: calc(70% - 10px) calc(30% - 10px);
border: 1px dashed #000;
}
.col--right {
display: flex;
flex-flow: column nowrap;
gap: 20px;
}
.item {
display: flex;
justify-content: center;
align-items: center;
font-size: 32px;
font-weight: bold;
}
.purple {
background-color: #5B00FF;
}
.red {
background-color: #FF0000;
}
.pink {
background-color: #FF00FD
}
.h-500 {
height: 500px;
}
.h-100 {
height: 100px;
}
.h-200 {
height: 200px;
}
<div class="grid">
<div class="col col--left">
<div class="item purple h-500">Item 1</div>
</div>
<div class="col col--right">
<div class="item red h-100">Item 2</div>
<div class="item pink h-200">Item 3</div>
</div>
</div>

CSS Grid use specific item height as max row height

I'm trying to prevent the sidebar height from exceeding the content height.
The "area" will contain an image of any height so its height is not fixed or known in advance.
function toggleTabsContent() {
const display = document.querySelector('.content').style.display;
if (display == "none") {
document.querySelector('.content').style.display = "block";
} else {
document.querySelector('.content').style.display = "none";
}
}
.grid {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 1fr auto;
grid-template-areas:
"tabs area"
"footer footer";
}
.tabs {
grid-area: tabs;
background: #FF9800;
}
.area {
grid-area: area;
height: 200px;
background: #673AB7;
}
.footer {
grid-area: footer;
background: #607D8B;
height: 40px;
}
.content {
height: 250px;
}
<button onclick="toggleTabsContent()">Make tabs taller/shorter</button>
<div class="grid">
<div class="tabs">
<div class="content" style="display: none"></div>
</div>
<div class="area"></div>
<div class="footer"></div>
</div>
JSFiddle
Display: flex; allows for a lot of flexibility - in fact it's purpose is exactly to flex the height or width to the required dimensions per the content inside of it.
Based on your question, this is exactly what you need.
I've separated your footer from the grid, in order to separate statically sized elements, from those you wish to flex.
Next I've added a min-width to your area, to avoid it collapsing completely. However if you want this functionality, you can remove this. i.e. if you want that when there is no image it doesn't appear, you can remove this.
Lastly, the display: flex; and flex-flow: row nowrap; allows the sizing to flex, and for elements within the grid (now named "flex") to be placed next to each other with the same dimensions and they won't wrap to the next line to accommodate the sizing changes.
function toggleTabsContent() {
const display = document.querySelector('.content').style.display;
if (display == "none") {
document.querySelector('.content').style.display = "block";
} else {
document.querySelector('.content').style.display = "none";
}
}
.flex {
display: flex;
flex-flow: row nowrap;
}
.tabs {
background: #FF9800;
height: auto;
width: 100%;
}
.area {
height: auto;
min-height: 200px;
width: 100%;
background: #673AB7;
}
.footer {
background: #607D8B;
height: 40px;
}
.content {
height: 400px;
}
<button onclick="toggleTabsContent()">Make tabs taller/shorter</button>
<div class="flex">
<div class="tabs">
<div class="content" style="display: none"></div>
</div>
<div class="area"></div>
</div>
<div class="footer"></div>
Use height:0;min-height:100%; on the tabs element. This will make sure the height of the element will not contribute on the size of its track then by adding min-height:100% you force it to use the height of the track defined by other elements:
function toggleTabsContent() {
const display = document.querySelector('.content').style.display;
if (display == "none") {
document.querySelector('.content').style.display = "block";
} else {
document.querySelector('.content').style.display = "none";
}
}
.grid {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 1fr auto;
grid-template-areas:
"tabs area"
"footer footer";
}
.tabs {
grid-area: tabs;
background: #FF9800;
height:0;
min-height:100%;
overflow:auto;
}
.area {
grid-area: area;
height: 200px;
background: #673AB7;
}
.footer {
grid-area: footer;
background: #607D8B;
height: 40px;
}
.content {
height: 250px;
}
<button onclick="toggleTabsContent()">Make tabs taller/shorter</button>
<div class="grid">
<div class="tabs">
<div class="content" style="display: none"></div>
</div>
<div class="area"></div>
<div class="footer"></div>
</div>
Related question: How can you set the height of an outer div to always be equal to a particular inner div?
You can set the max-height of tabs to be the height of area
.tabs {
grid-area: tabs;
background: #FF9800;
max-height: 400px;
overflow: scroll;
}
I added lot of H1 elements and it worked the way you wanted it.
JS Fiddle
You should go the flex route, but if the only content in .tabs is an image, you can set it as a background-image and let it automatically fill the area with background-size: cover.
document.getElementById("slider").addEventListener('change', changeAreaHeight);
function changeAreaHeight(event) {
let sliderValue = event.target.value;
document.querySelector('.area').style.height = sliderValue + 'px';
}
.grid {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: 1fr auto;
grid-template-areas:
"tabs area"
"footer footer";
}
.tabs {
grid-area: tabs;
background: #FF9800;
background-image: url('https://i.picsum.photos/id/167/100/300.jpg'); /* NEW */
background-size: cover; /* NEW */
}
.area {
grid-area: area;
height: 200px;
background: #673AB7;
}
.footer {
grid-area: footer;
background: #607D8B;
height: 40px;
}
<input type="range" id="slider" min="50" value="200" max="800" />
<label>Change height on .area</label>
<div class="grid">
<div class="tabs">
<div class="content" style="display: none"></div>
</div>
<div class="area"></div>
<div class="footer"></div>
</div>

Resources