№ 002

Coral Nav

A soft coral drop that sinks into the bar and flows between tabs.

Version
1.0.0
Download
8 KB · 3 files
Added
Oct 26, 2026
Built with
HTML, CSS, JS
Needs
Google Fonts
Tags
tab bar, liquid, svg

Usage

Copy coral-nav.css and coral-nav.js (and assets/, if there is one) into your project, then:

  new CoralNav(element, { tabs: [{ label, icon }], active: 0, onChange(tab, index) {} })
Active tab is exposed with aria-current="page". `icon` is a key of CoralNav.icons or a raw SVG string.

All geometry is in units of u = barHeight / 132. Shape coordinates are relative to the
drop's centre x and the bar's top edge (y = 0).

Source

/* Coral nav — an ivory pill bar; the active tab is a matte coral dome that swells out of it. */

.cn {
  --h: clamp(72px, 14vw, 99px);
  --u: calc(var(--h) / 132);
  --ink: #756762;
  --ico: calc(var(--u) * 36);
  position: relative;
  width: 100%;
  max-width: 684px;
  height: var(--h);
  margin-top: calc(var(--u) * 44);
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  font-size: clamp(12px, calc(var(--u) * 18), 18px);
}

.cn-bar,
.cn-drop {
  position: absolute;
  left: 0;
  overflow: visible;
  pointer-events: none;
}

.cn-bar {
  filter:
    drop-shadow(0 calc(var(--u) * 2) calc(var(--u) * 3) rgba(150, 105, 85, 0.1))
    drop-shadow(0 calc(var(--u) * 16) calc(var(--u) * 26) rgba(150, 105, 85, 0.12));
}

.cn-drop {
  filter:
    drop-shadow(0 calc(var(--u) * 3) calc(var(--u) * 4) rgba(190, 95, 70, 0.18))
    drop-shadow(0 calc(var(--u) * 10) calc(var(--u) * 14) rgba(190, 95, 70, 0.14));
}

.cn-tabs { position: absolute; inset: 0; }

.cn-tab {
  position: absolute;
  top: 0;
  left: 0;
  height: 100%;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  border: 0;
  border-radius: calc(var(--u) * 30);
  background: none;
  color: var(--ink);
  font: inherit;
  cursor: pointer;
  transition: color 0.2s;
  will-change: transform;
}

.cn-tab:hover { color: #4f4440; }

.cn-tab:focus-visible {
  outline: 2px solid #e9856b;
  outline-offset: -6px;
}

.cn-tab[aria-current="page"] { pointer-events: none; }

.cn-icon,
.cn-name {
  position: absolute;
  left: 50%;
  opacity: var(--f, 1);
}

.cn-icon {
  top: calc(var(--u) * 52 - var(--ico) / 2);
  display: grid;
  width: var(--ico);
  height: var(--ico);
  transform: translateX(-50%) scale(calc(0.7 + 0.3 * var(--f, 1)));
}

.cn-name {
  top: calc(var(--u) * 96);
  transform: translateX(-50%) translateY(calc(var(--u) * 6 * (1 - var(--f, 1))));
  translate: 0 -50%;
  white-space: nowrap;
  letter-spacing: 0.01em;
}

.cn-icon svg,
.cn-live-icon svg { width: 100%; height: 100%; }
.cn-live-icon svg { transform: scale(1.05); }

.cn-live {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  height: 100%;
  color: #fff;
  pointer-events: none;
  will-change: transform;
}

.cn-live-icon {
  position: absolute;
  left: 0;
  top: calc(var(--u) * 30 - var(--ico) / 2);
  display: grid;
  width: var(--ico);
  height: var(--ico);
  translate: -50% 0;
}

.cn-live-name {
  position: absolute;
  left: 0;
  top: calc(var(--u) * 83);
  translate: -50% -50%;
  white-space: nowrap;
  letter-spacing: 0.01em;
}

.cn-live-dash {
  position: absolute;
  left: 0;
  top: calc(var(--u) * 108);
  width: calc(var(--u) * 38);
  height: calc(var(--u) * 5);
  translate: -50% -50%;
  border-radius: 99px;
  background: #fff;
}

.cn-live-icon.pop { animation: cn-pop 0.2s ease-out; }
.cn-live-name.pop { animation: cn-rise 0.2s ease-out; }

@keyframes cn-pop { from { opacity: 0; transform: scale(0.6) translateY(6px); } }
@keyframes cn-rise { from { opacity: 0; transform: translateY(5px); } }

@media (prefers-reduced-motion: reduce) {
  .cn-live-icon.pop, .cn-live-name.pop { animation: none; }
}