I’m working on a fixed bottom navigation bar at the bottom of the page. I want to add a curved shape (like a rounded “notch”) at the top center of this bar.
Example:
I’ve tried using a ::before pseudo-element to create the curve with border-radius, and it mostly works but solution is not ideal, z-index are not helping.
The tricky part: I need the border of the curve to blend smoothly with the bar’s border, so it looks like one continuous border (even when the theme changes, like in .root_inverted). Also, I want the actual content inside the bar to stay unaffected—meaning I don’t want to restructure or move the DOM around just to fit the curve.
I’m trying to keep this CSS-only.
Has anyone solved something similar before? I’d love to hear ideas or clever tricks.
For now will simply share the small portion of CSS, because hope that somebody saw something similar or knows what I'm asking for. If it's not enough - will create the codepen demo.
Here's a simplified version of my SCSS:
.background {
position: fixed;
right: 0;
bottom: 0;
left: 0;
z-index: 10;
background: grey;
.root_inverted & {
background: red;
}
&::before {
content: '';
position: absolute;
top: -15px; // curve height
left: 50%;
transform: translateX(-50%);
width: 60px; // curve width
height: 30px;
border-top-left-radius: 30px;
border-top-right-radius: 30px;
background-color: grey;
border: 1px solid red;
border-bottom: none;
z-index: 1;
.root_inverted & {
background-color: yellow;
}
}
}
Here's a really basic example. Stacking two pseudo elements is the way I'd approach this. The first element is sent behind the "bar" across the bottom and the second is above it. The one that is behind the bar has the border and/or box-shadows applied, while the one in front, does not. Then, just stack your icon element above all of it.
nav {
inset: auto 0 0 0;
position: fixed;
box-shadow: 0 0 2rem 0 black;
border-radius: 2rem 2rem 0 0 / 1rem;
}
ul {
background: #222;
border-radius: 2rem 2rem 0 0 / 1rem;
display: flex;
justify-content: space-around;
padding: 1rem 2rem 0;
position: relative;
box-shadow: 0 0 0 0.25rem white;
&::before {
content: "";
position: absolute;
aspect-ratio: 1;
background: inherit;
box-shadow: 0 0 0 0.25rem white, 0 0 2rem 0 black;
height: 8rem;
border-radius: 50%;
top: -40%;
z-index: -1;
pointer-events: none;
}
&::after {
content: "";
position: absolute;
aspect-ratio: 1;
background: inherit;
height: 8rem;
border-radius: 50%;
top: -40%;
pointer-events: none;
}
}
li {
aspect-ratio: 1;
background: lightgray;
height: 3rem;
}
.home {
position: relative;
z-index: 10;
border-radius: 50%;
height: 4rem;
transform: translatey(-1rem);
background: red;
}
/* demo only */
*,
*::after,
*::before {
box-sizing: border-box;
margin: 0;
padding: 0;
}
ul {
list-style-type: none;
}
body {
background: radial-gradient(closest-side, #ccc, #444) bottom left/200% 200% no-repeat;
min-height: 100vh;
padding-bottom: 10rem;
}
<nav>
<ul>
<li></li>
<li></li>
<li class="home"></li>
<li></li>
<li></li>
</ul>
</nav>
Here's a more robust demo that uses this approach and is responsive as well, but you should be able to adapt this concept to your own design.
*,
*::after,
*::before {
box-sizing: border-box;
margin: 0;
padding: 0;
}
ul {
list-style-type: none;
}
a {
color: inherit;
}
body {
background: radial-gradient(closest-side, #303640, #1b2129) bottom left/200%
200% no-repeat;
font-family: system-ui;
min-height: 100vh;
padding-bottom: 10rem;
}
nav {
--size: 3rem;
--border-shadow: 0 0 0 0.05rem lightgray;
--element-shadow: 0 0 2rem 0 black;
inset: auto 0 0 0;
position: fixed;
box-shadow: var(--element-shadow);
border-radius: 2rem 2rem 0 0 / 1rem;
@media (width > 480px) {
--size: 4rem;
}
@media (width > 640px) {
--size: 5rem;
}
}
ul {
background: #111 linear-gradient(45deg, #171d25, #303640);
background-attachment: fixed;
display: flex;
justify-content: space-around;
min-width: 15rem;
border-radius: 2rem 2rem 0 0 / 1rem;
position: relative;
padding-block: 1rem;
box-shadow: var(--border-shadow);
&::before {
z-index: -1;
box-shadow: var(--border-shadow), var(--element-shadow);
}
&::before,
&::after {
content: "";
position: absolute;
top: -25%;
aspect-ratio: 1;
height: calc(var(--size) * 1.75);
background: inherit;
border-radius: 50%;
pointer-events: none;
}
}
li {
aspect-ratio: 1;
background: #606060;
height: var(--size);
position: relative;
z-index: 10;
}
span {
text-transform: capitalize;
text-align: center;
position: absolute;
bottom: -1.25rem;
font-weight: 600;
font-size: 0.875rem;
}
i {
font-size: calc(var(--size) * 0.5);
}
.home {
border-radius: 50%;
height: var(--size);
aspect-ratio: 1;
transform: translatey(-1rem);
background: #e23653;
> a {
display: grid;
place-items: center;
color: white;
height: 100%;
width: 100%;
filter: drop-shadow(0.35rem 0.35rem 0.4rem rgba(0, 0, 0, 0.5));
}
}
.icon-home {
mask-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M2.5192 7.82274C2 8.77128 2 9.91549 2 12.2039V13.725C2 17.6258 2 19.5763 3.17157 20.7881C4.34315 22 6.22876 22 10 22H14C17.7712 22 19.6569 22 20.8284 20.7881C22 19.5763 22 17.6258 22 13.725V12.2039C22 9.91549 22 8.77128 21.4808 7.82274C20.9616 6.87421 20.0131 6.28551 18.116 5.10812L16.116 3.86687C14.1106 2.62229 13.1079 2 12 2C10.8921 2 9.88939 2.62229 7.88403 3.86687L5.88403 5.10813C3.98695 6.28551 3.0384 6.87421 2.5192 7.82274ZM9 17.25C8.58579 17.25 8.25 17.5858 8.25 18C8.25 18.4142 8.58579 18.75 9 18.75H15C15.4142 18.75 15.75 18.4142 15.75 18C15.75 17.5858 15.4142 17.25 15 17.25H9Z' fill='%23000'%3E%3C/path%3E%3C/svg%3E");
background-color: currentcolor;
width: 1em;
aspect-ratio: 1;
}
<nav>
<ul>
<li></li>
<li></li>
<li class="home">
<a href="">
<i class="icon-home"></i>
<span>home</span>
</a>
</li>
<li></li>
<li></li>
</ul>
</nav>