Back to top

Awesome width-adjusting buttons with CSS

Rigid boxiness of HTML/CSS can be very frustrating. Often we want objects to have more unique mixtures of block and inline properties than display values block, inline or inline-block can give us.

This is especially true when buttons are concerned. Buttons are very important parts of any website. If designed properly they draw attention, communicate the purpose and entice visitors to act. It's not easy to predict the length of their copy and other metrics that usually vary between different forms.

This article is based on a case I did for a client in the past when changing the browser width to and fro evoked sighs of bewilderment and amazement. I also added how to achieve the same with new methods that make it easier to break out of CSS rigidity.

First, we'll take a look at the layout options used, then see what other design choices were made to make the buttons more responsive to their environment.


The old way—width, max-width, and auto margin

The main call to action deserves its own line with a strong block-level element statement. But using the whole width usually isn't the most usable choice. Restricting it to the expected with of the button text is a good way to make it more readable. I usually round it up to the nearest integer in rem units. Setting the width in font-size related units makes sure the button will scale nicely if the font size changes (even if browser zoom settings are set to affect only text size).

The main CTA button

Left and right margin set to auto centers block level elements horizontally.

  1. .layout-selfadjust [class$="user"] {
  2.   display: block;
  3.   margin-left: auto;
  4.   margin-right: auto;
  5.   width: 16em;
  6. }

Setting the width wide enough to contain the button text will render it all in one line if there is enough space for the whole button to be displayed. But what happens if there is a lot of text or the font size is larger? The button will leak out of its container, which isn't a nice sight to behold.

Block level element with fixed width can leak out

max-width property can come to the rescue because it overrides width and it makes the width property more variable. They have to be specified in different units to make it to work. The width is set in font-size related units to make the button scale and look good in different font metrics, while we want max-width to respond to the environment the button is in, its container. So it makes sense to use the percentages as units for max-width:

  1. .layout-selfadjust [class$="user"] {
  2.   display: block;
  3.   margin-left: auto;
  4.   margin-right: auto;
  5.   max-width: 90%;
  6.   width: 16em;
  7. }

I've set it to 90% instead of 100% to keep a little space on the sides if the container becomes too tight.

Two-lined button
When width in rems and max-width in percentages work together the button text flows into the second line when there isn't enough space to fit in a single line to satisfy the width constraint.


Vertical vs. horizontal in em or device/container width

Choosing which units to use in a particular property can have dire consequences down the line. Sometimes it's worth considering using different units for horizontal and vertical values inside the same property.

Vertical distances are often defined relative to font-size to keep the spacing proportional if the font size is increased. But setting horizontal spacing in ems, for example, can make your design pointless on narrow screens when the font size is increased. It is better then to use units relative to the viewport or device width.

Viewport width is also very convenient when we need to set the spacing to be equal horizontally and vertically, but without using fixed units.

I've applied that logic to the button container:

  1. .buttons-premium {
  2.   margin: 1vw;
  3.   padding: 2em 1% 1em;
  4. }

Shadows in pixels vs shadows in font-sized units

If the box-shadow is set in pixels the chosen colors and overall look won't scale nicely with changing the button font size. If the font size increases so does the button area, but a pixel-based box-shadow stays the same as before the change in the font size so it basically covers a smaller area than before. The opposite happens when we decrease the font size—the button area gets smaller but as the shadows once again stay the same, they cover a larger area than before the font size decrease.

  1. box-shadow: 0 -3px 42px red inset, 
  2.             0 -13px 26px black inset, 
  3.             0 0 20px black inset, 
  4.             0 1px 6px #805300, 
  5.             0 0 10px black;
Box shadows don't scale nicely if set in pixels.
Box shadows don't scale nicely if set in pixels. If font-size is increased shadows cover less area showing more background through. If font-size is decreased shadows cover larger area obscuring the background.

But if we recalculate the pixels that give the satisfying result when Zoom is 1 (no font size change in the browser) into ems we'll get a perfectly scalable solution (it scales well if ems are used because our button metrics are defined in ems):

  1. box-shadow: 0 (-3em / 32) (42em / 32) red inset, 
  2.             0 (-13em / 32) (26em / 32) black inset, 
  3.             0 0 (20em / 32) black inset, 
  4.             0 (1em / 32) (6em / 32) #805300, 
  5.             0 0 (10em / 32) black;
Box shadows do scale proportionately if set in font-based units
Box shadows do scale proportionately if set in font-based units
Is there any difference between using em instead of rem units? Although the numbers aren't necessarily exactly the same if the rem/em isn't in 1:1 ratio, we will get the same end result. The shadows will behave the same no matter if we calculate them in rem or em. It is a good practice though to set rem only on major design block font-size (in BEM, or molecules in atomic design nomenclature) and use em on everything inside. This way we set one size control point for the design block and set everything related to that block to refer to that control point. Specifically set font-size of the block in rem, and everything else that needs to scale with font size to em.

Border radius in em vs border-radius in px or vw

Buttons often have a circular finish on the sides. We can't reliably use percentages to achieve that because the button width and height are rarely the same so the percentage will turn out different horizontal and vertical border radii.

50% border-radius doesn't yield a circular finish.
50% means half width horizontally and half height vertically which doesn't yield a circular finish.

To get circular sides we might be tempted to use ems. As previously when we used ems to scale properties we can use ems here as well. But when it comes to some properties (and border-radius is one of them) we might not like to have them faithfully scaled!

If we set our button to border-radius of 4em we get nice circular sides even if our button text flows into more than one line (when there isn't enough room to keep it in a single line). The two-line example from above still looks nice in circular side finish, but with four lines it looks a bit odd:

four-lined button

This weird egg shape with text unevenly spaced from the edges isn't what we might call gracious scaling. So, in this case, we want the scaling to stop at a certain point, to be smart so we get a different finish when the circular doesn't serve us well anymore.

Pixels can help our button have circular sides when the button spans few lines of text, and more palatable rounded corners when spanning more lines. But this will look good only if we keep the font size intact or decrease it. If we increase the font size when there is enough room to fit everything into a single line, the original amount of pixels won't be enough to reach half button height to round it perfectly:

Rounded corners instead of half-circles because of px

This is the case when we want to keep the circled sides because that follows the design. It is only when things get awkward that we want the rounded corners to take over.

The ultimate solution here is to use viewport width units. The vw units are so magical that it is possible to find the sweet-spot that will make our button sides circular if the text spans one or two lines at any font size or screen width, and that turns the button into a rounded rectangle if space allows for three or more lines of text. The sweet-spot in our particular case is 14vw:

14vw border-radius

Conclusion and demo

In the end we used max-width to get width to behave, set the main metrics in font-size related units ems, ems also for shadows, and vw for border-radius to scale differently depending on geometry of the button.

Width-adjusting buttons

Investigate the full code:

See the Pen Width-adjusting button by Mihaela Jurković (@prkos) on CodePen.

What did you think of this article?