If you know anything about web accessibility, then you probably already know that all elements need to have a (or failing that, an aria-supplied label).
What you may not know is what to do when you have something like this:
The radio buttons have… two labels. How do you have two labels?
2 labels 1 input
Now you might be forgiven for thinking that the way to solve this problem is as simple as it sounds:
Unfortunately this doesn’t work. According to the spec, a may contain:
Phrasing content, but with no descendant labelable elements unless it is the element’s labeled control, and no descendant label elements.
You also can’t do this:
What do you have for sale?
Unfortunately, the for attribute only takes a single ID.
Say you want to have an image (or any other element) visually float left into a paragraph of text. But like… in the middle of the paragraph, not right at the top. It’s doable, but it’s certainly in the realm of CSS trickery!
One thing you can do is slap the image right in the middle of the paragraph:
Lorem ipsum dolor sit amet consectetur, adipisicing
elit. Similique quibusdam aliquam provident suscipit
corporis minima? Voluptatem temporibus nulla
But that’s mega awkward. Note the alt text. We can’t have random alt text in the middle of a sentence. It’s semantically blech and literally confusing for people using assistive technology.
So what we have to do is put the image before the paragraph.
Lorem ipsum dolor sit amet consectetur, adipisicing
elit. Similique quibusdam aliquam provident suscipit
corporis minima? Voluptatem temporibus nulla
But when we do that, we aren’t exactly floating the image in the middle of the paragraph anymore. It’s right at the top. No margin-top or vertical translate or anything is going to save us here. margin will just extend the height of the floated area and translate will push the image into the text.
The trick, at least one I’ve found, is to leverage shape-outside and a polygon() to re-shape the floated area around where you want it. You can skip the top-left part. Using Clippy is a great way to get a start to the polygon:
But instead of the clip-path Clippy gives you by default, you apply that value to shape-outside.
That should be enough if you are just placing a box in that place. But if it’s literally an image or needs a background color, you might also need to apply clip-path and perhaps transform things into place. This is where I ended up with some fiddling.
In this post, we’re going to use CSS superpowers to create a visual effect where two elements overlap and weave together. The epiphany for this design came during a short burst of spiritual inquisitiveness where I ended up at The Bible Project’s website. They make really cool animations, and I mean, really cool animations.
My attention, however, deviated from spiritualism to web design as I kept spotting these in-and-out border illustrations.
Screenshot form The Bible Project website.
I wondered if a similar could be made from pure CSS… and hallelujah, it’s possible!
The red frame is created using border. Its box-sizing is set to include the border size in the dimensions of the box so that the frame is centered around the picture after being rotated. Otherwise, the frame will be bigger than the image and get pulled towards the bottom-right corner.
Then we pick a pair of opposite corners of the image and overlay their quadrants with their corresponding portion in a copy of the same image as before. This hides the red frame in those corners.
We basically need to make a cut portion of the image that looks like below to go on top of the red frame.
The visible two quadrants will lay on top of the .rotated-border element.
So, how do we alter the image so that only two quadrants of the image are visible? CSS Blend Modes! The multiply value is what we’re going to reach for in this instance. This adds transparency to an element by stripping white from the image to reveal what’s behind the element.
Chris has a nice demo showing how a red background shows through an image with the multiply blend mode.
OK, nice, but what about those quadrants? We cover the quadrants we want to hide with white grid cells that will cause the image to bleed all the way through in those specific areas with a copy of the bird image right on top of it in the sourcecode.
.blend > * {
position: absolute;
height: 100%;
width: 100%;
}
/* Establishes our grid */
.grid {
display: grid;
grid: repeat(2, 1fr) / repeat(2, 1fr);
}
/* Adds white to quadrants with this attribute */
[data-white]{
background-color: white;
}
The result is a two-by-two grid with its top-right and bottom-left quadrants that are filled with white, while being grouped together with the image inside .blend.
To those of you new to CSS Grid, what we’re doing is adding a new .grid element that becomes a “grid” element when we declare display: grid;. Then we use the grid property (which is a shorthand that combinesgrid-template-columns and grid-template-rows) to create two equally spaced rows and columns. We’re basically saying, “Hey, grid, repeat two equal columns and repeat two equal rows inside of yourself to form four boxes.”
A copy of the image and a grid with white cells on top of the red border.
Now we apply the multiply blend mode to .blend using the mix-blend-mode property.
.blend { mix-blend-mode: multiply; }
The result:
As you can see, the blend mode affects all four quadrants rather than just the two we want to see through. That means we can see through all four quadrants, which reveals all of the red rotated box.
We want to bring back the white we lost in top-left and bottom-right quadrants so that they hide the red rotated box behind them. Let’s add a second grid, this time on top of .blend in the sourcecode.
The result!
Summing up, the browser renders the elements in our demo like this:
At bottommost is the bird image (represented by the leftmost grey shape in the diagram below)
Then a rotated red frame
On top of them is a grid with top-left and bottom-right white cells (corners where we don’t want to see the red frame in the final result)
Followed by a copy of the bird image from before and a grid with top-right and bottom-left white cells (corners where we do want to see the red frame) – both grouped together and given the blending mode, multiply.
You may have some questions about the approach I used in this post. Let me try to tackle those.
What about using CSS Masking instead of CSS Blend Modes?
For those of you familiar with CSS Masking – using either mask-image or clip-path – it can be an alternative to using blend mode.
I prefer blending because it has better browser support than masks and clipping. For instance, WebKit browsers don’t support SVG reference in the CSS mask-image property and they also provide partial support for clip-path values, especially Safari.
Another reason for choosing blend mode is the convenience of being able to use grid to create a simple white structure instead of needing to create images (whether they are SVG or otherwise).
Then again, I’m fully on board the CSS blend mode train, having used it for knockout text, text fragmentation effect… and now this. I’m pretty much all in on it.
Why did you use grid for the quadrants?
The white boxes needed in the demo can be created by other means, of course, but grid makes things easier for me. For example, we could’ve leaned on flexbox instead. Use what works for you.
Why use a data-attribute on the grid quadrant elements to make them white?
I used it while coding the demo without thinking much about it – I guess it was quicker to type. I later thought of changing it to a class, but left it as it is because the HTML looked neater that way… at least to me. 🙂
Is multiply the only blend mode that works for this example?
Nope. If you already know about blend modes then you probably also know you can use either screen, darken, or lighten to get a similar effect. (Both screen and lighten will need black grid cells instead of white.)
I say "was" because it's deprecated. It may still "work" (like everybody's favorite in some browsers), but it could stop working anytime, they say. The whole purpose of it was to display text in a monospace font, like the way Teletype machines used to.
Dave used it jokingly the other day.
Per recent events: As you can see by this official transcript, Dave Rupert LLC has done nothing wrong...
Client: This is the greatest call I've ever been on. Dave Rupert LLC: Definitely and we didn't even do anything illegal or quid pro quo'y.
Right here on CSS-Tricks. See, in my early days, I learned about that element and how its job is to set text as monospace. I thought, oh! like code! and then for years that's how I marked up code on this site. I had never heard of the element! When I did, I switched over to that. But I still haven't updated every single article from to . It lingers in articles like this:
I bring this up just because it's a funny little example of not knowing what you don't know. It's worth having a little sympathy for people early in their journey and just doing things that get the job done because that's all they know. We've all been there... and are always still there to some degree.
Let’s say you were gonna bounce an element all around a screen, sorta like an old school screensaver or Pong or something.
You’d probably be tracking the X location of the element, increasing or decreasing it in a time loop and — when the element reached the maximum or minimum value — it would reverse direction. Then do that same thing with the Y location and you’ve got the effect we’re after. Simple enough with some JavaScript and math.
Here’s The Coding Train explaining it clearly:
Here’s a canvas implementation. It’s Pong so it factors in paddles and is slightly more complicated, but the basic math is still there:
But what if we wanted to do this purely in CSS? We could write @keyframes that move the transform or left/top properties… but what values would we use? If we’re trying to bounce around the entire screen (viewport), we’d need to know the dimensions of the screen and then use those values. But we never know that exact size in CSS.
Or do we?
CSS has viewport units, which are based on the size of the entire viewport. Plus, we’ve got calc() and we presumably know the size of our own element.
The extra tricky part is breaking the X animation and the Y animation apart into two separate animations (one on a parent and one on a child) so that, when the direction reverses, it can happen independently and it looks more screensaver-like.
:root {
--width: 300px;
--x-speed: 13s;
--y-speed: 7s;
--transition-speed: 2.2s;
}
.el {
width: var(--width);
height: var(--width);
}
.x {
animation: x var(--x-speed) linear infinite alternate;
}
.y {
animation: y var(--y-speed) linear infinite alternate;
}
@keyframes x {
100% {
transform: translateX(calc(100vw - var(--width)));
}
}
@keyframes y {
100% {
transform: translateY(calc(100vh - var(--width)));
}
}
I stole that idea, and added some blobbiness and an extra element for this little demo: