Say you’re trying to pull off a design effect where the corner of an element are cut off. Maybe you’re a Battlestar Galactica fan? Or maybe you just like the unusual effect of it, since it avoids looking like a typical rectangle.
I suspect there are many ways to do it. Certainly, you could use multiple backgrounds to place images in the corners. You could just as well use a flexible SVG shape placed in the background. I bet there is also an exotic way to use gradients to pull it off.
But, I like the idea of simply taking some scissors and clipping off the dang corners. We essentially can do just that thanks to clip-path
. We can use the polygon()
function, provide it a list of X
and Y
coordinates and clip away what is outside of them.
Check out what happens if we list three points: middle top, bottom right, bottom left.
.module {
clip-path:
polygon(
50% 0,
100% 100%,
0 100%
);
}
Instead of just three points, let’s list all eight points needed for our notched corners. We could use pixels, but that would be dangerous. We probably don’t really know the pixel width or height of the element. Even if we did, it could change. So, here it is using percentages:
.module {
clip-path:
polygon(
0% 5%, /* top left */
5% 0%, /* top left */
95% 0%, /* top right */
100% 5%, /* top right */
100% 95%, /* bottom right */
95% 100%, /* bottom right */
5% 100%, /* bottom left */
0 95% /* bottom left */
);
}
That’s OK, but notice how the notches aren’t at perfect 45 degree angles. That’s because the element itself isn’t a square. That gets worse the less square the element is.
We can use the calc()
function to fix that. We’ll use percentages when we have to, but just subtract from a percentage to get the position and angle we need.
.module {
clip-path:
polygon(
0% 20px, /* top left */
20px 0%, /* top left */
calc(100% - 20px) 0%, /* top right */
100% 20px, /* top right */
100% calc(100% - 20px), /* bottom right */
calc(100% - 20px) 100%, /* bottom right */
20px 100%, /* bottom left */
0 calc(100% - 20px) /* bottom left */
);
}
And you know what? That number is repeated so many times that we may as well make it a variable. If we ever need to update the number later, then all it takes is changing it once instead of all those individual times.
.module {
--notchSize: 20px;
clip-path:
polygon(
0% var(--notchSize),
var(--notchSize) 0%,
calc(100% - var(--notchSize)) 0%,
100% var(--notchSize),
100% calc(100% - var(--notchSize)),
calc(100% - var(--notchSize)) 100%,
var(--notchSize) 100%,
0% calc(100% - var(--notchSize))
);
}
Ship it.
See the Pen Notched Boxes by Chris Coyier (@chriscoyier) on CodePen.
This may go without saying, but make sure you have enough padding to handle the clipping. If you wanna get really fancy, you might use CSS variables in your padding value as well, so the more you notch, the more padding there is.
But maybe don’t ship it right away? IE/Edge doesn’t appear to have support for clip-path in any version yet. https://caniuse.com/#search=clip-path
So then the corners won’t be notched, right? Oh well. Doesn’t mean don’t do it.
If you know the dimensions of the box or at least its aspect ratio, you can do it with a rotated pseudo-element behind the text content. I’ve used CSS variables to keep the code DRY, but you can go the full Sass route, not use CSS variables and then I believe support should go back all the way to IE9 (haven’t actually tested).
Coming back to the present day, if you want to speed up the implementation of features in Edge, please vote them on their Developer Feedback tracker because votes do matter. Here is the page for
clip-path
.Like the effect, but you will need prefixes for safari. (maybe turn on the “autoprefixer” in codepen)
Love it ! Thanks
Would anyone add an example using SVG clip-path for a wider browser support?
For everyone who wants only a top right corner:
Nice. A genuine CSS trick on CSS Tricks. :-)
Do you have a way to also use box shadow on this trick? Cause I would use that!
I swear I got it working with a
filter: drop-shadow()
while I was originally testing it, but can’t seem to figure it out now. If you really need the shadow, might be worth exploring other possibilities like an absolutely positioned and carefully flexible SVG element placed behind the content.Filters are applied before
clip-path
, so, in general, the solution is to set thedrop-shadow()
filter on the parent of the clipped element.In this particular case, I’d set the
background
andclip-path
on an absolutely positioned::before
pseudo that covers the entire box and then apply thefilter
on the actual box.Working demo. Things I’ve changed:
set
position: relative
and adrop-shadow()
filter on the boxadded an absolutely positioned
::before
covering its parent box (top: 0; right: 0; bottom: 0; left: 0;
) that I want to show up before the text content (z-index: -1
)moved the
background
and theclip-path
on this pseudo-elementI wrote about doing this with gradients 7 years ago. Works everywhere, doesn’t need that much code, and filter: drop-shadow() works just fine because you’re not clipping the shape. http://lea.verou.me/2011/03/beveled-corners-negative-border-radius-with-css3-gradients/
We are also working on adding this to CSS via a corner-shape property, but implementation interest has been slim.
That’s what
corner-shape: bevel
is for. (notch
is different.)What has happend to Lea Verou’s idea? It has landed in Level 4 of the spec https://drafts.csswg.org/css-backgrounds-4/#corner-shaping but AFAIS there’s no browser supporting this yet. Can I use doesn’t list it yet.
I’ve been trying to accomplish this with a transparent background and a border. I might have to stick with some version of an svg! Any ideas?
Using
clip-path
as in this post, you’d have to set a background on an absolutely positioned pseudo-element and cut out the inside part of it using this technique. This way, you’d be left with a “border”.Here is one-liner: https://codepen.io/anon/pen/yKMOqm
background: radial-gradient(ellipse, #f06d06 93.7%, rgba(0, 0, 0, 0) 94%);
Not a pixel perfect solution though.
A great ressources to shape your ideas and have fun with clip-path.
http://bennettfeely.com/clippy/
https://jsfiddle.net/frownonline/1hov6jp2/ ::before and ::after worked for me…