As CSS border-radius
will only work in the place the border is supposed to appear - between margin and padding - we need to come up with a more creative solution if we want a round edge where the content is.
The Problem
Working with Vuetify 3’s V-Main
component, it became noticeable that this main wrapper would always span the full width and height, but have a dynamic padding
based on the surrounding menus. Because of outside constraints, I was also unable to add another wrapping element.
Quick mockup - hover over it, or check the code in devtools:
Since .main
expands below .topnav
and .sidenav
, setting a border radius would not work here, because it would be hidden by the navigation elements.
Solution 1
The preferrable solution would be to set the background color to the nav menu color, add another wrapping element inside .main
, give that a border-top-left-radius
and hide the overflow
. This wasn’t possible in my situation, but I was able to add a helper element inside .main
.
Using this element, an SVG helper element and some CSS, we can simulate the border-radius
effect with clip-path
.
For this, we’re using an SVG to define the path that should be filled with the navigation background color:
<svg height="0" width="0">
<defs>
<clipPath id="edge-clippath" clipPathUnits="objectBoundingBox">
<path d="M.5,0H0v.5C0,.224,.224,0,.5,0z"></path>
</clipPath>
</defs>
</svg>
clipPathUnits="objectBoundingBox"
is necessary for the path to scale with the element. Then, we create a square filled with the navigation background color, and clip it to only show the part from the SVG definition:
.rounded-edge {
/* this is the border radius we want to simulate */
--border-radius: 10px;
/* our pseudo elements need to be of this size */
--border-element-size: calc(var(--border-radius) * 2);
/* this will position it at the top left corner of .main's padding's start */
position: absolute;
left: 0;
top: 0;
/* since it'll be overlaying other elements, ignore mouse clicks */
pointer-events: none;
/* by inheriting .main's padding, its content will start in the same position */
padding: inherit;
/* don't set z-index by hand, use an SCSS helper! */
z-index: 2;
}
.rounded-edge::before {
/* navigation background color */
background: var(--navigation-background-color);
content: '';
display: block;
/* this uses the path given in the SVG definition to clip the rendered content */
clip-path: url(#edge-clippath);
height: calc(var(--border-element-size));
width: calc(var(--border-element-size));
}
Solution 2
If using clip-path
is not possible, we can come up with a more complex solution. This has the drawback of possibly occluding anything that is in the top left corner of .main
.
The rounded border helper element inside it gets these CSS definitions:
.rounded-edge {
position: relative;
/* everything else the same as in solution #1 */
/* ... */
}
.rounded-edge::after,
.rounded-edge::before {
content: '';
position: absolute;
}
.rounded-edge::before {
/* the color of the surrounding navigation's background */
background: var(--navigation-background-color);
/*
the parent .rounded-edge element is exactly as big
as the padding on .main, so we need to move to the
bottom and right by half our border element's
desired size
*/
bottom: calc(-1 * var(--border-element-size) / 2);
right: calc(-1 * var(--border-element-size) / 2);
/*
this is a square exactly half the border element's length and width,
starting at the top left corner
*/
height: calc(var(--border-element-size) / 2);
width: calc(var(--border-element-size) / 2);
}
.rounded-edge::after {
/* color of the .main element's background */
background: var(--main-background-color);
/* this clips a circle */
border-radius: 50%;
/*
the parent .rounded-edge element is exactly as big
as the padding on .main, so we need to move to the
bottom and right by exactly our border element's
desired size
*/
bottom: calc(-1 * var(--border-element-size));
right: calc(-1 * var(--border-element-size));
height: var(--border-element-size);
width: var(--border-element-size);
}
What we are doing here is creating a square filled with the background of the navigation elements, and overlaying a circle filled with the .main
element’s background color.
To see how and why this works, take a look at this enlargened color-shifted demonstration: