Curved Box Cutouts in CSS
This post explores a trick to create the illusion of an element appended to another with a gap and curved edges at the corners. It’s useful for visually demarcating supplementary elements or user controls in a card module.
An example:
Let’s start with the HTML and code a simple design.
<div class="outer"> <div class="inner"></div> </div>
Code language: HTML, XML (xml)
Use a nested element (.inner
) or a stacked element to create the small box. The key is for the small box to overlap the larger one.
.outer { width: 375px; aspect-ratio: 1; border-radius: 12px; background: dodgerblue; .inner { width: 160px; height: 60px; border-radius: inherit; background: skyblue; } }
Code language: CSS (css)
The larger square box (.outer
) and the smaller rectangle box (.inner
) share the same border-radius
value (12px
).
Add an outline
of the same color as the page.
.inner { outline: 8px solid white; }
Code language: CSS (css)
That’s all we need to do with the inner box.
Add two small radial-gradient()
background images to the larger box’s background.
- Position the images where the smaller box’s corners overlap, with a negative offset equal to the outline size (
8px
). - The border radius (
12px
) plus the smaller box’s outline (8px
) equals the images’ size (20px 20px
). - The gradients are transparent circles the same size as the border radius (
12px
), with the rest white
.outer { background: -8px 60px / 20px 20px radial-gradient(circle at right bottom, transparent 12px, white 12px), 160px -8px / 20px 20px radial-gradient(circle at right bottom, transparent 12px, white 12px), dodgerblue; }
Code language: CSS (css)
The code is complete. You’ll get the final result as is. However, let’s make the code more flexible.
For ease of update, I’ll move the length values to CSS variables, and for clarity, I’ll list each of the background-
properties separately.
.outer { width: 375px; aspect-ratio: 1; --r: 12px; --w: 160px; --h: 60px; --o: 8px; --ofs: calc(-1 * var(--o)); --sz: calc(var(--r) + var(--o)); --img: radial-gradient(circle at right bottom, transparent var(--r), white var(--r)); border-radius: var(--r); background-image: var(--img), var(--img); background-position: var(--ofs) var(--h), var(--w) var(--ofs); background-size: var(--sz) var(--sz); background-repeat: no-repeat; background-color: dodgerblue; .inner { width: var(--w); height: var(--h); outline: var(--o) solid white; border-radius: inherit; background: skyblue; } }
Code language: CSS (css)
Place the smaller box in the desired corner against the bigger one, and update the radial gradient image positions and circles accordingly.
.outer { background-image: radial-gradient(circle at var(--cnr), transparent var(--r), white var(--r)), radial-gradient(circle at var(--cnr), transparent var(--r), white var(--r)), linear-gradient(45deg, rgb(210, 223, 246), rgb(51, 134, 242)); &:nth-of-type(1) { --cnr: right bottom; background-position: var(--ofs) var(--h), var(--w) var(--ofs), 0 0; } &:nth-of-type(2) { --cnr: left bottom; background-position: calc(100% - var(--ofs)) var(--h), calc(100% - var(--w)) calc(var(--ofs)), 0 0; } &:nth-of-type(3) { --cnr: left top; background-position: calc(100% - var(--ofs)) calc(100% - var(--h)), calc(100% - var(--w)) calc( 100% - var(--ofs)), 0 0; } &:nth-of-type(4) { --cnr: right top; background-position: var(--ofs) calc(100% - var(--h)), var(--w) calc(100% - var(--ofs)), 0 0; } }
Code language: CSS (css)
The larger box in the example is a square, so 100%
is used in calculating the radial gradient images’ positions both vertically and horizontally where needed.
Since the design uses an imitation of a gap, effects like drop shadow that require cutouts won’t work. However, no extra markup or style changes are needed, only the background is affected, making it suitable for simple designs.
This doesn’t have to be limited to gap-like designs, either. The outline can be used in other ways, too. The rounded corners will be a subtle touch up.
.date { outline: var(--outline) solid navy; }
Code language: CSS (css)