What is the best responsive grid solution for the following layout? - css

I'm trying to figure out the best way to achieve this grid layout. Naturally, it has to be responsive so the layout changes and goes through 3 states, from large to medium to small screens. I have attached an image of what I'm trying to get. I am using Bootstrap so have access to their grid feature but I'm not sure if this might be too complicated for that
Would a layout like this be better suited to a JS plugin (masonry or alike) or would flexbox or CSS grid be able to achive something like this?

This is a classic scenario for just plain CSS Grids.
Below you have a super simple implementation of this (note: there are better ways to do this, more implicit and shorthanded versions that don't even require some of the breakpoints, but this is the more clear way to understand what's going on imo).
I recommend reading this article for full description of the CSS Grids spec:
https://css-tricks.com/snippets/css/complete-guide-grid/
Here is a Codepen with the working solution:
https://codepen.io/sergiofruto/pen/zYqaLEL
HTML
<div class="grid">
<div class="grid-item">1</div>
<div class="grid-item">2</div>
<div class="grid-item">3</div>
<div class="grid-item">4</div>
<div class="grid-item">5</div>
</div>
CSS
.grid {
display: grid;
grid-gap: 10px;
width: 100%;
/*no explicit height, let the children determine it */
}
.grid-item {
/*arbitrary height, could be less, more, or grow depending on the content */
height: 200px;
font-size: 32px;
color: white;
background-color: black;
text-align: center;
border: 2px solid white;
}
#media (min-width: 768px) {
.grid {
/* here we set the two column layout fr are special units for grids, it represents the available space */
grid-template-columns: 1fr 1fr;
}
}
#media (min-width: 1024px) {
.grid {
height: 400px;
/* here we set the four column layout, fr are special units for grids, it represents the available space */
grid-template-columns: 1fr 1fr 1fr 1fr;
/* here we set the two row layout, fr are special units for grids, it represents the available space */
grid-template-rows: 1fr 1fr;
}
.grid-item {
height: auto;
}
.grid-item:first-child {
/* here we set a special size for the first children of the grid */
grid-column: 1 / 3;
grid-row: 1 / 3;
}
}

Related

Center The Leftover Item From The Last Row In GRID (1fr 1fr)

This is something that I've been struggling with for a while, but I can't seem to find a way to do it.
If you have an odd number of items in grid and you want 2 items per row (1fr 1fr), you end up with a single item in the last row that is left-centered.
I just want to make it centered so it looks nicer.
Here's a picture too.
You can try something like this jsfiddle:
/* visibility properties */
body {
width: 60%;
margin: 5% auto;
}
div {
margin: 3%;
width: 100px;
height: 100px;
background-color: black;
justify-self: center;
}
div:nth-of-type(2n) {
background-color: red;
}
/* actual code: */
section {
display: grid;
grid-template-columns: 1fr 1fr;
}
#last-div {
grid-column: 1 / span 2;
}
<section>
<div>
</div>
<div>
</div>
<div>
</div>
<div>
</div>
<div id="last-div">
</div>
</section>
Get more info on CSS Grid: complete-guide-grid
You could try something like this since I faced a similar issue in one of my earlier projects.
grid-template-columns : repeat(auto-fit, minmax(<minSize>, 1fr));
Set minSize to whatever minimum width you want an element to occupy.

Changing grid-template-areas without media query?

I am trying to achieve the below layout without using media queries. The reason I'd prefer to not use media queries is that this content is embedded on a site with a sidebar on the left that can expand and collapse. When it's expanded, it takes up about 400px, and it takes up maybe 50 when it's collapsed. It expands and collapses based off of a user interaction, not screen-width, so there's no good way for me to detect if it's open or not from CSS. Because of this, there could be some errors in my layout if I use a media query based on the screen width, instead of the actual width of the content. Is there any way I can use something like flex-basis or minmax to achieve this layout without media queries?
/* functional code */
.a1 {
grid-area: a1;
}
.a2 {
grid-area: a2;
}
.b1 {
grid-area: b1;
}
.grid {
display: grid;
grid-template-rows: 400px 200px 200px;
grid-template-columns: 1fr 300px;
grid-template-areas:
"a1 b1"
"a2 b1"
"a2 .";
}
/* Can I emulate this on the container width instead of the screen width? */
#media (max-width:1000px) {
.grid {
grid-template-columns: 1fr;
grid-template-areas: "a1"
"b1"
"a2";
grid-template-rows: 400px 600px 200px;
}
.b1 {
width:300px;
justify-self:center;
}
}
/* other styling */
.grid {
gap:8px;
}
.grid * {
border-radius: 5px;
background-color: lightgray;
display: grid;
place-items: center;
text-align: center;
}
.collapsed { display: none; }
#media (max-width:1000px) {
.uncollapsed {
display: none;
}
.collapsed {
display: block;
}
}
<div class="uncollapsed">Uncollapsed State (make screen smaller to see collapsed state)</div>
<div class="collapsed">Collapsed State (make screen bigger to see uncollapsed state)</div>
<div class="grid">
<div class="a1">
A1
<br>
Top-left normally
<br>
Top when collapsed
</div>
<div class="a2">
A2
<br>
Bottom-left normally
<br>
Bottom when collapsed
</div>
<div class="b1">
B1
<br>
Right side normally
<br>
Middle row when collapsed
</div>
</div>
FYI: I ended up just using a ResizeObserver polyfill and some JavaScript. Definitely not ideal, but it seems like container queries are currently impossible without JS.

CSS Grid - How to make items responsive

I was trying to make the 2 items (box1 and box2) responsive on small screen, but I couldn't seem to figure it out. Please help. Thanks!
<html lang="en">
<head>
<style>
body{
background: lightblue;
}
.container{
padding:10px;
display: grid;
background: lightyellow;
width:100%;
grid-gap:5px;
justify-content:center;
grid-auto-flow: column;
grid-auto-columns: 300px 100px;
}
.box1{
background: lightgray;
min-height:150px;
}
.box2{
background: lightgreen;
min-height:150px;
}
</style>
</head>
<body>
<div class="container">
<div class="box box1">BOX 1</div>
<div class="box box2">BOX 2</div>
</div>
</body>
</html>
You've told the columns to be a fixed width...so they're naturally not responsive.
Use percentage or fractional values instead.
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
::before,
::after {
box-sizing: inherit;
}
body {
background: lightblue;
}
.container {
padding: 10px;
display: grid;
background: lightyellow;
width: 100%;
grid-gap: 5px;
justify-content: center;
grid-auto-flow: column;
grid-auto-columns: 3fr 1fr;
}
.box1 {
background: lightgray;
min-height: 150px;
}
.box2 {
background: lightgreen;
min-height: 150px;
}
<div class="container">
<div class="box box1">BOX 1</div>
<div class="box box2">BOX 2</div>
</div>
grid-auto-columns: 300px 100px;
in that line above you use ABSOLUTE sizes. If you want them to be responsive use % instead of px.
something like
grid-auto-columns: 30% 10%;
Any time you use fixed pixel widths, your elements will remain at that size and not be responsive.
The quick and easy solution to this is to switch to percentage widths, which tells the element to be a proportion of its container's size. Assuming the container is itself responsive, then this will make your elements change size according to the width of the screens. You need to do this all the way through your CSS, as any fixed sizes further up the element tree could stop everything inside from responding.
However, a naive percentage figure is often not a perfect solution, because things may not look right with the same proportions at lower screen sizes. For example, a three-column layout may shrink down, but it will look very squashed on a small mobile phone screen.
There are a bunch of solutions to this, and the exact answer will depend on your page design and your preferences.
First up, consider using min-width and max-width with pixel sizes to limit the sizes of your elements. These CSS values will override the percentage if the percentage figure causes them to go above or below the max or min width that you specify. This can be helpful for preventing things from getting exessively squashed or stretched out while still responding appropriately within the desired range.
Next, you need to know about Media Queries. This is a CSS feature that allows you to specify CSS that is only applied when the browser size is within a specified range. (Media queries can do a lot more than this, but I'll leave it to you to investigate them further)
An example might help here:
#media(max-width:600px) {
.container {
grid-auto-flow: unset;
grid-auto-columns: unset;
}
}
The example above uses a media query to switch off your columns if the browser width is 600 pixels or less. For narrow browsers, a column-based layout may not be appropriate, so switching away from it at low resolutions is often a good idea.

CSS Grid auto fit with max-content

I have 4 columns. The actual content for columns 1 and 4 is 150px, column 2 is 250px and column 3 is 370px. I want to wrap the columns when the browser width changes. When I decrease the width of the browser, I want each column to shrink down to their lowest width before wrapping. So I imagine the 4th column would fall to the next row with a 100% width after it fell below 150px width.
Here's what I thought should've done the trick:
repeat(auto-fit, minmax(max-content, 1fr))
Is there a way to achieve this without passing a fixed width where 'max-content' is?
Here's my solution using media queries and hard widths
https://jsfiddle.net/9hjb5qv8/
Here's the html/css I used in the fiddle above:
.container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(370px, 1fr));
grid-gap: 8px;
}
#media (max-width: 799px) {
.container {
grid-template-columns: minmax(max-content, 1fr);
}
}
#media (min-width: 800px) {
.container .p2,
.container .p3 {
grid-column: auto / span 2;
}
}
.container > div {
background-color: gray;
text-align: center;
}
<div class="container">
<div class="p1">
<img src="https://via.placeholder.com/150x150">
</div>
<div class="p2">
<img src="https://via.placeholder.com/250x150">
</div>
<div class="p3">
<img src="https://via.placeholder.com/370x150">
</div>
<div class="p4">
<img src="https://via.placeholder.com/150x150">
</div>
</div>
I had a similar question when playing around with grid:
grid-template-columns: repeat(auto-fit, minmax(max-content, 1fr))
If we take a look at the documentation we can see that minmax command is valid:
https://developer.mozilla.org/en-US/docs/Web/CSS/minmax
But in a repeat documentation on csswg, it states one simple rule that disallows all of this from happening;
https://drafts.csswg.org/css-grid/#funcdef-repeat
The generic form of the repeat() syntax is, approximately,
repeat( [ <positive-integer> | auto-fill | auto-fit ] , <track-list> )
The first argument specifies the number of repetitions. The second
argument is a track list, which is repeated that number of times.
However, there are some restrictions:
The repeat() notation can’t be nested.
Automatic repetitions (auto-fill or auto-fit) cannot be combined with
intrinsic or flexible sizes.
Whats an intrinsic or flexible sizes ?
An intrinsic sizing function (min-content, max-content, auto, fit-content()).
So the command wont work in grid because each column/row will be different sizes and wrapping cannot take place. See bellow picture as example.
This behavior should be executed using flex-box instead.
#kivylius has a great answer to why max-content, min-content or other intrinsic sizing wouldn't work with auto-fit. He also suggested using flexbox to achieve what you are after. So, I am just extending on his answer leaving the flexbox way of doing it.
.flex-auto-wrap {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 10px;
}
.flex-auto-wrap > * {
flex: 1; /* this is to make elements/columns in each row have equal width and fill up all available space in the row, similar to auto-fit in grid */
/* flex-grow: 1; this can be used to make elements/columns in each row maintain their individual width but stretch to fill up all available space in the row */
}
div {
color: #ddd;
background-color: #222;
padding: 5px;
text-align: center;
}
<section class="flex-auto-wrap">
<div>1</div>
<div>11</div>
<div>111</div>
<div>1111</div>
<div>11111</div>
</section>
PS: I used to think grid came as an alternative to flexbox so I would try to do everything using the newer grid technology. But as it turned out, even though you can do most things with grid, it still doesn't replace flexbox. In fact, flexbox is one dimensional whereas grid is two dimensional. So, they were meant to do things differently. That's why there are some things like this one that can only be done with flexbox but not grid and there are many things that can only be done with grid but not flexbox.

How to make adaptive responsive flexible header with CSS Grid and/or/without Flexbox?

Ok, so my header module has a logo, a phone component and a basket component. Here's that header:
And here's what I'm trying to do:
Adaptive responsive header, so it look nicely on 400-1920px viewport widths.
To be able to reorder all header components in any order.
When viewport width is bigger than 640px I want logo be on the left side, phone and basket modules to be on the right side, as on the image above.
When viewport width is 480-640px I want phone and basket components both to be on the second row and I want distances from their edges to outer borders to be the same, like this:
When viewport width is smaller than 480px by default I want logo be on the first row, phone on the second row and basket on the third row, like this:
But I want to be able to swap logo and phone component without editing html, to make it to look like this:
I can't make it so it meets all the requirements. Here's the best I came up with so far:
https://next.plnkr.co/edit/9nHCyGk2KC8ZO9JA?preview
I can swap phone and basket, but not phone and logo and not basket and logo. If I remove <div class="pg-Header_phoneBasketContainer"> element from html and apply grid-area: phone and grid-area: basket directly on phone and basket accordingly, then I can't make same distances from phone and basket edges to outer borders be the same, because there will be 2 cells in a second row and it will position grid items relatively to their cells, and these items aren't the same length, so distance to the borders will be different.
If I could use grid-positioning on indirect children of a grid container, then it would be easily done with grid areas. I would just define appropriate grid-template-areas for a container and appropriate grid-area properties for .pg-Header_phoneBasketContainer, .pg-Header_phone and .hdr-Basket in different viewports:
.hdr-Top {
display: grid;
grid-template-areas: "logo phoneBasket";
}
.pg-Header_logo {
grid-area: logo;
}
.pg-Header_phoneBasketContainer {
grid-area: phoneBasket;
}
#media only screen and(max-width: 480px) {
.hdr-Top {
display: grid;
grid-template-areas: "phone" "logo" "basket";
}
.pg-Header_phone {
grid-area: phone;
}
.hdr-Basket {
grid-area: basket;
}
}
It would result in this on bigger screens:
And this on smaller:
Unfortunately, CSS Grid doesn't allow grid positioning of indirect children. Any ideas how I can achieve what I'm trying to achieve here?
You cannot divide phoneBasket and put logo in the middle for small screens - but you can achieve what you want by having 1 container with 3 children.
With this you can reorder grid-items using grid-template-areas property:
.header {
padding: .25rem;
border-bottom: 1px solid #333;
display: grid;
grid-template-columns: 1fr auto auto;
grid-template-areas: "logo phone basket";
align-items: center;
grid-gap: .5rem;
}
.header-item {
padding: .5rem;
background-color: #ccc;
}
.logo {
font-weight: 700;
text-transform: uppercase;
grid-area: logo;
}
.phone {
grid-area: phone;
}
.basket {
border: 2px solid #999;
grid-area: basket;
}
#media (max-width: 640px) {
.header {
grid-template-areas: "logo logo" "phone basket";
grid-template-columns: auto;
justify-content: center;
text-align: center;
}
}
#media (max-width: 480px) {
.header {
grid-template-areas: "phone" "logo" "basket";
}
}
<div class="header">
<div class="header-item logo">MyCompany</div>
<div class="header-item phone">555-3535</div>
<div class="header-item basket">3 items</div>
</div>

Resources