0

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:

enter image description here

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;
    }
  }
}

1 Answer 1

1

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>

Sign up to request clarification or add additional context in comments.

3 Comments

This seems fine on my laptop, and on iPhone in landscape but the red circle is not centred on iPhone portrait.
@AHaworth The demo was meant to simply demonstrate the stacking concept here. I thought that too many additional details would get in the way of that. The reason the red circle is off is because the padding and the buttons are fixed sizes. I've added a demo showing that it could indeed be responsive, but that's not the only way to approach the responsive aspect either.
@jme11 thank you a lot! Your concept really help me to recall how to do this UI. Respect++

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.