Flex - changing order of nested elements? - html

On small screens i'm trying to get my .image div to slot in between .title and .text divs, something like:
title
image
text
.title and .text are wrapped in a container, this is so on a different screen size (medium up) I can do:
title | image
text | continuation of image element
I've thought about using a flex column layout for small screens, and changing the order of elements, but order doesn't seem to have an effect on a nested child element.
Here's the code for small:
HTML:
<div class="container">
<div class="content">
<div class="title"></div>
<div class="text"></div>
</div>
<div class="image"></div>
</div>
CSS:
.container {
display: flex;
flex-direction: column;
}
.image {
order: 2;
}
.title {
order: 1;
}
.text {
order: 3;
}
For reference my code for medium is (cascading upwards from small):
.container {
flex-direction: row;
}
.content {
flex-basis: 50%;
}
.image {
flex-basis: 50%;
}

You can ordering only sibling elements. Title and text aren't sibling with image. You need wrap title, text and image in general parent block

I have similar case, but for solution CSS grid is used, however there is also another support is recommended: CSS subgrid
For a simple design like even columns or rows it is not that hard to replicate, but if parent container contain more complex grid sizes, then CSS subgrid will be requirement for an only CSS solution.
Here is full demo: https://codepen.io/XCanG/pen/vYaNZPo?editors=1100
HTML I have in the demo have very similar structure with having parent, some container for 2 nested elements and 3rd element.
This is quote from my demo, except I rename class names according to your example:
.container {
display: grid;
grid-template-rows: repeat(3, 1fr);
}
.container .content {
display: grid;
grid-column: 1;
grid-row: 1 / span 3;
grid-template-rows: repeat(3, 1fr); /* Fallback */
grid-template-rows: subgrid; /* Limited support: https://caniuse.com/css-subgrid */
grid-template-columns: subgrid;
}
.container .title {
grid-colimn: 1;
grid-row: 1;
}
.container .text {
grid-column: 1;
grid-row: 3;
}
.container .image {
grid-column: 1;
grid-row: 2;
}
CSS make that nested element follow grid columns and rows, with subgrid support you will have exactly defined part of the grid (otherwise you will need to define fallback). And with grid-column and grid-row it is possible to sort elements.

Grid-template-areas will come in pretty handy in this type of layout as they are not siblings. And grid-template-areas have full browser support.
.container {
display: grid;
grid-template-areas: "title img"
"txt img";
}
.title{grid-area: title;}
.text{grid-area: txt;}
.image{grid-area: img;}
#media(max-width: 800px){
.container{
grid-template-areas: 'title'
'img'
'txt';
}
}
<div class="container">
<div class="content">
<div class="title">title</div>
<div class="text">text</div>
</div>
<div class="image">img</div>
</div>

Related

How can I shrink the height of a flex child to 50% only?

I am trying to have a UI like this :
This is how I have tried doing it :
A div at the top as cover Image
The next div containing two child divs as profile photo & UI details as -
<div>Image Cover photos<div>
<div style="display : flex;">
<div>profile photo</div>
<div>UI Details Card</div>
</div>
Then I'm using the transform : translateY(-50%) to the profile photo div to move 50 percent of the portion on top of the background cover photo.
This, however creates a problem, the UI details remains at the same place(which is ideal), but the baseline has been changed.I want it to have only 50% of the height, so the baseline matches with the profile photo as well, and also UI details wcard will have some text, I do not want it to overlap on the Cover Image background as well(as that of profile photo). How can I achieve this?
One way to solve this problem is to use CSS grid to place everything.
body {
padding: 100px;
}
.card {
height: 300px;
width: 300px;
display: grid;
grid-template-columns: 1fr 2fr;
grid-template-rows: repeat(3, 1fr);
outline: 1px solid;
}
.details {
background-color: blanchedalmond;
grid-area: 3/2/4/3;
}
.photo {
background-color: aquamarine;
grid-area: 2/1/4/2;
}
.cover {
grid-area: 1/1/3/3;
background-color: grey;
}
.cover img,
.photo img {
height: 100%;
width: 100%;
}
<div class="card">
<div class="cover">
<img src="https://source.unsplash.com/random/?1" alt="">
</div>
<div class="photo">
<img src="https://source.unsplash.com/random/" alt="">
</div>
<div class="details">Details here</div>
</div>
Here's a diagram showing the different grid areas :
Notice the overlap region between the blue box (photo) and the red box (cover). Since the photo div appears after the cover div in the DOM, the photo div will have higher priority and will occupy this overlap region.
You can make a 2 column, 3 row grid and place items where you want them.
Here's a simple example:
.container {
display: grid;
grid-template-columns: 1fr 4fr;
grid-template-rows: 1fr 1fr 1fr;
width: 30vw;
height: 20vw;
}
.container :nth-child(1) {
grid-column: 1 / span 2;
grid-row: 1 / span 2;
background: red;
}
.container :nth-child(2) {
grid-column: 1;
grid-row: 2 / span 2;
background: green;
}
.container :nth-child(3) {
background: blue;
}
<div class="container">
<div></div>
<div></div>
<div></div>
</div>
Obviously you will want to alter the relative sizes of the grid to suit your particular case.
Note: it depends on whether you want to put some text in the first item so that it comes directly above the second item or not as to whether you start the first div in the first column or the second column.

How can I change the layout and order of children with flexbox? [duplicate]

This question already has answers here:
Left column and stacked right column using flexbox CSS [duplicate]
(2 answers)
Closed last year.
I have been trying many different methods to achieve the following but could not.
I tried using media query to change the order of the children within the flex box of the div that contains all 3 but it seems like I can only do so when all 3 children are divs. However when all 3 children are divs, I cannot achieve the desktop outcome. Basically the header and the paragraph text will each take a column.
Any help would be greatly appreciated.
Exiting code: https://codepen.io/lionellloh/pen/BawVpjm
<div class="parent">
<img src="https://upload.wikimedia.org/wikipedia/commons/2/27/Red_square.svg">
<h1> This is a header </h1>
<p> This is some body text that is very interesting </p>
</div>
.parent {
display: flex;
flex-wrap: wrap;
}
#media only screen and (max-width: 900px) {
.parent :nth-child(1) {
order: 2;
}
.parent :nth-child(2) {
order: 1;
}
.parent :nth-child(3) {
order: 3;
}
}
You may like to look at grid which will allow you to name areas and assign elements to them.
Then you can redefine the areas when the max-width is at some value.
This saves having to go through all the affected elements and specify their order.
Here's a simple snippet. It puts a background color on each element so you can see which is being allocated to where.
Obviously you'll want to look at exactly the proportions you want the header to take up compared to the paragraph and so on but this is to get you started.
.container {
display: grid;
min-width: 600px;
width: 25%;
aspect-ratio: 1 / 1;
gap: 10px;
grid-template-areas:
'I H'
'I P'
'I P'
'I P';
background: pink;
grid-template-columns: 1fr 1fr;
}
h1 {
grid-area: H;
background: cyan;
}
img {
grid-area: I;
background: magenta;
object-fit: cover;
width: 100%;
height: 100%;
}
p {
grid-area: P;
background: yellow;
}
#media (max-width: 600px) {
.container {
grid-template-areas:
'H'
'I'
'P';
}
}
<div class="container">
<h1>Heading</h1>
<img src="https://picsum.photos/id/1015/200/600">
<p>Paragraph text<br>Paragraph text<br>Paragraph text<br>Paragraph text<br>Paragraph text<br></p>
</div>
The CSS property you're looking for is (quite aptly) named order.
The order property controls the order in which flex items appear
within the flex container, by assigning them to ordinal groups. It
takes a single value, which specifies which ordinal group
the flex item belongs to.
https://www.w3.org/TR/css-flexbox-1/#order-property
So in your example, you would explicitly set the order on all 3 elements, and then within a media query override the order for one/some of the elements to adapt to the "other" layout.
Update: I see you've updated the question but didn't really have any content at all yet. In that case i'd advise going with grid instead of flex. Which is very much alike but has a few features that make it better suited for what you describe.
You could go with something like this:
.parent {
display: grid;
grid: 'heading'
'imagearea'
'content';
}
img { grid-area: imagearea; }
h1 { grid-area: heading; }
p { grid-area: content; }
#media only screen and (max-width: 900px) {
.parent {
grid-template-areas:
'imagearea heading'
'imagearea content';
}
}
/* unrelated CSS; just to show borders */
.parent > * { margin: 0; border: 2px solid black; background-color: #DEFFDE; }
<div class="parent">
<h1>This is a header</h1>
<p>This is some body text that is very interesting</p>
<img src="https://upload.wikimedia.org/wikipedia/commons/2/27/Red_square.svg">
</div>
Thank you for making suggestions that fit my skills.
<div class="parent">
<img src="https://upload.wikimedia.org/wikipedia/commons/2/27/Red_square.svg">
<div class="content">
<h1> This is a header </h1>
<p> This is some body text that is very interesting </p>
</div>
</div>
<style>
.parent {
display:flex;
flex-direction:row;
justify-content: space-between;
}
.content {
display:flex;
flex-direction:colume;
justify-content: space-between;
}
#media screen and (min-width:512px) {
.parent {
display:flex;
flex-direction:colume;
justify-content: space-between;
}
}
</style>
Thanks

Set column to different span dynamically using CSS grid [duplicate]

I know there are similar questions but this is specifically asking how to do this using CSS Grid Layout.
So we have this basic grid setup:
HTML (with sidebar):
<div class="grid">
<div class="content">
<p>content</p>
</div>
<div class="sidebar">
<p>sidebar</p>
</div>
</div>
CSS:
.grid {
display: grid;
grid-template-columns: 1fr 200px;
}
To create a layout that looks something like this:
| content | sidebar |
If the page doesn't have a sidebar though, ie. the html looks like this but with the same CSS:
HTML (no sidebar):
<div class="grid">
<div class="content">
<p>content</p>
</div>
</div>
The page layout looks like this (dashes represent empty space)
| content | ------- |
I know why it does that, the grid column is still defined in the grid-template-columns rule.
I'm just wondering how to tell the grid that if there is no content, then fill the remaining space similar to how flex-grow works for flexbox.
The desired result would look like this if no sidebar is present.
| content |
Don't define the columns explicitly with grid-template-columns.
Make the columns implicit instead and then use grid-auto-columns to define their widths.
This will allow the first column (.content) to consume all space in the row when the second column (.sidebar) doesn't exist.
.grid {
display: grid;
grid-auto-columns: 1fr 200px;
}
.content {
grid-column: 1;
}
.sidebar {
grid-column: 2;
}
.grid > * {
border: 1px dashed red; /* demo only */
}
<p>With side bar:</p>
<div class="grid">
<div class="content">
<p>content</p>
</div>
<div class="sidebar">
<p>sidebar</p>
</div>
</div>
<p>No side bar:</p>
<div class="grid">
<div class="content">
<p>content</p>
</div>
</div>
You can get closer by using content sizing keywords, something like:
.grid {
display: grid;
grid-template-columns: 1fr fit-content(200px);
}
.sidebar {
width: 100%;
}
The fit-content keyword will look at the size of the content and act like max-content until it gets to the value you pass in.
In reality you probably wouldn't need to stick a size on sidebar as the content is likely to dictate a size of at least 200 pixels (for example) but you can play around with this.
I think I know the definitive answer to this question now. The problem with the answers so far is that they don't explain how to handle a sidebar that is on the left side of the main content (mainly because I didn't ask for it in the original question).
<div class="grid">
<nav>
<p>navigation</p>
</nav>
<main>
<p>content</p>
</main>
<aside>
<p>sidebar</p>
</aside>
</div>
You can use this CSS:
.grid {
display: grid;
grid-template-columns: fit-content(200px) 1fr fit-content(200px);
}
nav, aside {
width: 100%;
}
/* ensures that the content will always be placed in the correct column */
nav { grid-column: 1; }
main { grid-column: 2; }
aside { grid-column: 3; }
This is also a good use case for grid-areas
.grid {
display: grid;
grid-template-columns: fit-content(200px) 1fr fit-content(200px);
grid-template-areas: "nav content sidebar";
}
nav, aside {
width: 100%;
}
/* ensures that the content will always be placed in the correct column */
nav { grid-area: nav; }
main { grid-area: content; }
aside { grid-area: sidebar; }
An IE compatible version would look like this:
.grid {
display: -ms-grid;
display: grid;
-ms-grid-columns: auto 1fr auto;
grid-template-columns: auto 1fr auto;
}
nav, aside {
width: 100%; /* Ensures that if the content exists, it takes up max-width */
max-width: 200px; /* Prevents the content exceeding 200px in width */
}
/* ensures that the content will always be placed in the correct column */
nav {
-ms-grid-column: 1;
grid-column: 1;
}
main {
-ms-grid-column: 2;
grid-column: 2;
}
aside {
-ms-grid-column: 3;
grid-column: 3;
}

Centering an image in CSS grid

I am trying to fix my grid layout.
Firstly, the image should be at the center of .container. I tried using align-self: center;, but that did not work.
The headers and paragraph are really messed up. Before this, the paragraph was pushing down the image so I thought if I gave both of them (and the headers) custom grid-row value, they'd be fixed, but instead, I have all of these elements overlap each other. I need them to be ordered correctly. The paragraph under H3 and H3 below H1.
.container {
display: grid;
grid-template-columns: auto auto auto;
grid-template-rows: 100%;
}
.container img {
grid-column: 1 / 2;
grid-row: 1;
}
.container h1,
h3 {
grid-column: 3;
grid-row: 1;
line-height: 0.35;
}
.container p {
grid-column: 3;
grid-row: 1;
width: 350px;
}
<div class="container">
<h1>-[IFwsI]- Jail</h1>
<h3>More than 40 000 registered players</h3>
<p>The most active, and one of the most successful servers. Jail has a set of rules players need to follow and enjoy the roleplay of inmates vs. CTs scenario</p>
<img src="https://i.imgur.com/epqMIJv.jpg" height="418" width="740" />
</div>
Create columns for the text and image to make it easier to manage the columns.
Take a look at grid-template-columns you can control the widths of each column in myriad ways. I have just set them to 33% width;
https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-columns
justify-content: center puts the columns in the horizontal center.
align-items: center aligns the items vertically.
To change the order I've created a new class .column--left with grid-row: 1 to put move it to the first column.
.container {
display: grid;
grid-template-columns: 33% 33%;
justify-content: center;
align-items: center;
grid-gap: 10px;
height: 100vh;
}
.column.column--left {
grid-row: 1;
}
img {
max-width: 100%;
height: auto;
}
<div class="container">
<div class="column">
<h1>-[IFwsI]- Jail</h1>
<h3>More than 40 000 registered players</h3>
<p>The most active, and one of the most successful servers. Jail has a set of rules players need to follow and enjoy the roleplay of inmates vs. CTs scenario</p>
</div>
<div class="column column--left">
<img src="https://i.imgur.com/epqMIJv.jpg" height="418" width="740" />
</div>
</div>
...but instead, I have all of these elements overlap each other.
In your code, you are specifically telling them to overlap each other.
.container h1, h3 {
grid-column: 3;
grid-row: 1;
line-height: 0.35;
}
.container p {
grid-column: 3;
grid-row: 1;
width: 350px;
}
The h1, h3 and p are all placed in column 3, row 1. Why wouldn't they overlap?
Here's another approach that may be useful to you:
.container {
display: grid;
height: 100vh;
align-items: center;
grid-column-gap: 20px;
grid-template-columns: 1fr 2fr 2fr 1fr;
grid-template-areas: " . pic header1 . "
" . pic header3 . "
" . pic ptext . "
" . pic ptext . "
}
.container > h1 { grid-area: header1; }
.container > h3 { grid-area: header3; }
.container > p { grid-area: ptext; }
.container > div { grid-area: pic; }
.container img { width: 100%; object-fit: contain; }
* { margin: 0; }
<div class="container">
<h1>-[IFwsI]- Jail</h1>
<h3>More than 40 000 registered players</h3>
<p>The most active, and one of the most successful servers. Jail has a set of rules players need to follow and enjoy the roleplay of inmates vs. CTs scenario</p>
<div>
<img src="https://i.imgur.com/epqMIJv.jpg" height="418" width="740" />
</div>
</div>
jsFiddle demo
Here's a visualization of the code using Firefox's grid overlay tool.

Reverse order of columns in CSS Grid Layout

I was hoping to use CSS Grid to reverse the apparent order of two side-by-side divs, where one of the divs grows arbitrarily (I don't want to use floats).
I've created a plunkr here: http://plnkr.co/edit/6WZBnHbwhD7Sjx2ovCO7?p=preview
#container {
grid-template-columns: 240px 1fr;
display: grid;
}
.a {
background: yellow;
}
.b {
background: blue;
color: white;
}
#container>.a {
grid-column: 1;
}
#container>.b {
grid-column: 2;
}
#container.reverse>.a {
grid-column: 2;
}
#container.reverse>.b {
grid-column: 1;
}
<div id="container" class="reverse" style="width: 800px;">
<div class="a">A</div>
<div class="b">B</div>
</div>
The crux of it is that when I have the .reverse class applied (so that you should see B | A), B is offset to a new line so it looks more like:
| A
B
If I invert the document ordering of .a with .b, this goes back to normal (but of course, if I drop the .reverse class, I get the same problem).
Why is this, and how can I address?
As the Grid auto-placement algorithm lays out items in the container, it uses next available empty cells (source).
In your source code the A element comes before the B element:
<div id="container" class="reverse" style="width: 800px;">
<div class="a">A</div>
<div class="b">B</div>
</div>
Therefore, the grid container first places A, then uses the next available space to place B.
By default, the auto-placement algorithm looks linearly through the grid without backtracking; if it has to skip some empty spaces to place a larger item, it will not return to fill those spaces. To change this behavior, specify the dense keyword in grid-auto-flow.
http://www.w3.org/TR/css3-grid-layout/#common-uses-auto-placement
grid-auto-flow: dense
One solution to this problem (as you have noted) is to override the default grid-auto-flow: row with grid-auto-flow: dense.
With grid-auto-flow: dense, the Grid auto-placement algorithm will look to back-fill unoccupied cells with items that fit.
#container {
display: grid;
grid-template-columns: 240px 1fr;
grid-auto-flow: dense; /* NEW */
}
7.7. Automatic Placement: the grid-auto-flow
property
Grid items that aren’t explicitly placed are automatically placed into
an unoccupied space in the grid container by the auto-placement
algorithm.
grid-auto-flow controls how the auto-placement algorithm works,
specifying exactly how auto-placed items get flowed into the grid.
dense
If specified, the auto-placement algorithm uses a “dense” packing
algorithm, which attempts to fill in holes earlier in the grid if
smaller items come up later. This may cause items to appear
out-of-order, when doing so would fill in holes left by larger items.
#container {
display: grid;
grid-template-columns: 240px 1fr;
grid-auto-flow: dense; /* NEW */
}
.a {
background: yellow;
}
.b {
background: blue;
color: white;
}
#container>.a {
grid-column: 1;
}
#container>.b {
grid-column: 2;
}
#container.reverse>.a {
grid-column: 2;
}
#container.reverse>.b {
grid-row: 1;
grid-column: 1;
}
<div id="container" class="reverse" style="width: 800px;">
<div class="a">A</div>
<div class="b">B</div>
</div>
grid-row: 1
Another solution would be to simply define the row for the second item.
#container>.b {
grid-column: 2;
grid-row: 1; /* NEW */
}
#container {
display: grid;
grid-template-columns: 240px 1fr;
}
.a {
background: yellow;
}
.b {
background: blue;
color: white;
}
#container>.a {
grid-column: 1;
}
#container>.b {
grid-column: 2;
grid-row: 1; /* NEW */
}
#container.reverse>.a {
grid-column: 2;
}
#container.reverse>.b {
grid-row: 1;
grid-column: 1;
}
<div id="container" class="reverse" style="width: 800px;">
<div class="a">A</div>
<div class="b">B</div>
</div>
The simplest way is to add order: 1 to element B or order: -1 to element A in .reverse
It's also correct CSS rather than hack-y
I'm not sure how to reverse more grid items. But if you have 2 grid items in your grid, you can simply position 2nd grid item using below code.
#container > .b {
grid-column-start: 1;
grid-row-start: 1;
}
I had this same issue just now. I tried auto-row-dense and then set the direction of the container parent to rtl. It worked.
Just this, on the plunker link, seemed to do the trick.
.reverse{
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-auto-flow: dense;
direction: rtl;
}
You can use direction property to reverse a grid x-axis order.
Nested elements will be reversed too so you have to make sure to add additional styles to fix this behavior.
<div class="grid">
<div class="grid-item"><div>
</div>
<style>
.grid { direction : rtl; }
.grid-item { direction : ltr; }
</style>
Edit: this may work but could cause accessibilty issues.
Round peg in square hole
Remember even if you're using fancy 'new' grid features the older flex layout will still work. You can combine them, nest them and sometime you have to admit that certain problems like this may just be better solved with good old
flex-direction: row-reverse
But I know some people will want to downvote me for that so here's another way with grid.
Use named template regions
You can use named template regions and reverse them in the definition.
#container
{
grid-template-areas: a b;
grid-template-rows: 240px 1fr;
display: grid;
}
#container.reverse
{
// note the order is flipped for both these properties
grid-template-areas: b a;
grid-template-rows: 1fr 240px;
}
.a {
grid-area: a;
background: yellow;
}
.b {
grid-area: b;
background: blue;
color: white;
}
Here's an more complex example that uses that technique with media queries
I found out: I need to apply grid-auto-flow: dense; on the container:
#container {
grid-template-columns: 240px 1fr;
display: grid;
grid-auto-flow: dense;
}
According to MDN, this algorithm attempts to fill in holes earlier in the grid.
I want to mention a solution which is also relevant to this question in some cases. When having a multi-row layout, and you want a reversed look of how you grid fills up.
You can play with grid-start combined with some :nth-child & :last-child selectors to achieve a reverse auto flow.
Reversed grid-auto-flow: column
.container{
display: grid;
width: 10rem;
gap: 0.5rem;
grid-template-rows: repeat(2, 1fr);
grid-auto-flow: column; /* => vertical grid*/
}
/* REMOVE THIS TO SEE THE DIFFERENCE */
.pixel:nth-child(odd):last-child { /* reversed auto-flow: column */
grid-row-start: 2;
}
.pixel{
width: 2rem;
height: 2rem;
background: red;
border: 1px solid black
}
<div class="container">
<!-- ADD/REMOVE SOME PIXELS to see the result -->
<div class="pixel"></div>
<div class="pixel"></div>
<div class="pixel"></div>
<div class="pixel"></div>
<div class="pixel"></div>
<div class="pixel"></div>
<div class="pixel"></div>
</div>
Reversed: horizontal & vertical
.container{
display: grid;
width: 10rem;
gap: 0.5rem;
grid-template-rows: repeat(2, 1fr);
grid-auto-flow: column;
direction: rtl; /* reversed horizontal */
}
/* REMOVE THIS TO SEE THE DIFFERENCE */
.pixel:nth-child(odd):last-child { /* reversed vertical */
grid-row-start: 2;
}
.pixel{
width: 2rem;
height: 2rem;
background: red;
border: 1px solid black
}
<div class="container">
<!-- ADD/REMOVE SOME PIXELS to see the result -->
<div class="pixel">1</div>
<div class="pixel">2</div>
<div class="pixel">3</div>
<div class="pixel">4</div>
<div class="pixel">5</div>
<div class="pixel">6</div>
<div class="pixel">7</div>
</div>
I found out: I need to apply grid-auto-flow: dense; on the container: