Changing grid-template-areas without media query? - css

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.

Related

Can I set css grid row height based on if anything targets it with grid-area?

Is it possible to set a css grid row height based on if the grid area is being used?
.container {
height: 100vh;
grid-template-areas:
"mobile-head mobile-head mobile-head"
"nav app app"
"footer footer footer";
}
.nav {
grid-area: nav
}
<div class="container">
<div class="nav">Nav stuff</div>
</div>
Given the above grid template and html, I see the Nav stuff in the nav area, but naturally the mobile nav area is taking a 1/3rd of the screen height. Because there is no div that has a grid-area: mobile-head I want the height to be 0px.
But if I change the .nav to
...
.nav {
grid-area: mobile-head
}
I would want the row to be a fixed height of say 200px.
I can get close. I can get the row to have a height of 0 when there is nothing in the mobile-area by doing this:
...
grid-template-rows: minmax(0px, auto) 1fr 1fr
But then the height is dictated by the content, rather than the grid.
I'm fairly sure the only alternative to achieve what im trying to do is to use 2 grids, and media queries. (I want to place the nav on the left for desktop, or top if mobile)
I think grid-auto-rows: min-content is what you're after.
<button class="toggle" style="margin-bottom:1em">Toggle hidden row</button>
<div class="container">
<div class="nav">Nav stuff</div>
</div>
<style>
.container {
box-shadow: inset 0 0 0 1px lightgray;
background-color: #fafafb;
display: grid;
height: 100vh;
grid-auto-rows: min-content;
grid-template-areas:
"mobile-head mobile-head mobile-head"
"nav app app"
"footer footer footer";
}
.nav {
grid-area: nav;
background-color: lightblue;
}
.nav.mobile {
grid-area: mobile-head;
}
</style>
<script>
document.querySelector('.toggle').onclick = function(){
document.querySelector('.nav').classList.toggle('mobile');
};
</script>

Grid layout with search bar in header

I am trying to come up with a solution for advanced layout. I decided to use a css grid as it seemed to be the best match for my needs.
The requirements:
Header - consists of three elements - logo, search bar and menu
search bar should be aligned with the main content if space allows, i.e. on small screen search bar doesn't need to start where the main content starts but it should end where main content ends or take all available space
Main - consists of content and sidebar- should be centred and take max 100rem width space
content should be 2 times bigger than the sidebar
main content should have at least 1rem space from left and right
This is how it looks now. It matches my requirements on big screens (4k) but when the screen gets smaller it gets messy. I would really like to avoid any javascript and solve this with pure CSS if possible.
How would you approach this problem? I am now more inclining that this is not solvable with pure CSS and JS is needed. (Probably some resize observer on main-content element)
Examples:
Big screen -> alignment is correct ✔️
Small screen -> alignment is not correct. ❌ The search box should be within the black "brackets"
Even smaller screen -> alignment is not correct too. ❌ The search box should expand up to the black line
html,
body {
padding: 0;
margin: 0;
}
body {
font-family: sans-serif;
display: grid;
grid-template-columns: minmax(1rem, 1fr) minmax(min-content, 100rem) minmax(
1rem,
1fr
);
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header header"
". main .";
min-height: 100vh;
}
.header {
padding: 1rem 1rem;
background: green;
grid-area: header;
display: grid;
grid-template-columns: minmax(max-content, 1fr) minmax(min-content, 100rem) minmax(
max-content,
1fr
);
}
.search-box {
background: yellow;
max-width: calc(2/3 * 100%);
}
.logo {
background: yellowgreen;
min-width: 5rem;
}
.menu {
background: brown;
}
.main {
grid-area: main;
display: flex;
}
.main-content {
background: red;
flex: 2;
}
.sidebar {
background: blue;
flex: 1;
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<header class="header">
<div class="logo">Logo</div>
<div class="search-box">
Search box should should match main content position
</div>
<div class="menu">Menu with two submenus at least</div>
</header>
<main class="main">
<section class="main-content">Main content</section>
<aside class="sidebar">Sidebar</aside>
</main>
</body>
</html>
Sandbox available here

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

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;
}
}

CSS Grid min-content cell expanding based on neighbour

Ive got a CSS grid that's two columns, five rows (at a display above 768px).
All the rows are set to "min-content" bar the last, being set to auto.
I've defined template grid areas, one for each "cell", with the exception of one that covers the 3rd to 5th row on the second column - named a6 (in the sample code)
When there is little or no content in a6, the grid behaves exactly as I desire. However, if when a bit more content is added to a6, the a5 and a7 areas expand in height, despite their content not changing.
CSS:
html,
body {
height: 100vh;
padding: 0;
margin: 0;
}
.maingrid {
height: 100%;
padding-left: 15px;
padding-right: 15px;
background-color: red;
display: grid;
grid-template-rows: min-content min-content min-content min-content min-content min-content min-content auto;
grid-template-areas: 'a1' 'a2' 'a3' 'a4' 'a5' 'a6' 'a7' 'a8';
grid-row-gap: .2em;
}
#media only screen and (min-width:768px) {
.maingrid {
grid-template-columns: 9fr 3fr;
grid-template-rows: min-content min-content min-content min-content auto;
grid-template-areas: 'a1 a2' 'a3 a4' 'a5 a6' 'a7 a6' 'a8 a6';
background-color: darkcyan;
}
}
.maingrid div {
background-color: black;
}
.a1 {
grid-area: a1;
background-color: pink !important;
}
.a2 {
grid-area: a2;
background-color: aliceblue !important;
}
.a3 {
grid-area: a3;
background-color: aqua !important;
}
.a5 {
grid-area: a4;
background-color: blue !important;
}
.a4 {
grid-area: a5;
background-color: brown !important;
}
.a6 {
grid-area: a6;
background-color: burlywood !important;
}
.a7 {
grid-area: a7;
background-color: chartreuse !important;
}
.a8 {
grid-area: a8;
background-color: darkorange !important;
}
HTML:
<main class="maingrid">
<div class="a1">BLAH</div>
<div class="a2">BLAH</div>
<div class="a3">BLAH</div>
<div class="a4">BLAH</div>
<div class="a5">BLAH</div>
<div class="a6">
at<br />at<br />
</div>
<div class="a7">BLAH</div>
<div class="a8">
<button type="button" onclick="BreakTheGrid();">click me :(</button>
</div>
</main>
JS (just to get the toggle button to work):
var isBroken = false;
function BreakTheGrid() {
if (!isBroken) {
$('.a6').html("the<br/>left<br />columns<br />have<br />expanded<br />boo!<br />");
} else {
$('.a6').html("no<br/>issue");
}
isBroken = isBroken == false;
}
Here's a JSFiddle replicating the issue: https://jsfiddle.net/up6afdj4/
If you click the button in a8, you can toggle the content of a6, thus toggling the issue.
I've only just started messing around with CSS grid, so I expect its something I've got completely wrong, but I can't figure it :)
By applying auto to the fifth row, which includes the a6 grid area, you trigger the Grid auto stretch algorithm, which distributes free space among rows covered by the grid area (spec §11.5, §11.5.1 and §11.8).
If you switch from auto to 1fr, the last row then consumes all free space, pinning the rows above to the top.
revised demo
For a more detailed explanation of auto space distribution, see my answers here:
Remove wide gaps in CSS Grid
How do you collapse unused row in a CSS grid?
(Illustrations generated by Firefox DevTools Grid Inspector.)
Changing auto to 1fr in the grid-template-rows definition solves the problem.
I don't know why however, and if someone could explain it better, I'll give you the accepted answer :)

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