Responsive CSS Patterns without Media Queries
Let me start by saying that despite the title, this article is not about doing away with media queries or media query bashing in any way. Media queries are incredibly useful and I use them all the time for all sorts of things. However, they don’t solve all our responsive design problems.
It’s often desirable to effect changes to an arrangement of elements based on the dimensions of their container, rather than the viewport. To solve this problem, the concept of element queries was born. However, element queries aren’t really a thing yet, and Mat Marquis has demonstrated some problems with the concept and reformulated them as container queries.
But these aren’t a thing yet either, unfortunately.
Hopefully they will be one day, but in the meantime, I’ve presented here a few tricks and techniques you can use to address some of the problems container queries will one day solve.
Flexbox with flex-wrap
Flex-wrap
can solve a multitude of problems when it comes to responding to container dimensions. For example, it’s common to want two elements to appear side-by-side if there’s enough space, but to stack one on top of the other if there isn’t. In the following demo we can see this behavior in action:
See the Pen Responsive module – flexbox by SitePoint (@SitePoint) on CodePen.
No fancy tricks here, just flexbox with flex-wrap
but it works a treat. Flexbox can be used for many more things than just creating two columns, of course, but I’ve kept things simple for this demo. The essential parts of this technique are simply:
<div class="container">
<div class="img">...</div>
<div class="content">...</div>
</div>
...
.container {
display: flex;
flex-wrap: wrap;
flex-direction: row;
}
.container .content,
.container .img {
flex: 1 0 12em;
/* Change 12em to your breakpoint. */
}
Understanding flex-grow
, flex-shrink
and flex-basis
is an important part of getting this right; I find this flexbox tip from Zoe Gillenwater really useful in understanding the relationship between these properties.
‘The Fab Four Technique’
The use of width
, min-width
, max-width
and calc
to create a breakpoint-based width-switch — dubbed ‘The Fab Four technique’ — was the brain-child of Rémi Parmentier. Originally created to help with responsive emails, it can easily be used for normal web pages and has really opened up some new possibilities in creating self-adapting modules, as demonstrated by Thierry. For example,
{
min-width: 50%;
width: calc((25em - 100%) * 1000);
max-width: 100%;
/* Change 25em to your breakpoint. */
}
This works because when width
is a percentage, it is a percentage of the element’s container width. The calc
function then compares this value to the desired breakpoint and then generates a really large positive number (if the width is less than the breakpoint), or a really large negative number (if the width is greater than the breakpoint), or zero if there’s an exact match. A large positive width is capped by max-width
and a large negative or zero width is set to the value of min-width
.
So, in the example above, we set a breakpoint of 25em
. This resolves to 400px
if the font-size is 16px
. If the container is 400px
or above (in other words equal-to or greater-than the breakpoint), the width resolves to 0 or a large negative number:
(400 - 400 = 0) * 1000 = 0
or (400 - 401 = -1) * 1000 = -1000
With values like these, min-width
kicks in so the resultant width of the element in the example above will be 50%
. However, if the container is 399px
or below (in other words smaller than the breakpoint), the width resolves to a large positive number:
(400 - 399 = 1) * 1000 = 1000
In this case max-width
kicks in and the resultant width is 100%
. The following diagram should help to visualize this process:
The next four demos all use this technique in different ways to switch the width of an element in response to the width of its container.
Floated Image – Full Width / Partial Width
In this demo I’ve used the ‘Fab Four Technique’ combined with float
to switch an image between full- and half-width depending on the width of the container:
See the Pen Responsive module – float by SitePoint (@SitePoint) on CodePen.
Similar to the flexbox
example above, this technique allows the elements to switch between a stacked arrangement at small widths, and floated/wrapped arrangement if there’s enough space.
Floated Image – Visible / Hidden
Adapted from the previous technique, I inverted the result of the calculation and removed the min-width
declaration to create an on/off switch. This is useful for hiding decorative elements in smaller containers where they may take up valuable space:
See the Pen Responsive module – float / hidden by SitePoint (@SitePoint) on CodePen.
And for the sake of clarity:
{
/* Removed min-width since we want the width to be zero at this point and negative widths are treated as zero */
/* Inverted the multiplier: */
width: calc((25em - 100%) * -1000);
max-width: 100%;
/* Change 25em to your breakpoint. */
}
Text and Image – Overlaid / Stacked
See the Pen Responsive module – overlaid / stacked by SitePoint (@SitePoint) on CodePen.
In a similar vein to the previous techniques, I’ve used an extra div
to pull the text up over the image, but where the image is too small and would otherwise be obscured by the text, the text snaps underneath instead. This part of the technique is a little complicated, so I’ll attempt to clarify it.
.pull {
/* Pull the text up over the image by this much: */
margin-bottom: -10em;
}
Here, the negative margin pulls subsequent content upwards so that it overlays the image. However, we need to turn this off when the container width crosses the breakpoint but since there’s no min/max-margin
property, we can’t use the ‘Fab Four Technique’ here to achieve this.
As luck would have it, though, if padding is given a percentage, it’s a percentage of the container’s width, and padding-top
and -bottom
will affect the height of an element. With this knowledge we can use calc
to create a padding-bottom value that switches between zero or ‘really large’ based on the container’s width:
padding-bottom: calc((30em - 100%) * 1000);
We can’t apply this to the .pull
div directly as there’s no min/max-padding
property to restrict these values; the solution is to put the padding switch on a pseudo-element to force the change in height and use max-height
on the .pull
element to restrict the height to the same value as the negative margin so that we effectively negate that margin.
.pull {
/* Pull the text up over the image by this much: */
margin-bottom: -10em;
/* Don't allow this container to be larger than the same amount: */
max-height: 10em;
/* and hide any overflow, just to be on the safe side: */
overflow: hidden;
}
.pull::before {
content: "";
display: block;
padding-bottom: calc((30em - 100%) * 1000);
/* Change 30em to your breakpoint */
}
The overlay gradient effect is achieved by applying an ‘on/off’ switch as described earlier to a pseudo-element to which the background gradient has been applied:
.image::after {
content: "";
display: block;
position: absolute;
left: 0;
top: 0;
/* Gradient to make the text more legible: */
background-image: linear-gradient(to bottom, rgba(0,20,30,0) 0%,rgba(0,20,30,0) 50%,rgba(0,20,30,1) 100%);
/* Extra .5% to prevent bleed due to rounding issues: */
height: 100.5%;
/* Toggle gradient overlay at the same breakpoint as the 'pull': */
width: calc((30em - 100%) * -1000);
/* Change 30em to your breakpoint */
max-width: 100%;
}
Truncating List
The final technique I developed was inspired by a version of the Priority Plus pattern on CSS tricks. Though not as sophisticated as that, this one doesn’t require any JavaScript:
See the Pen Truncating List by SitePoint (@SitePoint) on CodePen.
Again, this uses the ‘The Fab Four technique’, only this time based on the height of the container, rather than the width.
<div class="outer">
<div class="inner">
<div class="item">...</div>
...
<div class="control">...</div>
</div>
</div>
...
.outer {
height: 2.25em;
overflow: hidden;
}
.outer:target {
height: auto;
}
The outer container has a fixed height and hides any overflow unless the element is :target
-ed.
.inner {
display: flex;
flex-wrap: wrap;
}
The inner container is a flex container with flex-wrap
turned on, so it will increase in height as the elements wrap, but elements below the first line will be hidden by the .outer
container’s overflow:hidden
, creating the truncating effect.
.control {
height: calc((2.25em - 100%) * -1000);
max-height: 2.25em;
}
:target .control--open {
display: none;
}
:target .control--close {
display: block;
}
The ‘more/less‘ controls are only made visible if the height of the container exceeds the breakpoint (which is the same as the height of the main links), and the :target
state determines which control is visible.
Aligning Text Smartly in CSS
See the Pen Responsive Text Align by SitePoint (@SitePoint) on CodePen.
Aligning text centrally or on the left depending on the space available in the container compared to the length of the text is a really useful thing to be able to do. This technique was created by Vijay Sharma makes achieving this very easy.
Bonus: Flex-grow 9999 Hack
A great trick from Joren Van Hee that fits nicely into this collection is the flex-grow 9999 hack.
Praise: Look, No Media Queries by Vasilis van Gemert
Vasilis van Gemert’s talk Look, no media queries provided me with the impetus to investigate media query-less responsive design, which in turn led me to write this article. His talk is well worth a watch and includes some other ideas which, although really useful, didn’t quite fit into the theme of what I’ve presented here.
Conclusion
There are lots of things that can’t be done without element/container queries. Things like color values, font-size and line-height, borders and box-shadows, margins and paddings – the list is long. Adjusting all of these things in response to the state of a parent container should be possible with element/container queries, but, alas, there doesn’t appear to be any sign of those becoming a reality any time soon. However, I hope that in the meantime some of what I’ve presented here will be of use to you.
If you’d like to know what designing with Element Queries might be like, Writing Element Queries Today Using EQCSS will help you to get started.
If you want to know more about element/container queries here are some useful resources:
- EQCSS – A CSS Extension for Element Queries & More
- Why Element Queries Matter
- Container Queries: Once More Unto the Breach
- The Search For The Holy Grail: How I Ended Up With Element Queries, And How You Can Use Them Today
- Use Cases and Requirements for Element Queries Editor’s Draft
- Use cases for container queries
- How Style Scoping Works with Element Queries