Need to lay out an element to the right or the left, such that text wraps around it? That’s an easy task for the 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.
Markup and layout
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.
Now for the floated 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.
Let’s Optimize The Markup
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.
Want more?
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.
That’s it!
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:
- Adjust the
shape-outside
property to define the shape - Apply some styling to the image to follow the previously defined shape or apply the same value to
mask
in case we are using the pseudo element version
Then everything holds it place, even in responsive designs.
Beautiful article. It makes me feel confident that I’ll never have to use
float
ever again.Great exercise of style and technique, indeed! But are we sure that a language should be so convoluted to achieve that? I think it should provide simpler way of doing that, not that sorcery!
This is actually a very simple way to achieve it :) it’s only 6 lines of CSS code. If you search around the same topic you will find more complex code that only “approximate” this (let’s not talk about the ones that use JS)
For some of the older readers, this article may have triggered some
float
PTSD.I cannot remember the last time I could guarantee the height of my images.
Any ideas on how to handle a responsive image?
I think your only way is to specify the height explicitely: https://jsfiddle.net/8aoryLju/ it will remain reponsive but with a fixed height
So in the first few examples, in which the
float
class has a width of 100%…what prevents the text above the image – and underneath the float – from getting “clobbered” and being un-selectable? Is it theshape-outside
attribute that does the trick? I just had two situations in which the invisible part of a float covered up form elements that could no longer be clicked.Sorry, I meant a
height
of 100%.Indeed, you are right. It’s about the last examples where I am using mask. In that case the stacking order will make the float element above the text preventing text selection (or any interaction).
I didn’t pay attention since it doesn’t happen in the other examples.
To avoid this simply add
pointer-events:none
to the float element.Thanks for the great example! The only thing I noticed it that the height:100%; on the outer element and on the .float elements includes the height of the image as well. When the content of the box is smaller in height than the image, this causes the element to have some “whitespace” at the bottom. You can see what I mean in this fiddle, where there is space between the button and the container edge: https://jsfiddle.net/s8jqmb6v/
If the height:100% wouldn’t include the image height, the image would push a bit more to the top so the h3 would wrap as well.
Hopefully you kind of get what I mean and have some pointers/a workaround for this?
Unfortunately, you hit one limitation of this technique. When the image is big enough compared to your content you will face such issue because the height calculation is a bit complex.
Sorry but I don’t know a good workaround for this case. Probably, will find one in the future.
Thanks! Spent many hours trying to achieve this until I finally found this page!!!
With that I was able to restructure my page within a few minutes to get it to work. Awesome!
Seems it is now possible to achieve the same result without need to wrap the image in .box by adding `height: 100%; object-fit: cover; object-position: bottom; on the img itself.
*object-fit: contain;
Great article! I found it via Stackoverflow. Before I fiddled around with Javascript but you are demonstrating that it can be done without if the height of the element to be floated is well known.
What about a situation where it is not? E. g. a figure element with some caption. Can it be done without JS either?
Actually there is a CSS-only solution that works even with Internet Explorer 10 and 11 too!
And you can use percentage widths for the image, if you know the image aspect ratios. Unfortunately percentage heights don’t work.
Just move your mouse left-right to resize the div (done because IE doesn’t support the
resize:
property):Unfortunately, it doesn’t work reliably. At certain widths, some words may be truncated – the wrapper element doesn’t increase its height as needed. This happens quite often in my project, but I have been able to reproduce it on pen from article. It can be reproduced on both Firefox and Chrome, and I think larger images are more likely to cause it. Just change the wrapper width slowly until the last word is wrapped to another line and there is a width where there is a line break but the wrapper is still at its previous height.