Glassmorphic Nav Bar

This navigation bar uses a combination of advanced CSS properties to achieve a glassmorphic style, similar to that of visionOS UI components. Functionally it’s most like a segmented control, but visually it is more like the visionOS Tab bar component (except in a horizontal layout) and Toolbar component (but with icons).

The effect is achieved with a combination of a semi-transparent background and box-shadow, and a backdrop-filter for the bar itself.

.bar {
  --padding: 0.75em;
  background: rgb(128 128 128 / 0.3);
  box-shadow: 0 1px 20px -1px rgb(0 0 0 / 0.18);
  backdrop-filter: blur(10px);
  ...
}

The self-fading border on the upper-left and lower-right is a linear-gradient background with a mask set to exclude the content-box, applied to a pseudo element.

.bar::after {
  ...
  background: linear-gradient(
    165deg,
    rgb(255 255 255 / 0.4) 0%,
    rgb(255 255 255 / 0) 41%,
    rgb(255 255 255 / 0) 57%,
    rgb(255 255 255 / 0.1) 100%
  );
  mask:
    linear-gradient(black, black) content-box exclude,
    linear-gradient(black, black);
  padding: 2px;
}

The spotlight effect is an extra element extending the width of the tabs area, with a semi-transparent background and a clip-path applied using two css custom property values (--left and --width) which are set by JavaScript. The inset function with the round keyword creates the pill shape.

.spotlight {
  ...
  background: rgb(255 255 255 / 0.2);
  clip-path: inset(
    0 calc(100% - ((var(--left) + var(--width)) * 1px))
    0 calc(var(--left) * 1px) round 99em
  );
}

Finally, the spotlight movement is prepped by applying a transition to the clip-path property, via a data-init attribute set by JavaScript. Then the click handlers are added to each button, which apply new left and width values.

.spotlight {
  ...
  opacity: 0;

  &[data-init] {
    opacity: 1;
    transition-property: clip-path;
    transition-duration: 0.2s;
  }
}
const tabs = document.querySelectorAll<HTMLButtonElement>('.button')
const spotlight = document.querySelector<HTMLSpanElement>('.spotlight')!
const { offsetLeft, offsetWidth } = tabs[0]
const setTabPosition = (left: number, width: number) => {
  spotlight.style.setProperty('--left', `${left}`)
  spotlight.style.setProperty('--width', `${width}`)
}

setTabPosition(offsetLeft, offsetWidth)
spotlight.dataset.init = ''

tabs.forEach((element) => {
  element.addEventListener('click', () => {
    const { offsetLeft, offsetWidth } = element
    setTabPosition(offsetLeft, offsetWidth)
  })
})
View Astro Component →