5-Dec

Development, CSS and Design

Wrap your elements with box-shadow instead of border

Christmas is at the door, but not only presents are being wrapped. For years your tool of choice for wrapping elements has been border. But what if I told you there exists an even more flexible and layout friendly property than this?

In this guide we will explore how box-shadow can be used as an alternative to traditional borders, and how it can solve our layout problems.

4 min read

·

By Marcus Haaland

·

December 5, 2023

Wrapping with border

First, lets have a look at wrapping elements with the border property. Have a look beneath the tree:

A button with a border is beneath a Christmas tree
A button with a border is beneath a Christmas tree

Here's a challenge for you, so don't scroll further down too fast. Take a look at the following code. What do you think happens if you hover the button?

button {
  background-color: red;
  color: white;
  padding: 1rem;
  border: 4px solid green;
}

button.border:hover {
  border-width: 16px;
}

Here's the result:

Hovering the button leads to layout shift
Hovering the button leads to layout shift

Not surprising, the border of course got bigger. But it also caused a layout shift. When the border gets bigger, also the button gets bigger, and pushes elements around. This is often an unwanted effect. If you have a button in a form, you don't want the rest of the elements getting pushed when focusing a button.

I will first reveal some ways around this with border, and then suggest another solution using box-shadow.

Avoid layout shift using fixed height and width

The cause of the layout shift is that the border is a part of the element's actual size. So if an element is bigger, logically it will also move other elements around.

In form elements, including button, border and padding is baked into the element's size. This is through the CSS-declaration box-sizing: border-box. This makes it much simpler to control the size of an element, and it also something we can exploit to avoid layout shifts with border.

One solution is to define a big enough height and width, so the increased border size does not exceed the size it already has:

button {
  background-color: red;
  color: white;
  padding: 1rem;
  border: 4px solid green;
  width: 140px;
  height: 100px;
}

button.border:hover {
  border-width: 16px;
}

If the included border-width is not greater than the height and width that have been set, it will not lead to a layout shift:

Hovering in a button with a large enough set size does not lead to a layout shift
Hovering in a button with a large enough set size does not lead to a layout shift

Avoid layout shift by calculating the padding

Another solution to avoid layout shift is to calculate padding with regard to border-width. As the border-width increases, we also decrease the padding, so the button's size remains the same:

button {
  --border-width: 4px;
  background-color: red;
  color: white;
  padding: calc(1rem - var(--border-width));
  border: var(--border-width) solid green;
}

button.border:hover {
  --border-width: 16px;
}

button.with-transition {
	transition: padding 0.3s ease, border 0.3s ease;
}

The result looks like this, here a button with and without transition:

Calculation of padding leads to layout shift for transition
Calculation of padding leads to layout shift for transition

As you can see, it seems to work fine without transition. But if you look closely at the package with transition, it's as if the content is trying to jump out. The reason is that padding changes size at a different rate than border-width.

The conclusion is that it is possible to calculate with padding, but only if you are not going to use transition — or want to create a stylish effect.

Use shadows as a border

Since borders have some challenges, we can rather look to another tool. Box-shadow is another approach to wrapping an element. It behaves differently than a border, and thus has more advantages when it comes to changes. First of all, the box-shadow does not affect an element's size, which means that we do not need to take account of calculating height or padding.

Using box-shadow as a border can look like this, where we use either an outer or an inner shadow:

With box-shadow you can set an outer or inner border
With box-shadow you can set an outer or inner border

Notice that when we have an inner shadow without interactivity, it looks like a border set with border. Whilst the outer shadow is placed outside the element's size, which is different from border.

We can choose whether to have an outer or inner border with the inset value. The box-shadow property allows you to control how spread the shadow is, but when we use it as a border, we only care about 3 values: whether the border should be outside the element, the border size and the color (inset, 4px and green, respectively):

button {
  background-color: red;
  color: white;
  padding: 1rem;
  border: none;
}

button.outer {
  box-shadow: 0 0 0 4px green;
}

button.outer:hover {
  box-shadow: 0 0 0 16px green;
}

button.inner {
  box-shadow: inset 0 0 0 4px green;
}

button.inner:hover {
  box-shadow: inset 0 0 0 16px green;
}

You can just fine only use box-shadow alone to style a border, but it can also be useful to know that you can combine box-shadow and border.

Since border have certain styles that are not included in box-shadow, you may want a certain border-style when it is at rest, but an outer border using box-shadow on hover:

The button on the left is wrapped with border and box-shadow, while the one on the right is only with box-shadow
The button on the left is wrapped with border and box-shadow, while the one on the right is only with box-shadow
button.combo {
  border: 4px solid green;
  border-style: dotted;
}

button.combo:hover {
  box-shadow: inset 0 0 0 8px green;
}

button.box {
  border: none;
  box-shadow: inset 0 0 0 4px green;
}

button.box:hover {
  box-shadow: inset 0 0 0 8px green;
}

Should I always use box-shadow over border?

Both border and box-sizing have good cross-device support, but there may be certain browser versions and devices that override box-shadow. I have experienced that Safari 15 on the iPhone has removed the box-shadow, but this has been resolved by not allowing overriding via the appearance CSS property. Some e-mail clients report problems with box-shadow, so also this should be checked whether your styling result is as expected.

The conclusion is that you should start with borders. If you also need dynamic sizes for the border, use box-shadow. You can use it in combination or alone. But make sure to test whether the result is as expected across devices and browsers.

Final words

I think most of us have used border to wrap our elements. It works well as long as the size remains the same, but once the size is dynamic, you may want to look at other solutions. box-shadow solves this in a good good way, and is a tool you should consider the next time you need a dynamic border.