Responsive Layouts, Fewer Media Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Let’s first take a look at some widely used methods to build responsive layouts without media queries. No surprises here — these methods are related to flexbox and grid.
flex
and flex-wrap
In the above demo, flex: 400px
sets a base width for each element in the grid that is equal to 400px
. Each element wraps to a new line if there isn’t enough room on the currently line to hold it. Meanwhile, the elements on each line grow/stretch to fill any remaining space in the container that’s leftover if the line cannot fit another 400px
element, and they shrink back down as far as 400px
if another 400px
element can indeed squeeze in there.
Let’s also remember that flex: 400px
is a shorthand equivalent to flex: 1 1 400px
(flex-grow: 1
, flex-shrink: 1
, flex-basis: 400px
).
What we have so far:
auto-fit
and minmax
Similar to the previous method, we are setting a base width—thanks to repeat(auto-fit, minmax(400px, 1fr))
—and our items wrap if there’s enough space for them. This time, though we’re reaching for CSS Grid. That means the elements on each line also grow to fill any remaining space, but unlike the flexbox configuration, the last row maintains the same width as the rest of the elements.
So, we improved one of requirements and solved another, but also introduced a new issue since our items cannot shrink below 400px
which may lead to some overflow.
Both of the techniques we just looked at are good, but we also now see they come with a few drawbacks. But we can overcome those with some CSS trickery.
Let’s take our first example and change flex: 400px
to flex: max(400px, (100% - 20px)/3)
.
Resize the screen and notice that each row never has more than three items, even on a super wide screen. We have limited each line to a maximum of three elements, meaning each line only contains between one and three items at any given time.
Let’s dissect the code:
100%/3
gets bigger than 400px
at some point.max()
function as the width and are dividing 100%
by 3
in it, the largest any single element can be is just one-third of the overall container width. So, we get a maximum of three elements per row.400px
takes the lead and we get our initial behavior.You might also be asking: What the heck is that 20px
value in the formula?
It’s twice the grid template’s gap
value, which is 10px
times two. When we have three items on a row, there are two gaps between elements (one on each on the left and right sides of the middle element), so for N
items we should use max(400px, (100% - (N - 1) * gap)/N)
. Yes, we need to account for the gap
when defining the width, but don’t worry, we can still optimize the formula to remove it!
We can use max(400px, 100%/(N + 1) + 0.1%)
. The logic is: we tell the browser that each item has a width equal to 100%/(N + 1)
so N + 1
items per row, but we add a tiny percentage ( 0.1%
)—thus one of the items wraps and we end with only N
items per row. Clever, right? No more worrying about the gap!
Now we can control the maximum number of items per row which give us a partial control over the number of items per row.
The same can also be applied to the CSS Grid method as well:
Note that here I have introduced custom properties to control the different values.
We’re getting closer!
We noted earlier that using the grid method could lead to overflow if the base width is bigger than the container width. To overcome this we change:
max(400px, 100%/(N + 1) + 0.1%)
…to:
clamp(100%/(N + 1) + 0.1%, 400px, 100%)
Breaking this down:
400px
is clamped to 100%/(N + 1) + 0.1%
, maintaining our control of the maximum number of items per row.400px
is clamped to 100%
so our items never exceed the container width.We’re getting even closer!
So far, we’ve had no control over when elements wrap from one line to another. We don’t really know when it happens because it depends a number of things, like the base width, the gap, the container width, etc. To control this, we are going to change our last clamp()
formula, from this:
clamp(100%/(N + 1) + 0.1%, 400px, 100%)
…to:
clamp(100%/(N + 1) + 0.1%, (400px - 100vw)*1000, 100%)
I can hear you screaming about that crazy-looking math, but bear with me. It’s easier than you might think. Here’s what’s happening:
100vw
) is greater than 400px
, (400px - 100vw)
results in a negative value, and it gets clamped down to 100%/(N + 1) + 0.1%
, which is a positive value. This gives us N items per row.100vw
) is less than 400px
, (400px - 100vw)
is a positive value and multiplied by a big value that’s clamped to the 100%
. This results in one full-width element per row.Hey, we made our first media query without a real media query! We are updating the number of items per row from N to 1 thanks to our clamp()
formula. It should be noted that 400px
behave as a breakpoint in this case.
We can totally do that by updating our container’s clamped width:
clamp(100%/(N + 1) + 0.1%, (400px - 100vw)*1000, 100%/(M + 1) + 0.1%)
I think you probably get the trick by now. When the screen width is bigger than 400px
we fall into the first rule (N
items per row). When the screen width is smaller than 400px
, we fall into the second rule (M
items per row).
There we go! We can now control the number of items per row and when that number should change—using only CSS custom properties and one CSS declaration.
Controlling the number of items between two values is good, but doing it for multiple values is even better! Let’s try going from N
items per row to M
items per row, down to one item pre row.
Our formula becomes:
clamp(clamp(100%/(N + 1) + 0.1%, (W1 - 100vw)*1000,100%/(M + 1) + 0.1%), (W2 - 100vw)*1000, 100%)
A clamp()
within a clamp()
? Yes, it starts to get a big lengthy and confusing but still easy to understand. Notice the W1
and W2
variables in there. Since we are changing the number of items per rows between three values, we need two “breakpoints” (from N
to M
, and from M
to 1
).
Here’s what’s happening:
W2
, we clamp to 100%
, or one item per row.W2
, we clamp to the first clamp()
.W1
, we clamp to 100%/(M + 1) + 0.1%)
, or M items per row.W1
, we clamp to 100%/(N + 1) + 0.1%)
, or N items per row.We made two media queries using only one CSS declaration! Not only this, but we can adjust that declaration thanks to the CSS custom properties, which means we can have different breakpoints and a different number of columns for different containers.
How many media queries do we have in the above example? Too many to count but we will not stop there. We can have even more by nesting another clamp()
to get from N
columns to M
columns to P
columns to one column. (😱)
clamp(
clamp(
clamp(100%/(var(--n) + 1) + 0.1%, (var(--w1) - 100vw)*1000,
100%/(var(--m) + 1) + 0.1%),(var(--w2) - 100vw)*1000,
100%/(var(--p) + 1) + 0.1%),(var(--w3) - 100vw)*1000,
100%), 1fr))
N
columns to M
columns to P
columns to 1 columnAs I mentioned at the very beginning of this article, we have a responsive layout without any single media queries while using just one CSS declaration—sure, it’s a lengthy declaration, but still counts as one.
A small summary of what we have:
Everyone is excited about container queries! What makes them neat is they consider the width of the element instead of the viewport/screen. The idea is that an element can adapt based on the width of its parent container for more fine-grain control over how elements respond to different contexts.
Container queries aren’t officially supported anywhere at the time of this writing, but we can certainly mimic them with our strategy. If we change 100vw
with 100%
throughout the code, things are based on the .container
element’s width instead of the viewport width. As simple as that!
Resize the below containers and see the magic in play:
The number of columns change based on the container width which means we are simulating container queries! We’re basically doing that just by changing viewport units for a relative percentage value.
Now that we can control the number of columns, let’s explore more tricks that allow us to create conditional CSS based on either the screen size (or the element size).
Conditional background color
A while ago someone on StackOverflow asked if it is possible to change the color of an element based on its width or height. Many said that it’s impossible or that it would require a media query.
But I have found a trick to do it without a media query:
div {
background:
linear-gradient(green 0 0) 0 / max(0px,100px - 100%) 1px,
red;
}
max(0px,100px - 100%)
and a height equal to 1px
. The height doesn’t really matter since the gradient repeats by default. Plus, it’s a one color gradient, so any height will do the job.100%
refers to the element’s width. If 100%
computes to a value bigger than 100px
, the max()
gives us 0px
, which means that the gradient does not show, but the comma-separated red
background does.100%
computes to a value smaller than 100px
, the gradient does show and we get a green
background instead.In other words, we made a condition based on the width of the element compared to 100px
!
This demo supports Chrome, Edge, and Firefox at the time of writing.
The same logic can be based on an element’s height instead by rearranging where that 1px
value goes: 1px max(0px,100px - 100%)
. We can also consider the screen dimension by using vh
or vw
instead of %
. We can even have more than two colors by adding more gradient layers.
div {
background:
linear-gradient(purple 0 0) 0 /max(0px,100px - 100%) 1px,
linear-gradient(blue 0 0) 0 /max(0px,300px - 100%) 1px,
linear-gradient(green 0 0) 0 /max(0px,500px - 100%) 1px,
red;
}
To show/hide an element based on the screen size, we generally reach for a media query and plop a classic display: none
in there. Here is another idea that simulates the same behavior, only without a media query:
div {
max-width: clamp(0px, (100vw - 500px) * 1000, 100%);
max-height: clamp(0px, (100vw - 500px) * 1000, 1000px);
overflow: hidden;
}
Based on the screen width (100vw
), we either get clamped to a 0px
value for the max-height
and max-width
(meaning the element is hidden) or get clamped to 100%
(meaning the element is visible and never greater than full width). We’re avoiding using a percentage for the max-height
since it fails. That’s why we’re using a big pixel value (1000px
).
Notice how the green elements disappear on small screens:
It should be noted that this method is not equivalent to the toggle of the display value. It’s more of a trick to give the element 0×0 dimensions, making it invisible. It may not be suitable for all cases, so use it with caution! It’s more a trick to be used with decorative elements where we won’t have accessibility issues. Chris wrote about how to hide content responsibly.
It’s important to note that I am using 0px
and not 0
inside clamp()
and max()
. The latter makes invalidates property. I won’t dig into this but I have answered a Stack Overflow question related to this quirk if you want more detail.
The following trick is useful when we deal with a fixed or absolutely positioned element. The difference here is that we need to update the position based on the screen width. Like the previous trick, we still rely on clamp()
and a formula that looks like this: clamp(X1,(100vw - W)*1000, X2)
.
Basically, we are going to switch between the X1
and X2
values based on the difference, 100vw - W
, where W
is the width that simulates our breakpoint.
Let’s take an example. We want a div placed on the left edge (top: 50%; left:0
) when the screen size is smaller than 400px
, and place it somewhere else (say top: 10%; left: 40%
) otherwise.
div {
--c:(100vw - 400px); /* we define our condition */
top: clamp(10%, var(--c) * -1000, 50%);
left: clamp(0px, var(--c) * 1000, 40%);
}
First, I have defined the condition with a CSS custom property to avoid the repetition. Note that I also used it with the background color switching trick we saw earlier—we can either use (100vw - 400px)
or (400px - 100vw)
, but pay attention to the calculation later as both don’t have the same sign.
Then, within each clamp()
, we always start with the smallest value for each property. Don’t incorrectly assume that we need to put the small screen value first!
Finally, we define the sign for each condition. I picked (100vw - 400px)
, which means that this value will be negative when the screen width is smaller than 400px
, and positive when the screen width is bigger than 400px
. If I need the smallest value of clamp()
to be considered below 400px
then I do nothing to the sign of the condition (I keep it positive) but if I need the smallest value to be considered above 400px
I need to invert the sign of the condition. That’s why you see (100vw - 400px)*-1000
with the top
property.
OK, I get it. This isn’t the more straightforward concept, so let’s do the opposite reasoning and trace our steps to get a better idea of what we’re doing.
For top
, we have clamp(10%,(100vw - 400px)*-1000,50%)
so…
100vw
) is smaller than 400px
, then the difference (100vw - 400px
) is a negative value. We multiply it with another big negative value (-1000
in this case) to get a big positive value that gets clamped to 50%
: That means we’re left with top: 50%
when the screen size is smaller than 400px
.100vw
) is bigger than 400px
, we end with: top: 10%
instead.The same logic applies to what we’re declaring on the left
property. The only difference is that we multiply with 1000
instead of -1000
.
Here’s a secret: You don’t really need all that math. You can experiment until you get it perfect values, but for the sake of the article, I need to explain things in a way that leads to consistent behavior.
It should be noted that a trick like this works with any property that accepts length values (padding
, margin
, border-width
, translate
, etc.). We are not limited to changing the position
, but other properties as well.
Most of you are probably wondering if any of these concepts are at all practical to use in a real-world use case. Let me show you a few examples that will (hopefully) convince you that they are.
The background color changing trick makes for a great progress bar or any similar element where we need to show a different color based on progression.
This demo supports Chrome, Edge, and Firefox at the time of writing.
That demo is a pretty simple example where I define three ranges:
0% 30%]
30% 60%]
60% 100%]
There’s no wild CSS or JavaScript to update the color. A “magic” background property allows us to have a dynamic color that changes based on computed values.
It’s common to give users a way to edit content. We can update color based on what’s entered.
In the following example, we get a yellow “warning” when entering more than three lines of text, and a red “warning” if we go above six lines. This can a way to reduce JavaScript that needs to detect the height, then add/remove a particular class.
This demo supports Chrome, Edge, and Firefox at the time of writing.
Timelines are great patterns for visualizing key moments in time. This implementation uses three tricks to get one without any media queries. One trick is updating the number of columns, another is hiding some elements on small screens, and the last one is updating the background color. Again, no media queries!
When the screen width is below 600px
, all of the pseudo elements are removed, changing the layout from two columns to one column. Then the color updates from a blue/green/green/blue pattern to a blue/green/blue/green one.
Here’s a responsive card approach where CSS properties update based on the viewport size. Normally, we might expect the layout to transition from two columns on large screens to one column on small screens, where the card image is stacked either above or below the content. In this example, however, we change the position, width, height, padding, and border radius of the image to get a totally different layout where the image sits beside the card title.
Need some nice-looking testimonials for your product or service? These responsive speech bubbles work just about anywhere, even without media queries.
You know those buttons that are sometimes fixed to the left or right edge of the screen, usually for used to link up a contact for or survey? We can have one of those on large screens, then transform it into a persistent circular button fixed to the bottom-right corner on small screens for more convenient taps.
One more demo, this time for something that could work for those GDPR cookie notices:
Media queries have been a core ingredient for responsive designs since the term responsive design was coined years ago. While they certainly aren’t going anywhere, we covered a bunch of newer CSS features and concepts that allow us to rely less often on media queries for creating responsive layouts.
We looked at flexbox and grid, clamp()
, relative units, and combined them together to do all kinds of things, from changing the background of an element based on its container width, moving positions at certain screen sizes, and even mimicking as-of-yet-unreleased container queries. Exciting stuff! And all without one @media
in the CSS.
The goal here is not to get rid or replace media queries but it’s more to optimize and reduce the amount of code especially that CSS has evolved a lot and now we have some powerful tool to create conditional styles. In other words, it’s awesome to see the CSS feature set grow in ways that make our lives as front-enders easier while granting us superpowers to control how our designs behave like we have never had before.
Responsive Layouts, Fewer Media Queries originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>gap
property. It isn’t exactly new, but it did gain an important new ability last year: it now works in Flexbox in addition to CSS Grid. That, and the fact that I …
Minding the “gap” originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>gap
property. It isn’t exactly new, but it did gain an important new ability last year: it now works in Flexbox in addition to CSS Grid. That, and the fact that I believe the property is more complicated than it appears, made me want to go back and explain exactly how it works.
Let’s take a proper look at gap
and its associated properties, and understand how and where they work.
To start us off, let’s review all of the CSS properties related to gap
. There are six in total:
grid-row-gap
grid-column-gap
grid-gap
row-gap
column-gap
gap
From this list, we can already ignore the first three properties. The grid-*
properties were added early when CSS Grid’s specifications were being drafted, and later deprecated when gap
became more generalized. Browsers still support these deprecated grid-*
properties (as of this writing) and merely treat them as if the grid-
prefix is not present. Hence, grid-gap
is the same as gap
, grid-column-gap
is the same as column-gap
and grid-row-gap
is the same as row-gap
.
As for the other three properties, knowing that gap
is a shorthand that lets you specify the other two properties, we really only need to know what row-gap
and column-gap
do.
Our understanding of these properties depends on the type of CSS layout we’re using. Let’s look at those options first.
If you’re like me, you’ve used gaps in grid layouts, but they can now be used in Flexbox, as well as multi-column layouts. Let’s go over each case.
All browsers support gaps in grid layouts, and they’re quite simple to understand in this context.
row-gap
introduces space between row trackscolumn-gap
introduces space between column tracksLet’s create a grid with three columns and two rows:
.container {
display: grid;
grid-template-columns: 200px 100px 300px;
grid-template-rows: 100px 100px;
}
This gives us the following grid:
The lines in the picture above are called grid lines, and they separate the tracks (rows and columns) of the grid. These lines don’t really exist in the grid — they’re invisible, have no thickness, and are typically what DevTools displays when we enable the grid inspector (in Safari, Firefox, Edge or Chrome).
If we do, however, start adding gaps to our grid, it will work as though these lines start acquiring thickness.
Let’s add a 20px
gap:
.container {
display: grid;
grid-template-columns: 200px 100px 300px;
grid-template-rows: 100px 100px;
gap: 20px;
}
The lines between our tracks are now 20px
thick and therefore push grid items further apart.
It’s worth noting that the tracks still have the same sizes (defined by the grid-template-*
properties); therefore, the grid is wider and taller than it was without gaps.
In grid, row-gap
always applies between row tracks. So, if we replace gap
with row-gap
in the above example, we get this:
And column-gap
always applies between column tracks, so replacing gap
with column-gap
produces the following result:
Grid is simple because, by default, columns are vertical, and rows are horizontal, just like in a table. So it’s easy to remember where column-gap
and row-gap
apply.
Now, things do get a little more complicated when writing-mode
is used. The default writing mode on the web is horizontal, from left to right, but there are vertical writing modes as well, and when that happens, columns become horizontal and rows are vertical. Always pay attention to writing-mode
as it can make it less intuitive than it usually is.
This is a good transition into the next section as columns and rows get new meanings within Flexbox.
Let’s talk about gaps in Flexbox layouts, where things get a little more complicated. We’ll use the following example:
.container {
display: flex;
}
By default, this gives us a row
flex container, which means items within the container are stacked from left to right on the same horizontal line.
In this case, column-gap
is applied between items and row-gap
does nothing. That’s because there is only one line (or row). But now let’s add some gap between items:
.container {
display: flex;
column-gap: 10px;
}
Now let’s switch the flex-direction
of our container to column
, which stacks items vertically, from top to bottom, with the following code:
.container {
display: flex;
flex-direction: column;
column-gap: 10px;
}
Here is what happens:
The gap disappeared. Even if column-gap
did add space between items when the container was in a row
direction, it does not work anymore in the column
direction.
We need to use row-gap
to get it back. Alternatively, we could use the gap
shorthand with one value, which would apply the same gap in both directions and, therefore, work in both cases.
.container {
display: flex;
flex-direction: column;
gap: 10px;
}
So, to summarize, colum-gap
always works vertically (assuming the default writing-mode
), and row-gap
always works horizontally. This does not depend on the direction of the flex container.
But now take a look at an example where line wrapping is involved:
.container {
display: flex;
flex-wrap: wrap;
column-gap: 40px;
row-gap: 10px;
justify-content: center;
}
Here, we’re allowing items to wrap on multiple lines with flex-wrap: wrap
if there isn’t enough space to fit everything in a single line.
In this case, the column-gap
is still applied vertically between items, and row-gap
is applied horizontally between the two flex lines.
There’s one interesting difference between this and grid. The column gaps don’t necessarily align across flex lines. That’s because of justify-content: center
, which centers items within their flex lines. This way, we can see that each flex line is a separate layout where gaps apply independently of other lines.
Multi-column is a type of layout that makes it very easy to automatically flow content between several columns, like what you might expect in a traditional newspaper article. We set a number of columns and set the size for each column.
Gaps in multi-column layouts don’t quite work the same as grid or Flexbox. There are three notable differences:
row-gap
has no effect,column-gap
has a non-0 default value,Let’s break those down. First of all, row-gap
has no effect. In multi-column layouts, there aren’t any rows to separate. That means only column-gap
is relevant (as is the gap
shorthand).
Secondly, unlike in grid and Flexbox, column-gap
has a default value of 1em
in multi-column layouts (as opposed to 0). So, even when no gap is specified at all, the columns of content are still visually separated. The default gap can, of course, be overridden but it’s a good default to have.
Here is the code that the example is based on:
.container {
column-count: 3;
padding: 1em;
}
Finally, we can style the empty gap between columns in a multi-column layout. We use the column-rule
property which works like border
:
.container {
column-count: 3;
column-gap: 12px;
column-rule: 4px solid red;
padding: 12px;
}
column-rule
property gives us some styling affordance in a multi-column layout.gap
is really well-supported across the board. There’s more information over at caniuse, but to summarize:
gap
is supported everywhere except for Internet Explorer (which is on its way out), Opera Mini and UC Browser for Android. caniuse has global support at 87.31%.So, overall, the gap
property is well supported and, in most cases, workarounds are unnecessary.
Styling gap
in Flexbox and CSS Grid would be really useful. The sad news is that it isn’t supported anywhere today. But the good news is that it could be in the near future. This has been discussed over at the CSS working group and is in the works in Firefox. Once we have a working implementation in Firefox along with the spec proposal, perhaps it will drive implementation in other browsers.
In the meantime, there are ways around this.
One is to give the grid container a background color, then a different color for the items, and finally a gap to make the container color show through.
While this works, it means we’re unable to use gaps to introduce space between items. The gap
here acts as a border width instead. So, to visually separate the items out a bit more, we need to use padding
or margin
on the items, which isn’t as great… as we’ll see in the next section.
Yes, in most cases we can also use margin (and/or padding) to add visual space between elements of a layout. But gap
comes with multiple advantages.
First, gaps are defined at the container level. This means we define them once for the entire layout and they are applied consistently within it. Using margins would require a declaration on each and every item. This can get complicated when items are different in nature, or come from different reusable components.
On top of this, gaps do the right thing by default with just one line of code. For example, if we’re trying to introduce some space in between flex items, not around them, margin
would require special cases to remove extra margins before the first item and after the last one. With gaps, we don’t need to do this.
With a margin: 0 20px
on each flex item, we’d end up with:
However with a gap: 40px
on the container, we’d get this:
Similarly in grid layout, defining gap
at the container level is much simpler and provides better results than having to define a margin on each item and accounting for the margin that applies on the edge of the grid.
With margin: 20px
on each grid item:
And with gap: 40px
on the grid container instead:
With everything said up to this point, margin
and gap
don’t have to be exclusive. In fact, there are many ways to spread items of a layout further apart, and they all can work together very well.
The gap
property is just one part of the empty space created between boxes in a layout container. margin
, padding
, and alignment all may increase the empty space on top of what gap
already defines.
Let’s consider an example where we build a simple flex layout with a given width, some gap, some distribution of content (using justify-content
), and some margin and padding:
.container {
display: flex;
gap: 40px;
width: 900px;
justify-content: space-around;
}
.item {
padding: 20px;
margin: 0 20px;
}
Let’s assume this code produces the following result:
Now, let’s see exactly how the empty space between items got created:
As we see, there are four different types of empty space between two consecutive flex items:
gap
, it adds space both sides of all items.space-around
value.Let’s conclude with a topic that’s very near and dear to my heart: DevTools support for debugging gaps. There can always be cases where things go wrong, and knowing that DevTools has got our backs is very comforting, but we do need to know which tools can help us in this case.
For gap
, I can think of two very specific features that might become useful.
Unless we misspelled gap
or provided an invalid value, the property is always going to apply to the page. For example, this is correct:
.some-class {
display: block;
gap: 3em;
}
It won’t do anything, but it is valid CSS and the browser doesn’t mind that gap
doesn’t apply to block layouts. However, Firefox has a feature called Inactive CSS that does just this: it cares about valid CSS that’s applied to things that make sense. In this case, Firefox DevTools displays a warning in the Inspector.
Chrome and Microsoft Edge also have a very useful feature for debugging gaps. It was added through a collaboration between Microsoft and Google that was aimed at building layout debugging tools in Chromium (the open source project that powers both of the browsers, as well as others). In these browsers, you can hover over individual properties in the Styles panel, and see their effect on the page.
gap
and the justify-content
properties in the Styles panel, and the corresponding areas of the page light up to indicate where these properties have effect.margin
and padding
properties, which highlights the corresponding box model areas of the page.And that’s it. I hope this article was useful in helping understand some of the details of how gaps work in CSS.
Minding the “gap” originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Let’s break those two down and see how they work together.
Equal Columns With Flexbox: It’s More Complicated Than You Might Think originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>You bang through the hero section and get to work on the three-column section. It’s time to pull out our trusty friend flexbox! Except, you write display: flex
and you get this mess instead of getting the three equal columns you’d expect.
This happens because of how flexbox calculates the base size of an element. You’ve probably read lots of flexbox tutorials, and many of them (including my own) are an overly simplistic example of what flexbox does without really digging into the complex things that flexbox does for us that we take for granted.
I’m positive you’ve seen examples and tutorials that look at something like this, where three divs shrink down around the content that’s inside them:
In other words, we get some block-level elements shrinking down and slotting next to one another. It feels like flex wants things to be as small as possible. But in reality, flexbox actually wants things to be as big as possible.
Wait, what? Flex shrinks things by default — that can’t be right! Right?
As awesome as flexbox is, what it’s doing under the hood is actually a little strange because, by default, it is doing two things at once. It first looks at the content size which is what we would get if by declaring width: max-content
on an element. But on top of that, flex-shrink
is also doing some work allowing the items to be smaller, but only if needed.
To really understand what’s going on, let’s break those two down and see how they work together.
max-content
max-content
is a pretty handy property value in certain situations, but we want to understand how it works in this particular situation where we are trying to get three seemingly simple equal columns. So let’s strip away flexbox for a moment and look at what max-content
does on its own.
MDN does a good job of explaining it:
The
max-content
sizing keyword represents the intrinsic maximum width of the content. For text content this means that the content will not wrap at all even if it causes overflows.
Intrinsic might throw you off here, but it basically means we’re letting the content decide on the width, rather than us explicitly setting a set width. Uri Shaked aptly describes the behavior by saying “the browser pretends it has infinite space, and lays all the text in a single line while measuring its width.”
So, bottom line, max-content
allows the content itself to define the width of the element. If you have a paragraph, the width of that paragraph will be the text inside it without any line breaks. If it’s a long enough paragraph, then you end up with some horizontal scrolling.
Let’s revisit that overly-simplistic example of three block-level elements that shrink down and slot next to one another. That isn’t happening because of flex-shrink
; it’s happening because that’s the size of those elements when their declared width is max-content
. That’s literally as wide as they go because that’s as wide as the combined content inside each element.
Here, take a look at those elements without flexbox doing it’s flexbox stuff, but with a width: max-content
on there instead:
So, when there’s just a small amount of text, the intrinsic max-content
shrinks things down instead of flex-shrink
. Of course, flexbox also comes in with it’s default flex-direction: row
behavior which turns the flex items into columns, putting them right next to one another. Here’s another look but with the free space highlighted.
flex-shrink
to the equationSo we see that declaring display: flex
pushes that max-content
intrinsic size on flex items. Those items want to be as big as their content. But there is another default that comes in here as well, which is flex-shrink
.
flex-shrink
is basically looking at all the flex items in a flexible container to make sure they don’t overflow the parent. If the flex items can all fit next to each other without overflowing the flexible container, then flex-shrink
won’t do anything at all… it’s job is already done.
But if the flex items do overflow the container (thanks to that max-content
intrinsic width thing), the flex items are allowed to shrink to prevent that overflow because flex-shrink
is looking out for that.
This is why flex-shrink
has a default value of 1
. Without it, things would get messy pretty quickly.
Going back to our design scenario where we need three equal columns beneath a hero, we saw that the columns aren’t equal widths. That’s because flexbox starts by looking at the content size of each flex item before even thinking about shrinking them.
For simplicity’s sake, as we dive deeper into this, let’s work with some nice round numbers. We can do this by declaring widths on our flex items. When we declare a width on a flex item, we throw that intrinsic size out the window, as we’ve now declared an explicit value instead. This makes figuring out what’s really going on a lot easier.
In the Pen below, we have a parent that’s a 600px
wide flexible container (display: flex
). I’ve removed anything that might influence the numbers, so no gap
or padding
. I’ve also switched out the border
for an outline
so we can still visualize everything easily.
The first and third flex items have a width: 300px
and the middle one a width: 600px
. If we add that all up, it’s a total of 1200px
. That’s bigger than the the 600px
available within the parent, so flex-shrink
kicks in.
flex-shrink
is a ratio. If everything has the same flex-shrink
(which is 1
by default), they all shrink at the same rate. That doesn’t mean they all shrink to the same size or by the same amount, but they all shrink at the same rate.
If we jump back into Firefox DevTools, we can see the base size, the flex-shrink
and the final size. In this case, the two 300px
elements are now 150px
, and the 600px
one is now 300px
.
300px
become 150px
.600px
becomes 300px
.If we add up all the base sizes of all three flex items (the actual widths we declared on them), the total comes out to 1200px
. Our flex container is 600px
wide. If we divide everything by 2, it fits! They are all shrinking by the same rate, dividing their own widths by 2.
It’s not often that we have nice round numbers like that in the real world, but I think this does a nice job illustrating how flexbox does what it does when figuring out how big to make things.
There are a few different ways to get the three columns we want to be equal in width, but some are better than others. For all the approaches, the basic idea is that we want to get all the columns base size to be the same. If they have an equal base size, then they will shrink (or grow, if we use flex-grow
) at an equal rate when flexbox does it’s flex things, and in theory, that should make them the same size.
There are a few common ways to do this, but as I discovered while diving into all of this, I have come to believe those approaches are flawed. Let’s look at two of the most common solutions that I see used in the wild, and I’ll explain why they don’t work.
flex: 1
One way we can try to get all the flex items to have the same base size is by declaring flex: 1
on all of them:
.flex-parent { display: flex; }
.flex-parent > * { flex: 1; }
In a tutorial I made once, I used a different approach, and I must have had 100 people asking why I wasn’t using flex: 1
instead. I replied by saying I didn’t want to dive into the flex
shorthand property. But then I used flex: 1
in a new demo, and to my surprise, it didn’t work.
The columns weren’t equal.
The middle column here is larger than the other two. It’s not by a ton, but the whole design pattern I’m creating is just so you have perfectly equal columns every single time, regardless of the content.
So why didn’t it work in this situation? The culprit here is the padding
on the component in the middle column.
And maybe you’ll say it’s silly to add padding to one of the items and not the others, or that we can nest things (we’ll get to that). In my opinion, though, I should be able to have a layout that works regardless of the content that we’re putting in it. The web is all about components that we can plug and play these days, after all.
When I first set this up, I was sure it would work, and seeing this issue pop up made me want to learn what was really going on here.
The flex
shorthand does more than just set the flex-grow: 1
. If you don’t already know, flex
is shorthand for flex-grow
, flex-shrink
, and flex-basis
.
The default values for those constituent properties are:
.selector {
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
}
I’m not going to deep dive flex-basis
in this article, as that’s something Robin has already done well. For now, we can think of it like width
to keep things simple since we aren’t playing with flex-direction
.
We’ve already seen how flex-shrink
works. flex-grow
is the opposite. If the size of all the flex items along the main axis is smaller than the parent, they can grow to fill that space.
So by default, flex items:
max-content
.Putting that all together, the flex
shorthand defaults to:
.selector {
flex: 0 1 auto;
}
The fun thing with the flex
shorthand is you can omit values. So, when we declare flex: 1
, it’s setting the first value, flex-grow
, to 1
, which basically turns on flex-grow
.
The strange thing here is what happens to the values that you omit. You’d expect them to stay at their defaults, but they don’t. Before we get to what happens, though, let’s first dive into what flex-grow
even does.
As we’ve seen, flex-shrink
allows elements to shrink if their base sizes add up to a computed value that’s bigger than the available width of the parent container. flex-grow
is the opposite. If the grand total of the element base sizes is smaller than the value of the parent container’s width, then they will grow to fill the available space.
If we take that super basic example where we have three small divs next to one another and add flex-grow: 1
, they grow to fill that leftover space.
But if we have three divs with unequal widths — like those ones we started with — adding flex-grow
to them won’t do anything at all. They won’t grow because they’re already taking up all the available space —so much space, in fact, that flex-shrink
needs to kick in and shrink them down to fit!
But, as folks have pointed out to me, setting flex: 1
can work to create equal columns. Well, sort of, as we saw above! In simple situations it does work though, as you can see below.
When we declare flex: 1
it works because, not only does this set the flex-grow
to 1
, but it also changes the flex-basis
!
.selector {
flex: 1;
/* flex-grow: 1; */
/* flex-shrink: 1; */
/* flex-basis: 0%; Wait what? */
}
Yup, setting flex: 1
sets the flex-basis
to 0%
. This overwrites that intrinsic sizing we had before that behaved like max-content
. All of our flex-items now want to have a base size of 0
!
flex: 1
shorthand in DevTools shows us that the flex-basis
has changed to 0
So their base sizes are 0
now, but because of the flex-grow
, they can all grow to fill up the empty space. And really, in this case, flex-shrink
is no longer doing anything, as all the flex items now have a width of 0
, and are growing to fill the available space.
flex: 1
has a content size of 0
and is growing to fill the available space.Just like the shrink example before, we’re taking the space that’s available, and letting all the flex items grow at an equal rate. Since they are all a base width of 0
, growing at an equal rate means the available space is equally divided between them and they all have the same final size!
Except, as we saw, that’s not always the case…
The reason for this is because, when flexbox does all this stuff and distributes the space, whether it’s shrinking or growing a flex item, it’s looking at the content size of the element. If you remember back to the box model, we have the content size itself, then the padding
, border
, and margin
outside of that.
And no, I didn’t forget * { box-sizing: border-box; }
.
This is one of those strange quirks of CSS but it does make sense. If the browser looked at the width of those elements and included their padding
and borders
in the calculations, how could it shrink things down? Either the padding
would also have to shrink or things are going to overflow the space. Both of those situations are terrible, so instead of looking at the box-size
of elements when calculating the base size of them, it only looks at the content-box!
So, setting flex: 1
causes a problem in cases where you have border
s or padding
on some of your elements. I now have three elements that have a content-size of 0
, but my middle one has padding on it. If we didn’t have that padding, we’d have the same math we did when we looked at how flex-shrink
works.
A parent that is 600px
and three flex items with a width of 0px
. They all have a flex-grow: 1
so they grow at an equal rate, and they each end up 200px
wide. But the padding mucks it all up. Instead, I end up with three divs with a content size of 0
, but the middle one has padding: 1rem
. That means it has a content size of 0
, plus 32px
padding as well.
We have 600 - 32 = 568px
to divide equally, instead of 600px
. All the divs want to grow at an equal rate, so 568 / 3 = 189.3333px
.
And that’s what happens!
But… remember, that’s their content size, not the total width! That leaves us with two divs with a width of 189.333px
, and another with a which of 189.333px + 32 = 221.333px
. That’s a pretty substantial difference!
flex-basis: 100%
I have always handled this like this:
.flex-parent {
display: flex;
}
.flex-parent > * {
flex-basis: 100%;
}
I thought this worked for the longest time. Actually, this was supposed to be my final solution after showing that flex: 1
doesn’t work. But while I was writing this article, I realized it also falls into the same problem, but it’s a lot less obvious. Enough so that I didn’t notice it with my eyes.
The elements are all trying to be 100%
width, meaning they all want to match the width of the parent, which, again, is 600px
(and in normal circumstances, is often much bigger, which further reduces the perceivable difference).
The thing is that 100%
includes the padding in the computed values (because of * { box-size: border-box; }
, which for the sake of this article, I’m assuming everyone is using). So, the outer divs end up with a content size of 600px
, whereas the middle div ends up with a content size of 600 - 32 = 568px
.
When the browser is working out how to evenly divide the space, it isn’t looking at how to evenly squish 1800px
into a 600px
space, but rather it’s looking at how to squish 1768px
. Plus, as we saw earlier, flex items don’t shrink by the same amount, but at an equal pace! So, the element with padding shrinks slightly less in total than the others do.
This results in the .card
having a final width of 214.483px
while the others clock in at 192.75px
. Again, this leads to unequal width values, though the difference is smaller than we saw with the flex: 1
solution.
While all this is a little frustrating (and even a little confusing), it all happens for a good reason. If margins, padding, or borders changed sizes when flex gets involved, it would be a nightmare.
And maybe this means that CSS Grid might be a better solution to this really common design pattern.
I’ve long thought that flexbox was easier to pick up and start using than grid, but grid gives you more ultimate control in the long run, but that it’s a lot harder to figure out. I’ve changed my mind on that recently though, and I think not only does grid give us better control in this type of situation, but it’s actually more intuitive as well.
Normally, when we use grid, we explicitly declare our columns using grid-template-columns
. We don’t have to do that though. We can make it behave a lot like flexbox does by using grid-auto-flow: column
.
.grid-container {
display: grid;
grid-auto-flow: column;
}
Just like that, we end up with the same type of behavior as throwing display: flex
on there. Like flex
, the columns can potentially be unbalanced, but the advantage with grid is that the parent has total control over everything. So, rather than the content of the items having an impact like they would in flexbox, we only need one more line of code and we can solve the problem:
.grid-container {
display: grid;
grid-auto-flow: column;
grid-auto-columns: 1fr;
}
I love that this is all on the parent selector, and that we don’t have to select the children to help get the layout that we are after!
The interesting thing here is how fr
units work. They are literally called “flex units” in the spec, and they work just like flexbox does in dividing up space;. The big difference: they’re looking at the other tracks to determine how much space they have, paying no attention to the content inside those tracks.
That’s the real magic here. By declaring grid-auto-columns: 1fr
, we are in essence saying, “by default, all my columns should have an equal width,” which is what we’ve been after from the start!
What I love with this approach is we can keep it super simple:
.grid-container {
display: grid;
gap: 1em;
}
@media (min-width: 35em) {
grid-auto-flow: column;
grid-auto-columns: 1fr;
}
And just like that, it works perfectly. And by declaring display: grid
from the start, we can include the gap
to maintain equal spacing, whether the children are rows or columns.
I also find this to be a lot more intuitive than changing the flex-direction
within a media query to get a similar result. As much as I love flexbox (and I really do still think it has great use cases), the fact that declaring flex-direction: column
creates rows, and vice versa, is a little counter-intuitive at first glance.
And of course, if you prefer rolling without media queries, there is nothing stopping you from taking this to the next level with the help of auto-fit
, which would be similar to setting something up with flex-wrap
(though not exactly the same):
.grid-container {
display: grid;
gap: 1em;
grid-template-columns: repeat(auto-fit, minmax(10em, 25em));
}
I realize that we can get around this with flexbox by nesting the element with the padding
on it. We’ve done this since we started making layouts using floats, and Bootstrap really hammered home this type of design pattern.
<div class="container">
<div class="row">
<div class="col"> <div class="">... </div>
<div class="col"> <div class="element-with-padding">...</div> </div>
<div class="col"> ... </div>
</div>
</div>
And there is nothing wrong with that. It works! But floats work too, and we’ve stopped using them for layouts as better solutions have been released in recent years.
One of the reasons that I love grid is because we can simplify our markup quite a bit. Just like we ditched floats for a better solution, I’d at least like to people to keep an open mind that maybe, just maybe, grid could be a better, and more intuitive solution for this type of design pattern.
I still love flexbox, but I think its real power comes from times that we want to rely on the intrinsic width of the flex items, such as with navigations, or groups of elements, such as buttons or other items of varying width that you want to go next to one another.
In those situations, that behavior is an asset that makes things like even spacing between unequal items such a breeze! Flexbox is wonderful, and I have no plans to stop using.
In other situations though, when you find yourself fighting with how flexbox is trying to work, maybe you could turn to grid instead, even if it’s not a typical “2d” grid where you’re told you should be using it for.
People often tell me that they struggle to figure out grid because it’s too complicated, and while it can be, as we saw here, it doesn’t have to be.
Equal Columns With Flexbox: It’s More Complicated Than You Might Think originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>float
property. But what about if you also want to push that element (let’s call it …
Float an Element to the Bottom Corner originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>float
property. But what about if you also want to push that element (let’s call it an image) to one of the bottom corners while we’re at it? Sounds a bit tricky, right? We probably need JavaScript?
Nope, few lines of (tricky) CSS can do it! Here’s the CSS-only solution that will make your image to stick to the bottom corner, regardless of the size and content.
Resize the wrapper element and see the magic at work:
Let’s dissect the code.
We’ll need a wrapper element to contain everything, and we’ll be using flexbox on it. Flexbox allows us to rely on the default stretch
alignment to be able to later use height: 100%
.
<div class="wrapper">
<div class="box">
<div class="float"><img></div>
Lorem ipsum dolor ...
</div>
</div>
.wrapper {
display: flex;
}
.float {
float: right;
height: 100%;
display: flex;
align-items: flex-end;
shape-outside: inset(calc(100% - 100px) 0 0);
}
The .box
within the .wrapper
is our flex item. We don’t need any particular CSS applied to the box. It defines the height of the wrapper and, at the same time, is stretched to the same height. This behavior will give us a “reference height” that can be used by the child elements.
From the specification:
If the flex item has
align-self: stretch
, redo layout for its contents, treating this used size as its definite cross size so that percentage-sized children can be resolved.
The keyword is the definite which allows us to safely use a percentage (%) height inside the box element.
Our .float
element will take the entire height next to the text content, thanks to the height calculation we detailed above. Inside this element we push the image to the bottom using flexbox alignment.
Now for the real trickery, using the shape-outside
property. Here’s how MDN defines it:
The shape-outside CSS property defines a shape—which may be non-rectangular—around which adjacent inline content should wrap. By default, inline content wraps around its margin box;
shape-outside
provides a way to customize this wrapping, making it possible to wrap text around complex objects rather than simple boxes.
In other words, shape-outside
sets the way content flows around an element’s bounding box.
It takes a number of values. One of those is the inset()
function which, again, according to MDN:
Defines an inset rectangle. When all of the first four arguments are supplied they represent the top, right, bottom and left offsets from the reference box inward that define the positions of the edges of the inset rectangle.
So, with shape-outside: inset(calc(100% - X) 0 0)
we can create an inset rectangle that starts exactly at the top of the image. And the top is equal to 100% - X
, where X
is the image height and 100%
is the height of the .float
element. This allows the text to wrap within the free space on the top of the image. This is responsive, plus we can easily switch between left and right (by adjusting the float
property)
That’s it! The only major caveat is that you need to know the image height.
We can still optimize our code a little and remove the extra wrapper around the image.
<div class="wrapper">
<div class="box">
<img class="float">
Lorem ipsum dolor ...
</div>
</div>
Then our CSS will become
.wrapper {
display: flex;
}
.float {
float: right;
height: 100%;
width: 100px;
shape-outside: inset(calc(100% - 100px /*height */) 0 0);
object-fit: contain;
object-position: bottom;
}
Since we removed the extra wrapper we need another technique to place the image at the bottom corner. For this, we are using object-fit
and object-position
but we have to explicitly specify the width of the image.
Using this method we need to know both the width and the height of the image unlike the previous code where only the height is needed.
We can extend this concept a little further to account for fancier situations. For example, we can float the image to the right, but pin it to the middle of the box with justify-content: center
: and also adjust our inset rectangle to the middle by changing the shape-outside
from inset(calc(100% - X) 0 0)
to inset(calc(50% - X/2) 0 0)
We can also float two images at both bottom corners:
Nothing complex here. I am simply using the same floating element twice, once on the right, and again on the left. And why stop at two corners when we can place images at all four corners:
The same basic idea is at play here, but we’re are also relying on the common float feature for the top images. However, you’ll notice that this is where the concept starts to break down a bit, and we get some unwanted overflow depending on the size of the containing box. We can make the height of the .float
element greater than 100% and apply somewhat “magic numbers” that smooth things out by adjusting the padding and margin of the images.
Did you know that shape-outside
accepts radial-gradient()
as a value? We can use that to place rounded images like below:
The transparent part of the gradient is the free space where the text can go. You may have noticed that we applied a border-radius
to the image as well. The shape-outside
property will simply affect the .float
element and we need to manually adjust the shape of the image to follow the shape defined by shape-outside
.
While we’re at it, let’s combine this with our earlier example that pins the image to the vertical center of the box using justify-content: center
:
Another radial-gradient()
and also another border-radius
configuration.
We could have used a linear-gradient()
instead to make a triangular shape for the wrapping area:
This is the same idea that we used for the radial-gradient()
. The big difference is that we’re using clip-path
instead of border-radius
to cut our image.
And, since we did it for the others, let’s use the justify-content: center
idea to pin the image to the vertical center of the box’s right edge:
We used a conic-gradient()
in the above demo with shape-outside
to define the triangular shape and clip-path
to get a similar shape on the image
All of these examples can still be optimized using less of code in the case that the image is decorative (when it’s not needed inside the HTML for SEO purposes). Let’s replace the .float
element with a pseudo-element and apply the image as background instead:
We’re using mask
to show just the portion of the image that we need and, guess what, it uses the same value as shape-outside
! So, all we had to do is define one value for the shape.
There are a lot of possibilities here to place not just rectangles in corners, but any kind of shape at any position, using largely the same code structure. We only need to:
shape-outside
property to define the shapemask
in case we are using the pseudo element versionThen everything holds it place, even in responsive designs.
Float an Element to the Bottom Corner originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>How to Make an Area Chart With CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>To simplify things, we will be using <ul>
tags as wrappers and <li>
elements for individual data items. You can use any other HTML tag in your project, depending on your needs.
<ul class="area-chart">
<li> 40% </li>
<li> 80% </li>
<li> 60% </li>
<li> 100% </li>
<li> 30% </li>
</ul>
CSS can’t retrieve the inner HTML text, that is why we will be using CSS custom properties to pass data to our CSS. Each data item will have a --start
and an --end
custom properties.
<ul class="area-chart">
<li style="--start: 0.1; --end: 0.4;"> 40% </li>
<li style="--start: 0.4; --end: 0.8;"> 80% </li>
<li style="--start: 0.8; --end: 0.6;"> 60% </li>
<li style="--start: 0.6; --end: 1.0;"> 100% </li>
<li style="--start: 1.0; --end: 0.3;"> 30% </li>
</ul>
There are several design principles we ought to consider before moving into styling:
px
, em
, rem
, %
or any other unit). The --start
and --end
custom properties will be numbers between 0 and 1.width
for each <li>
element. We won’t be using %
either, as we don’t know how many items are there. Each column width will be based on the main wrapper width, divided by the total number of data items. In our case, that’s the width of the <ul>
element divided by the number of <li>
elements.<li>
is optional and only the --start
and --end
custom properties are required. Still, it’s best to include some sort of text or value for screen readers and other assistive technologies to describe the content.Let’s start with general layout styling first. The chart wrapper element is a flex container, displaying items in a row, stretching each child element so the entire area is filled.
.area-chart {
/* Reset */
margin: 0;
padding: 0;
border: 0;
/* Dimensions */
width: 100%;
max-width: var(--chart-width, 100%);
height: var(--chart-height, 300px);
/* Layout */
display: flex;
justify-content: stretch;
align-items: stretch;
flex-direction: row;
}
If the area chart wrapper is a list, we should remove the list style to give us more styling flexibility.
ul.area-chart,
ol.area-chart {
list-style: none;
}
This code styles all of the columns in the entire chart. With bar charts it’s simple: we use background-color
and height
for each column. With area charts we are going to use the clip-path
property to set the region that should be shown.
First we set up each column:
.area-chart > * {
/* Even size items */
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0;
/* Color */
background: var(--color, rgba(240, 50, 50, .75));
}
To create a rectangle covering the entire area, we will reach for the clip-path
property and use its polygon()
function containing the coordinates of the area. This basically doesn’t do anything at the moment because the polygon covers everything:
.area-chart > * {
clip-path: polygon(
0% 0%, /* top left */
100% 0%, /* top right */
100% 100%, /* bottom right */
0% 100% /* bottom left */
);
}
Now for the best part!
To show just part of the column, we clip it to create that area chart-like effect. To show just the area we want, we use the --start
and --end
custom properties inside the clip-path
polygon:
.area-chart > * {
clip-path: polygon(
0% calc(100% * (1 - var(--start))),
100% calc(100% * (1 - var(--end))),
100% 100%,
0% 100%
);
}
Seriously, this one bit of CSS does all of the work. Here’s what we get:
Now that we know the basics, let’s create an area chart with multiple datasets. Area charts often measure more than one set of data and the effect is a layered comparison of the data.
This kind of chart requires several child elements, so we are going to replace our <ul>
approach with a <table>
.
<table class="area-chart">
<tbody>
<tr>
<td> 40% </td>
<td> 80% </td>
</tr>
<tr>
<td> 60% </td>
<td> 100% </td>
</tr>
</tbody>
</table>
Tables are accessible and search engine friendly. And if the stylesheet doesn’t load for some reason, all the data is still visible in the markup.
Again, we will use the --start
and --end
custom properties with numbers between 0 and 1.
<table class="area-chart">
<tbody>
<tr>
<td style="--start: 0; --end: 0.4;"> 40% </td>
<td style="--start: 0; --end: 0.8;"> 80% </td>
</tr>
<tr>
<td style="--start: 0.4; --end: 0.6;"> 60% </td>
<td style="--start: 0.8; --end: 1.0;"> 100% </td>
</tr>
</tbody>
</table>
So, first we will style the general layout for the wrapping element, our table, which we’ve given an .area-chart
class:
.area-chart {
/* Reset */
margin: 0;
padding: 0;
border: 0;
/* Dimensions */
width: 100%;
max-width: var(--chart-width, 600px);
height: var(--chart-height, 300px);
}
Next, we will make the <tbody>
element a flex container, displaying the <tr>
items in a row and evenly sized:
.area-chart tbody {
width: 100%;
height: var(--chart-height, 300px);
/* Layout */
display: flex;
justify-content: stretch;
align-items: stretch;
flex-direction: row;
}
.area-chart tr {
/* Even size items */
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0;
}
Now we need to make the <td>
elements cover each other, one element on top of each other so we get that layered effect. Each <td>
covers the entire area of the <tr>
element that contains it.
.area-chart tr {
position: relative;
}
.area-chart td {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
Let’s put the magical powers of clip-path: polygon()
to use! We’re only displaying the area between the --start
and --end
custom properties which, again, are values between 0 and 1:
.area-chart td {
clip-path: polygon(
0% calc(100% * (1 - var(--start))),
100% calc(100% * (1 - var(--end))),
100% 100%,
0% 100%
);
}
Now let’s add color to each one:
.area-chart td {
background: var(--color);
}
.area-chart td:nth-of-type(1) {
--color: rgba(240, 50, 50, 0.75);
}
.area-chart td:nth-of-type(2) {
--color: rgba(255, 180, 50, 0.75);
}
.area-chart td:nth-of-type(3) {
--color: rgba(255, 220, 90, 0.75);
}
It’s important to use colors with opacity to get a nicer effect, which is why we’re using rgba()
values. You could use hsla()
here instead, if that’s how you roll.
And, just like that:
It doesn’t matter how many HTML elements we add to our chart, the flex-based layout makes sure all the items are equally sized. This way, we only need to set the width of the wrapping chart element and the items will adjust accordingly for a responsive layout.
We have covered one technique to create area charts using pure CSS. For advanced use cases, you can check out my new open source data visualization framework, ChartsCSS.org. See the Area Chart section to see how area charts can be customized with things like different orientations, axes, and even a reversed order without changing the HTML markup, and much more!
How to Make an Area Chart With CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]><div class="parent"<div class="child"Child</div<div class="child"Child</div<div class="child"Child</div</div
…
Understanding flex-grow, flex-shrink, and flex-basis originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]><div class="parent">
<div class="child">Child</div>
<div class="child">Child</div>
<div class="child">Child</div>
</div>
And then we write some CSS…
.parent {
display: flex;
}
These are technically not the only styles we’re applying when we write that one line of CSS above. In fact, a whole bunch of properties will be applied to the .child
elements here, as if we wrote these styles ourselves:
.child {
flex: 0 1 auto; /* Default flex value */
}
That’s weird! Why do these elements have these extra styles applied to them even though we didn’t write that code? Well, that’s because some properties have defaults that are then intended to be overridden by us. And if we don’t happen to know these styles are being applied when we’re writing CSS, then our layouts can get pretty darn confusing and tough to manage.
That flex
property above is what’s known as a shorthand CSS property. And really what this is doing is setting three separate CSS properties at the same time. So what we wrote above is the same as writing this:
.child {
flex-grow: 0;
flex-shrink: 1;
flex-basis: auto;
}
So, a shorthand property bundles up a bunch of different CSS properties to make it easier to write multiple properties at once, precisely like the background
property where we can write something like this:
body {
background: url(sweettexture.jpg) top center no-repeat fixed padding-box content-box red;
}
I try to avoid shorthand properties because they can get pretty confusing and I often tend to write the long hand versions just because my brain fails to parse long lines of property values. But it’s recommended to use the shorthand when it comes to flexbox, which is…weird… that is, until you understand that the flex
property is doing a lot of work and each of its sub-properties interact with the others.
Also, the default styles are a good thing because we don’t need to know what these flexbox properties are doing 90% of the time. For example, when I use flexbox, I tend to write something like this:
.parent {
display: flex;
justify-content: space-between;
}
I don’t even need to care about the child elements or what styles have been applied to them, and that’s great! In this case, we’re aligning the child items side-by-side and then spacing them equally between each other. Two lines of CSS gives you a lot of power here and that’s the neatest thing about flexbox and these inherited styles — you don’t have to understand all the complexity under the hood if you just want to do the same thing 90% of the time. It’s remarkably smart because all of that complexity is hidden out of view.
But what if we want to understand how flexbox — including the flex-grow
, flex-shrink
, and flex-basis
properties — actually work? And what cool things can we do with them?
Just go to the CSS-Tricks Almanac. Done!
Just kidding. Let’s start with a quick overview that’s a little bit simplified, and return to the default flex
properties that are applied to child elements:
.child {
flex: 0 1 auto;
}
These default styles are telling that child element how to stretch and expand. But whenever I see it being used or overridden, I find it helpful to think of these shorthand properties like this:
/* This is just how I think about the rule above in my head */
.child {
flex: [flex-grow] [flex-shrink] [flex-basis];
}
/* or... */
.child {
flex: [max] [min] [ideal size];
}
That first value is flex-grow
and it’s set to 0
because, by default, we don’t want our elements to expand at all (most of the time). Instead, we want every element to be dependent on the size of the content within it. Here’s an example:
.parent {
display: flex;
}
I’ve added the contenteditable
property to each .child
element above so you can click into it and type even more content. See how it responds? That’s the default behavior of a flexbox item: flex-grow
is set to 0
because we want the element to grow based on the content inside it.
But! If we were to change the default of the flex-grow
property from 0
to 1
, like this…
.child {
flex: 1 1 auto;
}
Then all the elements will take an equal portion of the .parent
element, but only if the lengths of their contents are the same.
This is exactly the same as writing…
.child {
flex-grow: 1;
}
…and ignoring the other values because those have been set by default anyway. I think this confused me for such a long time when I started working with flexible layouts. I would see code that would add just flex-grow
and wonder where the other styles are coming from. It was like an infuriating murder mystery that I just couldn’t figure out.
Now, if we wanted to make just one of these elements grow more than the others we’d just need to do the following:
.child-three {
flex: 3 1 auto;
}
/* or we could just write... */
.child-three {
flex-grow: 3;
}
Is this weird code to look at even a decade after flexbox landed in browsers? It certainly is for me. I need extra brain power to say, “Ah, max, min, ideal size,” when I’m reading the shorthand, but it does get easier over time. Anyway, in the example above, the first two child elements will take up proportionally the same amount of space but that third element will try to grow up to three times the space as the others.
Now this is where things get weird because this is all dependent on the content of the child elements. Even if we set flex-grow
to 3
, like we did in the example above and then add more content, the layout will do something odd and peculiar like this:
That second column is now taking up too much darn space! We’ll come back to this later, but for now, it’s just important to remember that the content of a flex item has an impact on how flex-grow
, flex-shrink
, and flex-basis
work together.
OK so now for flex-shrink
. Remember that’s the second value in the shorthand:
.child {
flex: 0 1 auto; /* flex-shrink = 1 */
}
flex-shrink
tells the browser what the minimum size of an element should be. The default value is 1
, which is saying, “Take up the same amount of space at all times.” However! If we were to set that value to 0
like this:
.child {
flex: 0 0 auto;
}
…then we’re telling this element not to shrink at all now. Stay the same size, you blasted element! is essentially what this CSS says, and that’s precisely what it’ll do. We’ll come back to this property in a bit once we look at the final value in this shorthand.
flex-basis
is the last value that’s added by default in the flex
shorthand, and it’s how we tell an element to stick to an ideal size. By default, it’s set to auto
which means, “Use my height or width.” So, when we set a parent element to display: flex
…
.parent {
display: flex;
}
.child {
flex: 0 1 auto;
}
We’ll get this by default in the browser:
Notice how all the elements are the width of their content by default? That’s because auto
is saying that the ideal size of our element is defined by its content. To make all the elements take up the full space of the parent we can set the child elements to width: 100%
, or we can set the flex-basis
to 100%
, or we can set flex-grow
to 1
.
Does that make sense? It’s weird, huh! It does when you think about it. Each of these shorthand values impact the other and that’s why it is recommended to write this shorthand in the first place rather than setting these values independently of one another.
OK, moving on. When we write something like this…
.child-three {
flex: 0 1 1000px;
}
What we’re telling the browser here is to set the flex-basis
to 1000px
or, “please, please, please just try and take up 1000px
of space.” If that’s not possible, then the element will take up that much space proportionally to the other elements.
You might notice that on smaller screens this third element is not actually a 1000px
! That’s because it’s really a suggestion. We still have flex-shrink
applied which is telling the element to shrink to the same size as the other elements.
Also, adding more content to the other children will still have an impact here:
Now, if we wanted to prevent this element from shrinking at all we could write something like this:
.child-three {
flex: 0 0 1000px;
}
Remember, flex-shrink
is the second value here and by setting it to 0 we’re saying, “Don’t shrink ever, you jerk.” And so it won’t. The element will even break out of the parent element because it’ll never get shorter than 1000px
wide:
Now all of this changes if we set flex-wrap
to the parent element:
.parent {
display: flex;
flex-wrap: wrap;
}
.child-three {
flex: 0 0 1000px;
}
We’ll see something like this:
This is because, by default, flex items will try to fit into one line but flex-wrap: wrap
will ignore that entirely. Now, if those flex items can’t fit in the same space, they’ll break onto a new line.
Anyway, this is just some of the ways in which flex
properties bump into each other and why it’s so gosh darn valuable to understand how these properties work under the hood. Each of these properties can affect the other, and if you don’t understand how one property works, then you sort of don’t understand how any of it works at all — which certainly confused me before I started digging into this!
But to summarize:
flex
shorthand Understanding flex-grow, flex-shrink, and flex-basis originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>#196: Learning Grid & Flexbox with Kyle Simpson originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Along the way, we let the VS Code extension TabNine see what it could do to help us write CSS.
#196: Learning Grid & Flexbox with Kyle Simpson originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Balancing on a Pivot with Flexbox originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>I’m big on word games, so I recently re-imagined the main menu of my website as a nod to crossword puzzles, with my name as the vertical word, and the main sections of my website across the horizontals.
Here’s how the design looks with the names of some colors instead:
And here’s a sample of the HTML that drives this puzzle:
<div class="puzzle">
<div class="word">
<span class="letter">i</span>
<span class="letter">n</span>
<span class="letter">d</span>
<span class="letter">i</span>
<span class="letter pivot">g</span>
<span class="letter">o</span>
</div>
<!-- MORE WORDS -->
</div>
In this example, the letter g
is the pivot. See how it’s not at the halfway mark? That’s the beauty of this challenge.
We could apply an offset to each word using hard-coded CSS or inline custom properties and walk away. It certainly gets an award for being the most obvious way to solve the problem, but there’s a downside — in addition to the .pivot
class, we’d have to specify an offset for every word. The voice in my head tells me that’s adding unnecessary redundancy, is less flexible, and requires extra baggage we don’t need every time we add or change a word.
Let’s take a step back instead and see how the puzzle looks without any balancing:
Imagine for a moment that we use display: none
to hide all of the letters before the pivot; now all we can see are the pivots and everything after them:
With no further changes, our pivots would already be aligned. But we’ve lost the start of our words, and when we reintroduce the hidden parts, each word gets pushed out to the right and everything is out of whack again.
If we were to hide the trailing letters instead, we’d still be left with misaligned pivots:
All of this back-and-forth seems a bit pointless, but it reveals a symmetry to my problem. If we were to use a right-to-left (RTL) reading scheme, we’d have the opposite problem — we’d be able to solve the right side but the left would be all wrong.
Wouldn’t it be great if there was a way to have both sides line up at the same time?
As a matter of fact, there is.
Given we already have half a solution, let’s borrow a concept from algorithmics called divide and conquer. The general idea is that we can break a problem down into parts, and that by finding a solution for the parts, we’ll find a solution for the whole.
In that case, let’s break our problem down into the positioning of two parts. First is the “head” or everything before the pivot.
Next is the “tail” which is the pivot plus everything after it.
The flex
display type will help us here; if you’re not familiar with it, flex
is a framework for positioning elements in one-dimension. The trick here is to take advantage of the left and right ends of our container to enforce alignment. To make it work, we’ll swap the head and tail parts by using a smaller order
property value on the tail than the head. The order property is used by flex to determine the sequence of elements in a flexible layout. Smaller numbers are placed earlier in the flow.
To distinguish the head and tail elements without any extra HTML, we can apply styles to the head part to all of the letters, after which we’ll make use of the cascading nature of CSS to override the pivot and everything after it using the subsequent-sibling selector .pivot ~ .letter
.
Here’s how things look now:
Okay, so now the head is sitting flush up against the end of the tail. Hang on, don’t go kicking up a stink about it! We can fix this by applying margin: auto
to the right of the last element in the tail. That just so happens to also be the last letter in the word which is now sitting somewhere in the middle. The addition of an auto margin serves to push the head away from the tail and all the way over to the right-hand side of our container.
Now we have something that looks like this:
The only thing left is stitch our pieces back together in the right order. This is easy enough to do if we apply position: relative
to all of our letters and then chuck a left: 50%
on the tail and a right: 50%
on our head items.
Here’s a generalized version of the code we just used. As you can see, it’s just 15 lines of simple CSS:
.container {
display: flex;
}
.item:last-child {
margin-right: auto;
}
.item {
order: 2;
position: relative;
right: 50%;
}
.pivot, .pivot ~ .item {
order: 1;
left: 50%;
}
It’s also feasible to use this approach for vertical layouts by setting the flex-direction
to a column
value. It should also be said that the same can be achieved by sticking the head and tail elements in their own wrappers — but that would require more markup and verbose CSS while being a lot less flexible. What if, for example, our back-end is already generating an unwrapped list of elements with dynamically generated classes?
Quite serendipitously, this solution also plays well with screen readers. Although we’re ordering the two sections backwards, we’re then shifting them back into place via relative positioning, so the final ordering of elements matches our markup, albeit nicely centered.
Here’s the final example on CodePen:
Developers are better at balancing than acrobats. Don’t believe me? Think about it: many of the common challenges we face require finding a sweet spot between competing requirements ranging from performance and readability, to style and function, and even scalability and simplicity. A balancing act, no doubt.
But the point at which we find balance isn’t always midway between one thing and another. Balance is often found at some inexplicable point in between; or, as we’ve just seen, around an arbitrary HTML element.
So there you have it! Go and tell your friends that you’re the greatest acrobat around.
Balancing on a Pivot with Flexbox originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Achieving Vertical Alignment (Thanks, Subgrid!) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Let’s take a look at just one particular scenario: a “bar” with some buttons in it. There are two groups of these buttons, each contained within a <fieldset>
with a <legend>
.
On a large screen, we’re all set:
And here’s a very basic CSS method that accomplishes that layout, and also breaks down onto two “rows” at a mobile breakpoint:
.accessibility-tools fieldset {
width: 48%;
float: left;
margin-right: 1%;
}
/* Mobile */
@media only screen and (max-width: 480px) {
.accessibility-tools fieldset {
width: 100%;
}
}
On a small screen, we end up with this:
This is the problem: lack of vertical alignment. Let’s say we want to align those buttons into a more pleasing arrangement where the button edges align with each other nicely.
To begin, we could go for fixed-width, pixel-based CSS solutions to force elements to line up nicely at various breakpoints, using magic numbers like this:
/* Mobile */
@media only screen and (max-width: 480px) {
legend {
width: 160px;
}
button {
width: 130px;
}
}
That does the trick.
But… this is not exactly a flexible solution to the problem. Aside from the magic numbers (fixed-pixel values based on specific content), it also relied on the use of media queries which I am trying to move away from when I can. I discussed this in a post called “Stepping away from Sass” on my blog.
As I moved towards some of the more modern features of CSS the need to target specific screen sizes with unique code was removed.
What I need is each button and label to respond to:
and!
The problem with using media queries is that they don’t take into account the space around the elements that are being realigned — a point perfectly demonstrated in this image from “The Flexbox holy albatross” by Heydon Pickering:
What I really want is for the second <fieldset>
to wrap under the first only when they can no longer fit neatly on one row.
A key selling point for flexbox is its ability to create elements that respond to the space around them. Components can “flex” to fill additional space and shrink to fit into smaller spaces.
For this situation, the flex-wrap
property is set to wrap
. This means as soon as both <fieldset>
elements no longer fit on one line, they will wrap onto a second line.
.wrapper--accessibility-tools {
display: flex;
flex-wrap: wrap;
}
The flex-wrap
property has three available values. The default value is nowrap
, leaving items on one line. The wrap
value allows elements to flow onto multiple lines. Then there’s wrap-reverse
, which allows items to wrap but — wait for it — in reverse (it is weird to see: when elements wrap, they go above the previous row in left-to-right situations).
Using flexbox stops the layout from being quite as rigid, but a min-width
value is still needed to remove the vertical alignment problem. So: close but no cigar.
CSS Grid is the very first CSS module created specifically to solve the ongoing layout problems faced by web designers and developers. It is not a direct replacement for flexbox; rather the two modules usually work pretty well together.
Like flexbox, grid can be used to allow each <fieldset>
to occupy as much or as little space as they need. Getting right to it, we can leverage the auto-fill
and auto-fit
keywords (within a repeat()
function) to allow grid items to flow onto multiple lines without the need for media queries. The difference is a bit subtle, but well-explained in “Auto-Sizing Columns in CSS Grid: auto-fill vs auto-fit” by Sara Soueidan. Let’s use auto-fit
:
.wrapper--accessibility-tools {
display: grid;
grid-template-columns: repeat(auto-fit, 450px);
grid-gap: 10px;
}
Like the flexbox example, I still need to set an absolute value for the width of the label to align the <fieldset>
elements as they stack.
CSS Grid also allows elements to respond based on their content using flexible grid tracks. In addition to other length values like percentages, relative units, or pixels, CSS Grid accepts a Fractional Unit (fr
), where 1fr
will take up one part of the available space, 2fr
will take up two parts of the available space, and so on. Let’s set up two equal columns here:
.wrapper--accessibility-tools {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 10px;
}
There’s also a minmax()
function which creates grid tracks that flex to the available space, but also don’t shrink narrower than a specified size.
.wrapper--accessibility-tools {
display: grid;
grid-template-columns: minmax(auto, max-content) minmax(auto, max-content);
grid-gap: 10px;
}
Both of these demos work, and are free from any absolute values or device specific CSS. The results are far from ideal though, each grid now responds at different points. Maybe not a huge problem, but certainly not great.
This happens because when adding display: grid
to a container, only the direct children of that container become grid items. This means the intrinsic sizing units we used only relate to elements in the same grid.
To really achieve my goal, I need the buttons and labels to react to elements in sibling grid containers. CSS Grid Level 2 includes the subgrid feature. Although we have always been able to nest grids, the elements within each grid container have been independent. With subgrid, we get to set up nested (child) grids that use parent grids tracks.
This makes a number patterns that were previously difficult much easier, in particular the “card” pattern which seems to be the most popular example to show the benefits of subgrid. Without subgrid, each card is defined as an independent grid, meaning track sizing in the first card cannot respond to a change of height in the second. Pulling from an example Rachel Andrew used, here’s a simple group of cards:
Subgrid allows the cards to use the rows defined in the parent grid, meaning they can react to content in surrounding cards.
Each card in this example still spans three row tracks, but those rows are now defined on the parent grid, allowing each card to occupy the same amount of vertical space.
For the example we’ve been working with, we do not need to use rows. Instead, we need to size columns based on content from sibling grids. First, let’s set the parent grid to contain the two <fieldset>
elements. This is similar to the code we previously look at in the auto-fit
demo.
.wrapper--accessibility-tools {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
grid-gap: 10px;
}
Then we position each subgrid onto the parent grid.
.sub-grid {
display: grid;
grid-column: span 3;
grid-template-columns: subgrid;
align-items: center;
}
All of the labels and buttons are now aligned to the tracks of their parent grid, keeping them consistent. They will each have an equal width based on the space that is available. If there is not enough space for each nested grid on one line, the second will wrap onto a new line.
This time, the two nested grid items align perfectly. The grid is also flexible if we introduce a longer title on a one of the buttons, the other elements will respond accordingly.
Support for subgrid is not great at the time of writing. It is only supported in Firefox 71+, although there are positive signals from other browsers. CSS feature queries can be used to provide alternative styling to Chrome and Edge.
This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.
Chrome | Firefox | IE | Edge | Safari |
---|---|---|---|---|
117 | 71 | No | 117 | 16.0 |
Android Chrome | Android Firefox | Android | iOS Safari |
---|---|---|---|
127 | 127 | 127 | 16.0 |
Note that I am using an extra wrapper around the fieldsets in these demos. This is to combat a bug with form elements and grid and flexbox.
<fieldset class="accessibility-tools__colour-theme">
<div class="wrapper"></div>
</fieldset>
The layout CSS is applied to the wrapper with the fieldset being set to display: contents
.
.accessibility-tools fieldset {
display: contents;
border: 0;
}
Achieving Vertical Alignment (Thanks, Subgrid!) originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Holy Albatross with Widths originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>I’ve used it before, although it was pointed out to me that using it only on two elements isn’t really necessary, and that the Holy Albatross is most useful when working with three or more elements.
The original article kind didn’t get into setting the widths on the horizontal row of elements (say one of them needs to be larger than the others), but the follow-up article has a demo in it showing that flex-grow
can be used to do exactly that. But Xiao Zhuo Jia notes that it’s not exactly a great system:
Problem is, it’s very difficult to set width to your non-stacked column, because the width is taken over by the hack.
One suggestion by Heydon is to use flex-grow, the problems are:
1. It is a very unintuitive way to set width – for a 3 column layout, you want 1 column to be 50% wide, you have to set flex-grow to 2.333
2. You have to know the number of total columns and set other columns’ flex-grow value accordinglyThe other method is to use min-width and max-width, as shown by this codepen. I don’t believe
max-width: 100%
is needed as anything larger than 100% will be changed to 100% due to flex-shrink, so really we’re dealing with min-width.The problem with this method is that we have to set min-width for all columns, or else flex-grow will take over and expand the column beyond the min-width we’ve set.
None of this is fun.
I poked around a bit and I found that you can have your cake and eat it too…
Xiao Zhuo Jia calls the Unholy Albatross. Check out the How does it work? part of the article to see the great CSS trickery. It has to do with using the max()
function and CSS custom properties with fullbacks. It still feels very in the spirit of the Holy Albatross and allows you to set the width (via --width
) on any given element with a pixel value or ratio. Plus, it supports gaps.
To Shared Link — Permalink on CSS-Tricks
Holy Albatross with Widths originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Flexbox is essentially for laying out items in a single dimension – in a row OR
…
Grid for layout, flexbox for components originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Flexbox is essentially for laying out items in a single dimension – in a row OR a column. Grid is for layout of items in two dimensions – rows AND columns.
Ahmad Shadeed wrote a post where he gives the same advice, but from a different angle. He argues we should use grid for layout and flexbox for components:
Remember that old layout method might be perfect for the job. Overusing flexbox or grid can increase the complexity of your CSS by time. I don’t mean they are complex, but using them correctly and in the right context as explained from the examples in this article is much better.
Speaking of which, there’s so many great layout examples in this post, too.
To Shared Link — Permalink on CSS-Tricks
Grid for layout, flexbox for components originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>gap
with flexbox, which means:
.flex-parent {
display: flex;
gap: 1rem;
}
.flex-child
…
Chromium lands Flexbox gap originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>gap
with flexbox, which means:
.flex-parent {
display: flex;
gap: 1rem;
}
.flex-child {
flex: 1;
}
That’s excellent, as putting space in between flex items has been tough in the past. We have justify-content: space-between
, which is nice sometimes, but that doesn’t allow you to explicitly tell the flex container how much space you want. For that, we’d typically use margin
, but that means avoiding setting the margin on the first or last element depending on the direction of the margin — which is annoying gets complicated.
We have gap
in CSS Grid and it’s way better. It’s great to have it in flexbox.
But it’s going to get weird for a minute. Safari doesn’t support it yet (nor stable Chrome) so we can’t just plop it out there and expect it to work with flexbox. But shouldn’t we be able to do an @supports
query these days?
/* Nope, sorry. This "works" but it doesn't
actually tell you if it works in *flexbox* or not.
This works in grid in most browsers now, so it will pass. */
@supports (gap: 1rem) {
.flex-parent {
gap: 1rem;
}
}
That got weird because grid-gap
was dropped in favor of just gap
. I’m sure grid-gap
will be supported forever because that’s generally how these things go, but we’re encouraged to use gap
instead. So, you might say gap is a little overloaded, but that should shake out over time (a year?). It’s complicated a smidge more by the fact that column-gap
is now going to gap
as well. gap
has a whole bunch of jobs.
I’d say I’m still in favor of the change, despite the overloading. Simpler mental modals are important for the long-term, and there isn’t exactly anything coming up to challenge CSS for styling in the browser. I’d bet my 2-year old daughter writes some CSS in her lifetime.
To Shared Link — Permalink on CSS-Tricks
Chromium lands Flexbox gap originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>…
Exciting Things on the Horizon For CSS Layout originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>gap
for display: flex
, which is great, and now Chrome is getting that too. To Shared Link — Permalink on CSS-Tricks
Exciting Things on the Horizon For CSS Layout originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>position: absolute;
but don’t apply any top/right/bottom/left properties, …
Flexbox and absolute positioning originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>position: absolute;
but don’t apply any top/right/bottom/left properties, then flexbox alignment will still apply to it.
It’s odd to see, but it makes a certain sort of sense as well. When you apply position: absolute;
to things (and nothing else), they kinda just stay where they are until you apply other positioning. Check out how this SVG icon just sits in the middle of this paragraph, and even flows with it on resize, because it doesn’t have any specific positioning instructions other than to not affect anything else.
To Shared Link — Permalink on CSS-Tricks
Flexbox and absolute positioning originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>Say you have a …
How Auto Margins Work in Flexbox originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>
Say you have a flex container with some flex items inside that don’t fill the whole area.
See the Pen
ZEYLVEX by Chris Coyier (@chriscoyier)
on CodePen.
Now I want to push that “Menu” item to the far right. That’s where auto margins come in. If I put a margin-left: auto;
on it, it’ll push as far away as it possibly can on that row.
See the Pen
WNbRLbG by Chris Coyier (@chriscoyier)
on CodePen.
Actually, you might consider margin-inline-start: auto;
instead and start using logical properties everywhere so that you’re all set should you need to change direction.
See the Pen
gObgZpb by Chris Coyier (@chriscoyier)
on CodePen.
Also, note that auto margins work in both directions as long as there is room to push. In this example, it’s not alignment that is moving the menu down, it’s an auto margin.
See the Pen
XWJpobE by Chris Coyier (@chriscoyier)
on CodePen.
How Auto Margins Work in Flexbox originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.
]]>