Responsive CSS border radius with the Fab Four technique
Tags: css, webdev, tutorial, programming
Published: 2021-12-21 20:31:46

**Written by [Kiril Peyanski](https://blog.logrocket.com/author/kirilpeyanski/)** ✏️

Browser support for various CSS features has gone a long way in the last couple of years. While vertically centering a `div` is something we execute fairly easily this way, there are still a couple of crucial features we are missing.

You are probably already familiar with `media-queries`, which apply styling based on the browser viewport or the screen resolution, but sometimes we might need [`container-queries`](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries), which are still only available under `feature` flags for some browsers.

Today we will implement the Fab Four technique to apply a `border-radius` to an element depending on its `container` dimension, rather than the `screen` width.

> **Author’s note**: I have recently stumbled upon this technique on [Twitter](https://twitter.com/shadeed9/status/1444683275616854022) where [Frank Yan](https://twitter.com/frankyan) (a Facebook engineer) further explained its implication. In the thread it was suggested that this piece of code should not be “human-written,” so I’ve got you covered by the end of this blog post.

## What does this technique do?

The Fab Four technique can be implemented through the usage of various CSS functions like `min`, `max`, `calc`, and `clamp` to calculate if a specific CSS rule should be applied, while comparing it to the dimension of the container element instead of the device viewport.

Let's take a look at the following demo with a couple of `card` components wrapped in a resizable container:

See the Pen [Untitled](https://codepen.io/kspeyanski/pen/YzrKKKW) by Kiril Peyanski ([@kspeyanski](https://codepen.io/kspeyanski)) on [CodePen](https://codepen.io).

The specific requirement we want to implement here is to remove the `border-radius` of the cards whenever their container is less than `400px` wide.

![Resizing the Border Radius of the Cards](https://blog.logrocket.com/wp-content/uploads/2021/12/Dynamic-Border-Fab-Four.gif)

> **Author’s note**: The CSS equivalent to debugging with `console.log` is `border: 1px solid red`, and you should not feel bad about using it!

Taking a deeper look at our code, we can observe the following `border-radius` calculation:

```css
.dynamic-card {
  border-radius: max(0px, min(16px, (100% - 400px + 1px) * 9999)) / 16px;
}
```

It might look strange at first sight, but we will go over each calculation and explain it further. We want to identify the following variables in the code:

*   Desired `border-radius`
*   Container width `breakpoint`

```css
.dynamic-card {
  border-radius: max(0px, min(DESIRED_BORDER_RADIUS, (100% - WIDTH_BREAKPOINT + 1px) * 9999)) / DESIRED_BORDER_RADIUS;
}
```

In order to deep-dive into the above CSS rule, we would have to split it into three different calculations:

- The `min` calculation
- The `max` calculation
- The `division` by pixels

### The `min` calculation

```css
  min(DESIRED_BORDER_RADIUS, (100% - WIDTH_BREAKPOINT + 1px) * 9999)
```

What is happening here? Well, first we have to think of what the `100%` is doing. Setting `width: 100%` to a CSS element will (in most cases) expand the element to its full container width, meaning that when the `100%` is used in a `min` function, it will always return the `width` of our container.

This means that if our container is `480px` wide and we've defined a `WIDTH_BREAKPOINT` of `400px`, the calculation would be:

```css
  /* 480px - 400px + 1px = 81px * 9999 = a really big POSITIVE number */
  min(DESIRED_BORDER_RADIUS, (480px - 400px + 1px) * 9999)
```

The `* 9999` part of the calculation is just to be sure we're always either way above the `max` value or way below the `min` value. Otherwise, we might end up with something in between.

Take a look at the alternative scenario where the container width is less than our predefined breakpoint:

```css
  /* 320px - 400px + 1px = -79px * 9999 = a really big NEGATIVE number */
  min(DESIRED_BORDER_RADIUS, (320px - 400px + 1px) * 9999)
```

Since we have a `min` function, we have the following result from the calculation:

*   When the `container` width is above the `breakpoint`, the final result would be `DESIRED_BORDER_RADIUS` (or `16px` in our example)
*   When the `container` width is below the `breakpoint`, we have a negative number (or `-789,921` in our example)

The `max` calculation This is the next function that would be called in our complex set of calculations:

```css
  max(0px, RESULT_FROM_MIN)
```

We already know that we would receive either a positive border radius or a negative number. By applying the result from the `min` calculation, we get the following:

*   When the `container` width is above the `breakpoint`: `RESULT_FROM_MIN` (or `16px` in our case)
*   When the `container` width is below the `breakpoint`: `0px`

### The `/ DESIRED_BORDER_RADIUS` calculation

```css
  border-radius: RESULT_FROM_MAX / DESIRED_BORDER_RADIUS;
```

This is not a `division` operator, but an [extended syntax](https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius#values) to apply `border-radius` to an element. The code above will be evaluated (notice the missing `/` character):

```css
  border-top-left-radius:     RESULT_FROM_MAX DESIRED_BORDER_RADIUS;
  border-top-right-radius:    RESULT_FROM_MAX DESIRED_BORDER_RADIUS;
  border-bottom-right-radius: RESULT_FROM_MAX DESIRED_BORDER_RADIUS;
  border-bottom-left-radius:  RESULT_FROM_MAX DESIRED_BORDER_RADIUS;
```

This is important, as the CSS parsing engine is not particularly happy with how we try to define the border radius with the `min` and `max` functions, so we use this neat little trick.

We can use the `clamp` function as an alternative, but we avoid doing so since it's [not supported](https://caniuse.com/css-math-functions) in Safari 12.

![Clamp Support in Safari](https://blog.logrocket.com/wp-content/uploads/2021/12/clamp_support_in_safari.png)

### Comparison to `media-queries`

This technique relies on our CSS parsing engine to account for the `100%` in the calculation. This percentage would evaluate to the `width` of our container as opposed to the `screen width`, which we can use in `media-queries`.

The following code would work in most scenarios, but would fail when we have a resizable container as in the example above:

```css
.dynamic-card {
    border-radius: 0;
}
@media screen and (min-width: 400px) {
  .dynamic-card {
    border-radius: 16px;
  }
}
```

### Reversing the logic

The Fab Four technique could be applied in multiple scenarios depending on the application requirements. Sometimes a specific rule should be applied whenever the container `width` is less than a specific `breakpoint`. In such cases, we can invert the logic:

From:

```css
(100% - WIDTH_BREAKPOINT + 1px) * 9999))
```

To:

```css
(WIDTH_BREAKPOINT - 1px - 100%) * 9999))
```

This would apply the desired `value` only when the `width` is below the `breakpoint`.

## Utilities

Let's face the fact that this calculation is not straightforward and hard to read, even when you are familiar with the technique. Luckily for us, we can extract SASS mixins, or utility functions for various CSS-in-JS libraries (like [styled-components](https://styled-components.com/)).

### Native CSS with CSS variables

```css
  .dynamic-card {
    --border-radius: 16px;
    --breakpoint: 400px;
  }
  .dynamic-border {
    border-radius: max(0px, min(var(--border-radius), (100% - var(--breakpoint) + 1px) * 9999)) / var(--border-radius);
  }
```

### SASS mixin

The following [SASS mixin](https://sass-lang.com/documentation/at-rules/mixin) helps us implement the Fab Four technique in SASS based projects:

```scss
@mixin dynamic-border-radius($value, $breakpoint) {
  & {
    border-radius: #{"max(0px, min(#{$value}, 100% - #{$breakpoint} + 1px) * 9999) / #{$value}"};
  }
}
.dynamic-card {
  @include dynamic-border-radius(16px, 400px);
}
```

### CSS-in-JS

```css
import styled, { css } from 'styled-components';
const dynamicBorderRadius = (value, breakpoint) => css`
  border-radius: max(0px, min(${value}px, 100% - ${breakpoint}px + 1px) * 9999) /
    ${value}px;
`;
const Card = styled('div')`
    ${dynamicBorderRadius(16, 400)};
`;
```

## Conclusion

With the web moving forward, we will be seeing more features like `container-queries` implemented in most web browsers. Until this day comes, we can use techniques like the Fab Four to implement specific application requirements, which don’t always look straightforward.

---

## Is your frontend hogging your users' CPU?

As web frontends get increasingly complex, resource-greedy features demand more and more from the browser. If you’re interested in monitoring and tracking client-side CPU usage, memory usage, and more for all of your users in production, [try LogRocket](https://logrocket.com/signup/).

[![LogRocket Dashboard Free Trial Banner](https://blog.logrocket.com/wp-content/uploads/2019/12/cpu-memory-usage.png)](https://logrocket.com/signup/)

[LogRocket](https://logrocket.com/signup/) is like a DVR for web apps, recording everything that happens in your web app or site. Instead of guessing why problems happen, you can aggregate and report on key frontend performance metrics, replay user sessions along with application state, log network requests, and automatically surface all errors.

Modernize how you debug web apps — [Start monitoring for free](https://logrocket.com/signup/).