Tag: Borders

Fancy CSS Borders Using Masks

Have you ever tried to make CSS borders in a repeating zig-zag pattern? Like where a colored section of a website ends and another differently colored section begins — not with a straight line, but angled zig zags, rounded humps, or waves. There are a number of ways you could do this sort of CSS border, dating all the way back to using a background-image. But we can get more modern and programmatic with it. In this article, we’ll look at some modern CSS mask techniques to achieve the look.

Before we dig into the technical parts, though, let’s take a look at what we are building. I have made a CSS border generator where you can easily generate any kind of border within a few seconds and get the CSS code.

Did you see that? With the CSS mask property and a few CSS gradients, we get a responsive and cool-looking border — all with CSS by itself. Not only this, but such effect can be applied to any element where we can have any kind of coloration (e.g. image, gradient, etc). We get all this without extra elements, pseudo elements, or magic numbers coming from nowhere!

Oh great! All I have to do is to copy/paste code and it’s done!

True, but it’s good to understand the logic to be able to manually adjust the code if you need to.

Masking things

Since all our effects rely on the CSS mask property, let’s take a quick refresh on how it works. Straight from the spec:

The effect of applying a mask to a graphical object is as if the graphical object will be painted onto the background through a mask, thus completely or partially masking out parts of the graphical object.

If we check the formal syntax of the mask property we can see it accepts an <image> as a value, meaning either a URL of an image or a color gradient. Gradients are what we’ll be using here. Let’s start with basic examples:

In the first example of this demo, a gradient is used to make it appear as though the image is fading away. The second example, meanwhile, also uses a gradient, but rather than a soft transition between colors, a hard color stop is used to hide (or mask) half of the image. That second example illustrates the technique we will be using to create our fancy borders.

Oh, and the CSS mask property can take multiple gradients as long as they are comma-separated. That means we have even more control to mask additional parts of the image.

That example showing multiple masking gradients may look a bit tricky at first glance, but what’s happening is the same as applying the multiple gradients on the background property. But instead of using a color that blends in with the page background, we use a “transparent” black value (#0000) for the hidden parts of the image and full black (#000) for the visible parts.

That’s it! Now we can tackle our fancy borders.

Zig-Zag CSS borders

As we saw in the video at the start of this article, the generator can apply borders on one side, two sides, or all sides. Let’s start with the bottom side using a step-by-step illustration:

  1. We start by adding the first gradient layer with a solid color (red) at the top. A height that’s equal to calc(100% - 40px) is used to leave 40px of empty space at the bottom.
  2. We add a second gradient placed at the bottom that takes the remaining height of the container. There’s a little bit of geometry happening to make this work.
Diagram showing how the shape of a zig-zag is created in CSS. An upside down triangle in blue represents the shape and green areas to the left and right of it show the leftover space that is masked out with CSS.
  1. Next, we repeat the last gradient horizontally (replacing no-repeat with repeat-x). We can already see the zig-zag shape!
  2. Gradients are known to have anti-aliasing issues creating jagged edges (especially on Chrome). To avoid this, we add a slight transition between the colors, changing blue 90deg, green 0 to green, blue 1deg 89deg, green 90deg.
  3. Then we update the colors to have a uniform shape
  4. Last, we use everything inside the mask property!

We can extract two variables from those steps to define our shape: size (40px) and angle (90deg). Here’s how we can express that using placeholders for those variables. I will be using JavaScript to replace those variables with their final values.

mask:   linear-gradient(red 0 0) top/100% calc(100% - {size}) no-repeat,   conic-gradient(     from {-angle/2} at bottom,     #0000, #000 1deg {angle - 1} ,#0000 {angle}   ) bottom/{size*2*tan(angle/2)} {size} repeat-x;

We can use CSS custom properties for the size and the angle, but trigonometric functions are unsupported features at this moment. In the future, we’ll be able to do something like this:

--size: 40px; --angle: 90deg; mask:   linear-gradient(red 0 0) top/100% calc(100% - var(--size)) no-repeat,   conic-gradient(     from calc(var(--angle)/-2) at bottom,     #0000, #000 1deg calc(var(--angle) - 1deg), #0000 var(--angle)   ) bottom/calc(var(--size)*2*tan(var(--angle)/2)) var(--size) repeat-x;

Similar to the bottom border, the top one will have almost the same code with a few adjustments:

mask:   linear-gradient(red 0 0) bottom/100% calc(100% - {size}) no-repeat,   conic-gradient(     from {180deg - angle/2} at top,     #0000, #000 1deg {angle - 1}, #0000 {angle}   ) top/{size*2*tan(angle/2)} {size} repeat-x;

We changed bottom with top and top with bottom, then updated the rotation of the gradient to 180deg - angle/2 instead of -angle/2. As simple as that!

That’s the pattern we can use for the rest of the sides, like the left:

mask:   linear-gradient(red 0 0) right/calc(100% - {size}) 100% no-repeat,   conic-gradient(     from {90deg - angle/2} at left,     #0000, #000 1deg {angle - 1}, #0000 {angle}   ) left/{size} {size*2*tan(angle/2)} repeat-y;

…and the right:

mask:   linear-gradient(red 0 0) left/calc(100% - {size}) 100% no-repeat,   conic-gradient(     from {-90deg - angle/2} at right,     #0000, #000 1deg {angle - 1}, #0000 {angle}   ) right/{size} {size*2*tan(angle/2)} repeat-y;

Let’s make the borders for when they’re applied to two sides at once. We can actually reuse the same code. To get both the top and bottom borders, we simply combine the code of both the top and bottom border.

We use the conic-gradient() of the top side, the conic-gradient() of the bottom side plus a linear-gradient() to cover the middle area.

mask:   linear-gradient(#000 0 0) center/100% calc(100% - {2*size}) no-repeat,   conic-gradient(     from {-angle/2} at bottom,     #0000, #000 1deg {angle - 1},     #0000 {angle}   ) bottom/{size*2*tan(angle/2)} {size} repeat-x;   conic-gradient(     from {180deg - angle/2} at top,      #0000, #000 1deg {angle - 1}, #0000 {angle}   ) top/{size*2*tan(angle/2)} {size} repeat-x;

The same goes when applying borders to the left and right sides together:

mask:   linear-gradient(#000 0 0) center/calc(100% - {2*size}) 100% no-repeat,   conic-gradient(     from {90deg - angle/2} at left,     #0000, #000 1deg {angle - 1}, #0000 {angle}   ) left/{size} {size*2*tan(angle/2)} repeat-y,   conic-gradient(     from {-90deg - angle/2} at right,     #0000, #000 1deg {angle - 1}, #0000 {angle}   ) right/{size} {size*2*tan(angle/2)} repeat-y;

So, if we want to apply borders to all of the sides at once, we add all the gradients together, right?

Exactly! We have four conic gradients (one on each side) and one linear-gradient() in the middle. We set a fixed angle equal to 90deg because it the only one that results in nicer corners without weird overlapping. Note that I’m also using space instead of repeat-x or repeat-y to avoid bad result on corners like this:

Resizing a container with four sides configuration

Rounded CSS borders

Now let’s tackle rounded borders!

Oh no! another long explanation with a lot of calculation?!

Not at all! There is nothing to explain here. We take everything from the zig-zag example and update the conic-gradient() with a radial-gradient(). It’s even easier because we don’t have any angles to deal with — only the size variable.

Here is an illustration for one side to see how little we need to do to switch from the zig-zag border to the rounded border:

Again, all I did there was replace the conic-gradient() with this (using placeholders for size):

background:    radial-gradient(circle farthest-side, #0000 98%, #000)    50% calc(100% + {size})/{1.85*size} {2*size} repeat-x

And this for the second one:

background:   radial-gradient(circle farthest-side, #000 98%, #0000)    bottom/{1.85*size} {2*size} repeat-x

What is the logic behind the magic numbers 1.85 and 98%?

Logically, we should use 100% instead of 98% to have a circle that touches the edges of the background area; but again, it’s the anti-aliasing issue and those jagged edges. We use a slightly smaller value to prevent weird overlapping.

The 1.85 value is more of a personal preference than anything. I initially used 2 which is the logical value to get a perfect circle, but the result doesn’t look quite as nice, so the smaller value creates a more seamless overlap between the circles.

Here’s the difference:

Now we need to replicate this for the rest of the sides, just as we did with the zig-zag CSS border.

There is a small difference, however, when applying all four sides at once. You will notice that for one of the rounded borders, I used only one radial-gradient() instead of four. That makes sense since we can repeat a circular shape over all the sides using one gradient like illustrated below:

Here’s the final CSS:

mask:   linear-gradient(#000 0 0) center/calc(100% - {1.85*size}) calc(100% - {1.85*size}) no-repeat,   radial-gradient(farthest-side,#000 98%,#0000) 0 0/{2*size} {2*size} round;

Note how I’m using the round value instead of repeat. That’s to make sure we don’t cut off any of the circles. And, again, that 1.85 value is a personal preference value.

For the other type of rounded border, we still have to use four radial gradients, but I had to introduce the CSS clip-path property to correct an overlapping issue at the corners. You can see the difference between with and without clip-path in the following demo:

It’s an eight-point path to cut the corners:

clip-path: polygon(    {2*size} 0,calc(100% - {2*size}) 0,    100% {2*size},100% calc(100% - {2*size}),    calc(100% - {2*size}) 100%,{2*size} 100%,    0 calc(100% - {2*size}),0 {2*size} );

Wavy CSS borders

Both the zig-zag and rounded CSS borders needed one gradient to get the shapes we wanted. What about a wavy sort of border? That take two gradients. Below is an illustration to understand how we create one wave with two radial gradients.

Showing three diagrams of CSS borders, each with a piece of the border and an accompanying snippet of CSS to achieve the effect.It shows how one part cuts a circular white shape out of a red rectangle. The second part showing how to create a red circle shape. The third part shows two radial gradients used to position the two circles so they combine to create the wave shape.

We repeat that shape at the bottom plus a linear gradient at the top and we get the wavy border at the bottom side.

mask:    linear-gradient(#000 0 0) top/100% calc(100% - {2*size}) no-repeat,   radial-gradient(circle {size} at 75% 100%,#0000 98%,#000) 50% calc(100% - {size})/{4*size} {size} repeat-x,   radial-gradient(circle closest-side at 25% 50%,#000 99%,#0000 101%) bottom/{4*size} {2*size} repeat-x;

We do the same process for the other sides as we did with the zig-zag and rounded CSS borders. All we need is to update a few variables to have a different wave for each side.

Showing part of the CSS for each side. You can find the full code over at the generator.

What about applying a wavy CSS border on all four sides? Will we have 9 gradients in total??”

Nope, and that’s because there is no demo where a wavy border is applied to all four sides. I was unable to find a combination of gradients that gives a good result on the corners. Maybe someone reading this knows a good approach? 😉

That’s borderline great stuff!

So, you know the ins and outs of my cool little online CSS border generator! Sure, you can use the code it spits out and do just fine — but now you have the secret sauce recipe that makes it work.

Specifically, we saw how gradients can be used to mask portions of an element. Then we went to work on multiple gradients to make certain shapes from those gradient CSS masks. And the result is a pattern that can be used along the edges of an element, creating the appearance of fancy borders that you might otherwise result to background-image for. Only this way, all it takes is swapping out some values to change the appearance rather than replace an entire raster image file or something.


Fancy CSS Borders Using Masks originally published on CSS-Tricks. You should get the newsletter and become a supporter.

CSS-Tricks

, , ,

How to Add Text in Borders Using Basic HTML Elements

Some HTML elements come with preset designs, like the inconveniently small squares of <input type="checkbox"> elements, the limited-color bars of <meter> elements, and the “something about them bothers me” arrows of the <details> elements. We can style them to match the modern aesthetics of our websites while making use of their functionalities. There are also many elements that rarely get used as both their default appearance and functionality are less needed in modern web designs.

One such HTML element is <fieldset>, along with its child element <legend>.

A <fieldset> element is traditionally used to group and access form controls. We can visually notice the grouping by the presence of a border around the grouped content on the screen. The caption for this group is given inside the <legend> element that’s added as the first child of the <fieldset>.

This combination of <fieldset> and <legend> creates a unique ready-made “text in border” design where the caption is placed right where the border is and the line of the border doesn’t go through the text. The border line “breaks” when it encounters the beginning of the caption text and resumes after the text ends.

In this post, we’ll make use of the <fieldset> and <legend> combo to create a more modern border text design that’s quick and easy to code and update.

For the four borders, we need four <fieldset> elements, each containing a <legend> element inside. We add the text that will appear at the borders inside the <legend> elements.

<fieldset><legend>Wash Your Hands</legend></fieldset> <fieldset><legend>Stay Apart</legend></fieldset> <fieldset><legend>Wear A Mask</legend></fieldset> <fieldset><legend>Stay Home</legend></fieldset>

To begin, we stack the <fieldset> elements on top of each other in a grid cell and give them borders. You can stack them using any way you want — it doesn’t necessarily have to be a grid.

Only the top border of each <fieldset> element is kept visible while the remaining edges are transparent since the text of the <legend> element appears at the top border of the <fieldset> by default.

Also, we give all the <fieldset> elements a box-sizing property with a value of border-box so the width and height of the <fieldset> elements include their border and padding sizes too. Doing this later creates a leveled design, when we style the <legend> elements.

body {   display: grid;    margin: auto; /* to center */   margin-top: calc(50vh - 170px); /* to center */   width: 300px; height: 300px;  }  fieldset {   border: 10px solid transparent;    border-top-color: black;    box-sizing: border-box;    grid-area: 1 / 1; /* first row, first column */   padding: 20px;    width: inherit;  }

After this, we rotate the last three <fieldset> elements in order to use their top borders as the side and bottom borders of our design.

/* rotate to right */ fieldset:nth-of-type(2){ transform: rotate(90deg); } /* rotate to bottom */ fieldset:nth-of-type(3){ transform: rotate(180deg); } /* rotate to left */ fieldset:nth-of-type(4){ transform: rotate(-90deg); }

Next up is styling the <legend> elements. The key to create smooth border text using a <legend> element is to give it a zero (or small enough) line-height. If it has a large line height, that will displace the position of the border it’s in, pushing the border down. And when the border moves with the line height, we won’t be able to connect all the four sides of our design and will need to readjust the borders.

legend {   font: 15pt/0 'Averia Serif Libre';    margin: auto; /* to center */   padding: 0 4px;  }  fieldset:nth-of-type(3) > legend {    transform: rotate(180deg); }

I used the font shorthand property to give the values for the font-size, line-height and font-family properties of the <legend> elements.

The <legend> element that adds the text at the bottom border of our design, fieldset:nth-of-type(3)>legend, is upside-down because of its rotated <fieldset> parent element. Flip that <legend> element vertically to show its text right-side-up.

Add an image to the first <fieldset> element and you get something like this:

Lateral margins can move the text along the border. Left and right margins with auto values will center the text, as seen in the above Pen. Only the left margin with an auto value will flush the text to the right, and vice versa, for the right margin.

Bonus: After a brief geometrical detour, here’s an octagonal design I made using the same technique:


The post How to Add Text in Borders Using Basic HTML Elements appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , , ,
[Top]

More Control Over CSS Borders With background-image

You can make a typical CSS border dashed or dotted. For example:

.box {    border: 1px dashed black;    border: 3px dotted red; }

You don’t have all that much control over how big or long the dashes or gaps are. And you certainly can’t give the dashes slants, fading, or animation! You can do those things with some trickery though.

Amit Sheen build this really neat Dashed Border Generator:

The trick is using four multiple backgrounds. The background property takes comma-separated values, so by setting four backgrounds (one along the top, right, bottom, and left) and sizing them to look like a border, it unlocks all this control.

So like:

.box {   background-image: repeating-linear-gradient(0deg, #333333, #333333 10px, transparent 10px, transparent 20px, #333333 20px), repeating-linear-gradient(90deg, #333333, #333333 10px, transparent 10px, transparent 20px, #333333 20px), repeating-linear-gradient(180deg, #333333, #333333 10px, transparent 10px, transparent 20px, #333333 20px), repeating-linear-gradient(270deg, #333333, #333333 10px, transparent 10px, transparent 20px, #333333 20px);   background-size: 3px 100%, 100% 3px, 3px 100% , 100% 3px;   background-position: 0 0, 0 0, 100% 0, 0 100%;   background-repeat: no-repeat; }

I like gumdrops.


The post More Control Over CSS Borders With background-image appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

CSS-Tricks

, , , ,
[Top]

Blurred Borders in CSS

Say we want to target an element and just visually blur the border of it. There is no simple, single built-in web platform feature we can reach for. But we can get it done with a little CSS trickery.

Here’s what we’re after:

Screenshot of an element with a background image that shows oranges on a wooden table. The border of this element is blurred.
The desired result.

Let’s see how we can code this effect, how we can enhance it with rounded corners, extend support so it works cross-browser, what the future will bring in this department and what other interesting results we can get starting from the same idea!

Coding the basic blurred border

We start with an element on which we set some dummy dimensions, a partially transparent (just slightly visible) border and a background whose size is relative to the border-box, but whose visibility we restrict to the padding-box:

$ b: 1.5em; // border-width  div {   border: solid $ b rgba(#000, .2);   height: 50vmin;   max-width: 13em;   max-height: 7em;   background: url(oranges.jpg) 50%/ cover                  border-box /* background-origin */                 padding-box /* background-clip */; }

The box specified by background-origin is the box whose top left corner is the 0 0 point for background-position and also the box that background-size (set to cover in our case) is relative to. The box specified by background-clip is the box within whose limits the background is visible.

The initial values are padding-box for background-origin and border-box for background-clip, so we need to specify them both in this case.

If you need a more in-depth refresher on background-origin and background-clip, you can check out this detailed article on the topic.

The code above gives us the following result:

See the Pen by thebabydino (@thebabydino) on CodePen.

Next, we add an absolutely positioned pseudo-element that covers its entire parent’s border-box and is positioned behind (z-index: -1). We also make this pseudo-element inherit its parent’s border and background, then we change the border-color to transparent and the background-clip to border-box:

$ b: 1.5em; // border-width  div {   position: relative;   /* same styles as before */      &:before {     position: absolute;     z-index: -1;     /* go outside padding-box by       * a border-width ($ b) in every direction */     top: -$ b; right: -$ b; bottom: -$ b; left: -$ b;     border: inherit;     border-color: transparent;     background: inherit;     background-clip: border-box;     content: ''   } }

Now we can also see the background behind the barely visible border:

See the Pen by thebabydino (@thebabydino) on CodePen.

Alright, you may be seeing already where this is going! The next step is to blur() the pseudo-element. Since this pseudo-element is only visible only underneath the partially transparent border (the rest is covered by its parent’s padding-box-restricted background), it results the border area is the only area of the image we see blurred.

See the Pen by thebabydino (@thebabydino) on CodePen.

We’ve also brought the alpha of the element’s border-color down to .03 because we want the blurriness to be doing most of the job of highlighting where the border is.

This may look done, but there’s something I still don’t like: the edges of the pseudo-element are now blurred as well. So let’s fix that!

One convenient thing when it comes to the order browsers apply properties in is that filters are applied before clipping. While this is not what we want and makes us resort to inconvenient workarounds in a lot of other cases… right here, it proves to be really useful!

It means that, after blurring the pseudo-element, we can clip it to its border-box!

My preferred way of doing this is by setting clip-path to inset(0) because… it’s the simplest way of doing it, really! polygon(0 0, 100% 0, 100% 100%, 0 100%) would be overkill.

See the Pen by thebabydino (@thebabydino) on CodePen.

In case you’re wondering why not set the clip-path on the actual element instead of setting it on the :before pseudo-element, this is because setting clip-path on the element would make it a stacking context. This would force all its child elements (and consequently, its blurred :before pseudo-element as well) to be contained within it and, therefore, in front of its background. And then no nuclear z-index or !important could change that.

We can prettify this by adding some text with a nicer font, a box-shadow and some layout properties.

What if we have rounded corners?

The best thing about using inset() instead of polygon() for the clip-path is that inset() can also accommodate for any border-radius we may want!

And when I say any border-radius, I mean it! Check this out!

div {   --r: 15% 75px 35vh 13vw/ 3em 5rem 29vmin 12.5vmax;   border-radius: var(--r);   /* same styles as before */      &:before {     /* same styles as before */     border-radius: inherit;     clip-path: inset(0 round var(--r));   } }

It works like a charm!

See the Pen by thebabydino (@thebabydino) on CodePen.

Extending support

Some mobile browsers still need the -webkit- prefix for both filter and clip-path, so be sure to include those versions too. Note that they are included in the CodePen demos embeded here, even though I chose to skip them in the code presented in the body of this article.

Alright, but what if we need to support Edge? clip-path doesn’t work in Edge, but filter does, which means we do get the blurred border, but no sharp cut limits.

Well, if we don’t need corner rounding, we can use the deprecated clip property as a fallback. This means adding the following line right before the clip-path ones:

clip: rect(0 100% 100% 0)

And our demo now works in Edge… sort of! The right, bottom and left edges are cut sharply, but the top one still remains blurred (only in the Debug mode of the Pen, all seems fine for the iframe in the Editor View). And opening DevTools or right clicking in the Edge window or clicking anywhere outside this window makes the effect of this property vanish. Bug of the month right there!

Alright, since this is so unreliable and it doesn’t even help us if we want rounded corners, let’s try another approach!

This is a bit like scratching behind the left ear with the right foot (or the other way around, depending on which side is your more flexible one), but it’s the only way I can think of to make it work in Edge.

Some of you may have already been screaming at the screen something like “but Ana… overflow: hidden!” and yes, that’s what we’re going for now. I’ve avoided it initially because of the way it works: it cuts out all descendant content outside the padding-box. Not outside the border-box, as we’ve done by clipping!

This means we need to ditch the real border and emulate it with padding, which I’m not exactly delighted about because it can lead to more complications, but let’s take it one step at a time!

As far as code changes are concerned, the first thing we do is remove all border-related properties and set the border-width value as the padding. We then set overflow: hidden and restrict the background of the actual element to the content-box. Finally, we reset the pseudo-element’s background-clip to the padding-box value and zero its offsets.

$ fake-b: 1.5em; // fake border-width  div {   /* same styles as before */   overflow: hidden;   padding: $ fake-b;   background: url(oranges.jpg) 50%/ cover                  padding-box /* background-origin */                 content-box /* background-clip */;      &:before {     /* same styles as before */     top: 0; right: 0; bottom: 0; left: 0;     background: inherit;     background-clip: padding-box;   } }

See the Pen by thebabydino (@thebabydino) on CodePen.

If we want that barely visible “border” overlay, we need another background layer on the actual element:

$ fake-b: 1.5em; // fake border-width $ c: rgba(#000, .03);  div {   /* same styles as before */   overflow: hidden;   padding: $ fake-b;   --img: url(oranges.jpg) 50%/ cover;   background: var(--img)                 padding-box /* background-origin */                 content-box /* background-clip */,                 linear-gradient($ c, $ c);      &:before {     /* same styles as before */     top: 0; right: 0; bottom: 0; left: 0;     background: var(--img);   } }

See the Pen by thebabydino (@thebabydino) on CodePen.

We can also add rounded corners with no hassle:

See the Pen by thebabydino (@thebabydino) on CodePen.

So why didn’t we do this from the very beginning?!

Remember when I said a bit earlier that not using an actual border can complicate things later on?

Well, let’s say we want to have some text. With the first method, using an actual border and clip-path, all it takes to prevent the text content from touching the blurred border is adding a padding (of let’s say 1em) on our element.

See the Pen by thebabydino (@thebabydino) on CodePen.

But with the overflow: hidden method, we’ve already used the padding property to create the blurred “border”. Increasing its value doesn’t help because it only increases the fake border’s width.

We could add the text into a child element. Or we could also use the :after pseudo-element!

The way this works is pretty similar to the first method, with the :after replacing the actual element. The difference is we clip the blurred edges with overflow: hidden instead of clip-path: inset(0) and the padding on the actual element is the pseudos’ border-width ($ b) plus whatever padding value we want:

$ b: 1.5em; // border-width  div {   overflow: hidden;   position: relative;   padding: calc(1em + #{$ b});   /* prettifying styles */ 	   &:before, &:after {     position: absolute;     z-index: -1; /* put them *behind* parent */     /* zero all offsets */     top: 0; right: 0; bottom: 0; left: 0;     border: solid $ b rgba(#000, .03);     background: url(oranges.jpg) 50%/ cover                    border-box /* background-origin */                   padding-box /* background-clip */;     content: ''   } 	   &:before {     border-color: transparent;     background-clip: border-box;     filter: blur(9px);   } }

See the Pen by thebabydino (@thebabydino) on CodePen.

What about having both text and some pretty extreme rounded corners? Well, that’s something we’ll discuss in another article – stay tuned!

What about backdrop-filter?

Some of you may be wondering (as I was when I started toying with various ideas in order to try to achieve this effect) whether backdrop-filter isn’t an option.

Well, yes and no!

Technically, it is possible to get the same effect, but since Firefox doesn’t yet implement it, we’re cutting out Firefox support if we choose to take this route. Not to mention this approach also forces us to use both pseudo-elements if we want the best support possible for the case when our element has some text content (which means we need the pseudos and their padding-box area background to show underneath this text).

For those who don’t yet know what backdrop-filter does: it filters out what can be seen through the (partially) transparent parts of the element we apply it on.

The way we need to go about this is the following: both pseudo-elements have a transparent border and a background positioned and sized relative to the padding-box. We restrict the background of pseudo-element on top (the :after) to the padding-box.

Now the :after doesn’t have a background in the border area anymore and we can see through to the :before pseudo-element behind it there. We set a backdrop-filter on the :after and maybe even change that border-color from transparent to slightly visible. The bottom (:before) pseudo-element’s background that’s still visible through the (partially) transparent, barely distinguishable border of the :after above gets blurred as a result of applying the backdrop-filter.

$ b: 1.5em; // border-width  div {   overflow: hidden;   position: relative;   padding: calc(1em + #{$ b});   /* prettifying styles */ 	   &:before, &:after {     position: absolute;     z-index: -1; /* put them *behind* parent */     /* zero all offsets */     top: 0; right: 0; bottom: 0; left: 0;     border: solid $ b transparent;     background: $ url 50%/ cover                    /* background-origin & -clip */                   border-box;     content: ''   } 	   &:after {     border-color: rgba(#000, .03);     background-clip: padding-box;     backdrop-filter: blur(9px); /* no Firefox support */   } }

Remember that the live demo for this doesn’t currently work in Firefox and needs the Experimental Web Platform features flag enabled in chrome://flags in order to work in Chrome.

Eliminating one pseudo-element

This is something I wouldn’t recommend doing in the wild because it cuts out Edge support as well, but we do have a way of achieving the result we want with just one pseudo-element.

We start by setting the image background on the element (we don’t really need to explicitly set a border as long as we include its width in the padding) and then a partially transparent, barely visible background on the absolutely positioned pseudo-element that’s covering its entire parent. We also set the backdrop-filter on this pseudo-element.

$ b: 1.5em; // border-width  div {   position: relative;   padding: calc(1em + #{$ b});   background: url(oranges.jpg) 50%/ cover;   /* prettifying styles */ 	   &:before {     position: absolute;     /* zero all offsets */     top: 0; right: 0; bottom: 0; left: 0;     background: rgba(#000, .03);     backdrop-filter: blur(9px); /* no Firefox support */     content: ''   } }

Alright, but this blurs out the entire element behind the almost transparent pseudo-element, including its text. And it’s no bug, this is what backdrop-filter is supposed to do.

Screenshot.
The problem at hand.

In order to fix this, we need to get rid of (not make transparent, that’s completely useless in this case) the inner rectangle (whose edges are a distance $ b away from the border-box edges) of the pseudo-element.

We have two ways of doing this.

The first way (live demo) is with clip-path and the zero-width tunnel technique:

$ b: 1.5em; // border-width $ o: calc(100% - #{$ b});  div {   /* same styles as before */ 	   &:before {     /* same styles as before */      /* doesn't work in Edge */     clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%,                         0 0,                         #{$ b $ b}, #{$ b $ o}, #{$ o $ o}, #{$ o $ b},                         #{$ b $ b});   } }

The second way (live demo) is with two composited mask layers (note that, in this case, we need to explicitly set a border on our pseudo):

$ b: 1.5em; // border-width  div {   /* same styles as before */ 	   &:before {     /* same styles as before */      border: solid $ b transparent;      /* doesn't work in Edge */     --fill: linear-gradient(red, red);     -webkit-mask: var(--fill) padding-box,                    var(--fill);     -webkit-mask-composite: xor;             mask: var(--fill) padding-box exclude,                    var(--fill);   } }

Since neither of these two properties works in Edge, this means support is now limited to WebKit browsers (and we still need to enable the Experimental Web Platform features flag for backdrop-filter to work in Chrome).

Future (and better!) solution

This is not implemented by any browser at this point, but the spec mentions a filter() function that will allow us to apply filters on individual background layers. This would eliminate the need for a pseudo-element and would reduce the code needed to achieve this effect to two CSS declarations!

border: solid 1.5em rgba(#000, .03); background: $ url                border-box /* background-origin */               padding-box /* background-clip */,              filter($ url, blur(9px))                /* background-origin & background-clip */               border-box

If you think this is something useful to have, you can add your use cases and track implementation progress for both Chrome and Firefox.

More border filter options

I’ve only talked about blurring the border up to now, but this technique works for pretty much any CSS filter (save for drop-shadow() which wouldn’t make much sense in this context). You can play with switching between them and tweaking values in the interactive demo below:

See the Pen by thebabydino (@thebabydino) on CodePen.

And all we’ve done so far has used just one filter function, but we can also chain them and then the possibilities are endless – what cool effects can you come up with this way?

See the Pen by thebabydino (@thebabydino) on CodePen.

The post Blurred Borders in CSS appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]

Stacked “Borders”

A little while back, I was in the process of adding focus styles to An Event Apart’s web site. Part of that was applying different focus effects in different areas of the design, like white rings in the header and footer and orange rings in the main text. But in one place, I wanted rings that were more obvious—something like stacking two borders on top of each other, in order to create unusual shapes that would catch the eye.

A row of four images, the second of which includes a dashed red border.

I toyed with the idea of nesting elements with borders and some negative margins to pull one border on top of another, or nesting a border inside an outline and then using negative margins to keep from throwing off the layout. But none of that felt satisfying.

It turns out there are a number of tricks to create the effect of stacking one border atop another by combining a border with some other CSS effects, or even without actually requiring the use of any borders at all. Let’s explore, shall we?

Outline and box-shadow

If the thing to be multi-bordered is a rectangle—you know, like pretty much all block elements—then mixing an outline and a spread-out hard box shadow may be just the thing.

Let’s start with the box shadow. You’re probably used to box shadows like this:

.drop-me {   background: #AEA;   box-shadow: 10px 12px 0.5rem rgba(0,0,0,0.5); }
A turquoise box containing the words div text and a heavy box shadow.

That gets you a blurred shadow below and to the right of the element. Drop shadows, so last millennium! But there’s room, and support, for a fourth length value in box-shadow that defines a spread distance. This increases the size of the shadow’s shape in all directions by the given length, and then it’s blurred. Assuming there’s a blur, that is.

So if we give a box shadow no offset, no blur, and a bit of spread, it will draw itself all around the element, looking like a solid border without actually being a border.

.boxborder-me {   box-shadow: 0 0 0 5px firebrick; }
A red box containing a thick red border.

This box-shadow “border” is being drawn just outside the outer border edge of the element. That’s the same place outlines get drawn around block boxes, so all we have to do now is draw an outline over the shadow. Something like this:

.boxborder-me {   box-shadow: 0 0 0 5px firebrick;   outline: dashed 5px darkturquoise; }
A box containing a dashed red border with a turquoise background filling the dash gaps.

Bingo. A multicolor “border” that, in this case, doesn’t even throw off layout size, because shadows and outlines are drawn after element size is computed. The outline, which sits on top, can use pretty much any outline style, which is the same as the list of border styles. Thus, dotted and double outlines are possibilities. (So are all the other styles, but they don’t have any transparent parts, so the solid shadow could only be seen through translucent colors.)

If you want a three-tone effect in the border, multiple box shadows can be created using a comma-separated list, and then an outline put over top that. For example:

.boxborder-me {   box-shadow: 0 0 0 1px darkturquoise,               0 0 0 3px firebrick,               0 0 0 5px orange,               0 0 0 6px darkturquoise;   outline: dashed 6px darkturquoise; }
A box containing a dashed border where the dashes are doubled with red and gold and a turquoise background filling in the dash gaps.

Taking it back to simpler effects, combining a dashed outline over a spread box shadow with a solid border of the same color as the box shadow creates yet another effect:

.boxborder-me {   box-shadow: 0 0 0 5px firebrick;   outline: dashed 5px darkturquoise;   border: solid 5px darkturquoise; }
A box with a dashed red border and a turquoise background that not only fills the dash gaps, but overflows the red border toward the inside edge of the box.

The extra bonus here is that even though a box shadow is being used, it doesn’t fill in the element’s background, so you can see the backdrop through it. This is how box shadows always behave: they are only drawn outside the outer border edge. The “rest of the shadow,” the part you may assume is always behind the element, doesn’t exist. It’s never drawn. So you get results like this:

A box with a turquoise border and heavy box shadow toward the bottom right edge that is set against a turquoise background.

This is the result of explicit language in the CSS Background and Borders Module, Level 3, section 7.1.1:

An outer box-shadow casts a shadow as if the border-box of the element were opaque. Assuming a spread distance of zero, its perimeter has the exact same size and shape as the border box. The shadow is drawn outside the border edge only: it is clipped inside the border-box of the element.

(Emphasis added.)

Border and box-shadow

Speaking of borders, maybe there’s a way to combine borders and box shadows. After all, box shadows can be more than just drop shadows. They can also be inset. So what if we turned the previous shadow inward, and dropped a border over top of it?

.boxborder-me {   box-shadow: 0 0 0 5px firebrick inset;   border: dashed 5px darkturquoise; }
A box with a dashed turquoise border and a solid red border that lies on the inside of the dashed border.

That’s… not what we were after. But this is how inset shadows work: they are drawn inside the outer padding edge (also known as the inner border edge), and clipped beyond that:

An inner box-shadow casts a shadow as if everything outside the padding edge were opaque. Assuming a spread distance of zero, its perimeter has the exact same size and shape as the padding box. The shadow is drawn inside the padding edge only: it is clipped outside the padding box of the element.

(Ibid; emphasis added.)

So we can’t stack a border on top of an inset box-shadow. Maybe we could stack a border on top of something else…?

Border and multiple backgrounds

Inset shadows may be restricted to the outer padding edge, but backgrounds are not. An element’s background will, by default, fill the area out to the outer border edge. Fill an element background with solid color, give it a thick dashed border, and you’ll see the background color between the visible pieces of the border.

So what if we stack some backgrounds on top of each other, and thus draw the solid color we want behind the border? Here’s step one:

.multibg-me {   border: 5px dashed firebrick;   background:     linear-gradient(to right, darkturquoise, 5px, transparent 5px);   background-origin: border-box; }
A box with a dashed red border and a turquoise background filling in the dash gaps along the left edge of the box.

We can see, there on the left side, the blue background visible through the transparent parts of the dashed red border. Add three more like that, one for each edge of the element box, and:

.multibg-me {   border: 5px dashed firebrick;   background:     linear-gradient(to top, darkturquoise, 5px, transparent 5px),     linear-gradient(to right, darkturquoise, 5px, transparent 5px),     linear-gradient(to bottom, darkturquoise, 5px, transparent 5px),     linear-gradient(to left, darkturquoise, 5px, transparent 5px);   background-origin: border-box; }
A box with a dashed red border and a turquoise background that fills in the dash gaps.

In each case, the background gradient runs for five pixels as a solid dark turquoise background, and then has a color stop which transitions instantly to transparent. This lets the “backdrop” show through the element while still giving us a “stacked border.”

One major advantage here is that we aren’t limited to solid linear gradients—we can use any gradient of any complexity, just to spice things up a bit. Take this example, where the dashed border has been made mostly transparent so we can see the four different gradients in their entirety:

.multibg-me {   border: 15px dashed rgba(128,0,0,0.1);   background:     linear-gradient(to top,    darkturquoise, red 15px, transparent 15px),     linear-gradient(to right,  darkturquoise, red 15px, transparent 15px),     linear-gradient(to bottom, darkturquoise, red 15px, transparent 15px),     linear-gradient(to left,   darkturquoise, red 15px, transparent 15px);   background-origin: border-box; }
A diagram showing the same box with a dashed red border and turquoise background, but with transparency to show how the stacked borders overlap.

If you look at the corners, you’ll see that the background gradients are rectangular, and overlap each other. They don’t meet up neatly, the way border corners do. This can be a problem if your border has transparent parts in the corners, as would be the case with border-style: double.
Also, if you just want a solid color behind the border, this is a fairly clumsy way to stitch together that effect. Surely there must be a better approach?

Border and background clipping

Yes, there is! It involves changing the clipping boxes for two different layers of the element’s background. The first thing that might spring to mind is something like this:

.multibg-me {   border: 5px dashed firebrick;   background: #EEE, darkturquoise;   background-clip: padding-box, border-box; }

But that does not work, because CSS requires that only the last (and thus lowest) background be set to a <color> value. Any other background layer must be an image.

So we replace that very-light-gray background color with a gradient from that color to that color: this works because gradients are images. In other words:

.multibg-me {   border: 5px dashed firebrickred;   background: linear-gradient(to top, #EEE, #EEE), darkturquoise;   background-clip: padding-box, border-box; }
A box with a red dashed border, a turquoise background to fill in the dash gaps, and a light gray background set inside the box.

The light gray “gradient” fills the entire background area, but is clipped to the padding box using background-clip. The dark turquoise fills the entire area and is clipped to the border box, as backgrounds always have been by default. We can alter the gradient colors and direction to anything we like, creating an actual visible gradient or shifting it to all-white or whatever other linear effect we would like.

The downside here is that there’s no way to make that padding-area background transparent such that the element’s backdrop can be seen through the element. If the linear gradient is made transparent, then the whole element background will be filled with dark turquoise. Or, more precisely, we’ll be able to see the dark turquoise that was always there.

In a lot of cases, it won’t matter that the element background isn‘t see-through, but it’s still a frustrating limitation. Isn’t there any way to get the effect of stacked borders without wacky hacks and lost capabilities?

Border images

In fact, what if we could take an image of the stacked border we want to see in the world, slice it up, and use that as the border? Like, say, this image becomes this border?

An image of two stacked boxes, the top a square box with a red dashed border and a turquoise and gold striped background filling in the dash gaps. The bottom box is a demonstration using the top box as a border image for the bottom box.

Here’s the code to do exactly that:

.borderimage-me {   border: solid 5px;   border-image: url(triple-stack-border.gif) 15 / 15px round; }

First, we set a solid border with some width. We could also set a color for fallback purposes, but it’s not really necessary. Then we point to an image URL, define the slice inset(s) at 15 and width of the border to be 15px, and finally the repeat pattern of round.

There are more options for border images, which are a little too complex to get into here, but the upshot is that you can take an image, define nine slices of it using offset values, and have those images used to synthesize a complete border around an image. That’s done by defining offsets from the edges of the image itself, which in this case is 15. Since the image is a GIF and thus pixel-based, the offsets are in pixels, so the “slice lines” are set 15 pixels inward from the edges of the image. (In the case of an SVG, the offsets are measured in terms of the SVG’s coordinate system.) It looks like this:

A diagram outlining how the border image is sliced and positioned along the box's edges, corner's and offsets.

Each slice is assigned to the corner or side of the element box that corresponds to itself; i.e., the bottom right corner slice is placed in the bottom right corner of the element, the top (center) slice is used along the top edge of the element, and so on.

If one of the edge slices is smaller than the edge of the element is long—which almost always happens, and is certainly true here—then the slice is repeated in one of a number of ways. I chose round, which fills in as many repeats as it can and then scales them all up just enough to fill out the edge. So with a 70-pixel-long slice, if the edge is 1,337 pixels long, there will be 19 repetitions of the slice, each of which is scaled to be 70.3 pixels wide. Or, more likely, the browser generates a single image containing 19 repetitions that’s 1,330 pixels wide, and then stretches that image the extra 7 pixels.

You might think the drawback here is browser support, but that turns out not to be the case.

This browser support data is from Caniuse, which has more detail. A number indicates that browser supports the feature at that version and up.

Desktop

Chrome Opera Firefox IE Edge Safari
56 43 50 11 12 9.1

Mobile / Tablet

iOS Safari Opera Mobile Opera Mini Android Android Chrome Android Firefox
9.3 46 all* 67 71 64

Just watch out for the few bugs (really, implementation limits) that linger around a couple of implementations, and you’ll be fine.

Conclusion

While it might be a rare circumstance where you want to combine multiple “border” effects, or stack them atop each other, it’s good to know that CSS provides a number of ways to get the job done, and that most of them are already widely supported. And who knows? Maybe one day there will be a simple way to achieve these kinds of effects through a single property, instead of by mixing several together. Until then, happy border stacking!

The post Stacked “Borders” appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]

Gradient Borders in CSS

Let’s say you need a gradient border around an element. My mind goes like this:

  • There is no simple obvious CSS API for this.
  • I’ll just make a wrapper element with a linear-gradient background, then an inner element will block out most of that background, except a thin line of padding around it.

Perhaps like this:

See the Pen Gradient with Wrapper by Chris Coyier (@chriscoyier) on CodePen.

If you hate the idea of a wrapping element, you could use a pseudo-element, as long as a negative z-index value is OK (it wouldn’t be if there was much nesting going on with parent elements with their own backgrounds).

Here’s a Stephen Shaw example of that, tackling border-radius in the process:

See the Pen Gradient border + border-radius by Shaw (@shshaw) on CodePen.

You could even place individual sides as skinny pseudo-element rectangles if you didn’t need all four sides.

But don’t totally forget about border-image, perhaps the most obtuse CSS property of all time. You can use it to get gradient borders even on individual sides:

See the Pen Gradient Border on 2 sides with border-image by Chris Coyier (@chriscoyier) on CodePen.

Using both border-image and border-image-slice is probably the easiest possible syntax for a gradient border, it’s just incompatible with border-radius, unfortunately.

See the Pen CSS Gradient Borders by Chris Coyier (@chriscoyier) on CodePen.

The post Gradient Borders in CSS appeared first on CSS-Tricks.

CSS-Tricks

,
[Top]