Explorer
KNOW-PAT-112

Web UI/UX - Sticky Glassmorphism Header : backdrop-filter blur, border-bottom, z-index

Domaine
web-uiux
Type
pattern
Priorité
P1

Parent : [[INDEX-WEB-UIUX]]

Web UI/UX - Sticky Glassmorphism Header : backdrop-filter blur, border-bottom, z-index

Problème

Un header fixe sans transparence crée une barre opaque moche qui masque le contenu en dessous. Un header transparent sans blur rend le texte illisible sur du contenu complexe.

Solution

Header sticky (pas fixed) avec backdrop-filter: blur(12px) et fond semi-transparent, border-bottom subtil, et z-index: 40.

<header
  className="sticky top-0 z-40"
  style={{
    background: 'rgba(10, 11, 13, 0.92)',
    borderBottom: '1px solid var(--border)',
    backdropFilter: 'blur(12px)',
    WebkitBackdropFilter: 'blur(12px)',
  }}
>
  <div className="max-w-[1600px] mx-auto px-6 h-14 flex items-center justify-between gap-8">
    {/* Logo */}
    <div className="flex items-center gap-3 shrink-0">
      <div
        className="w-7 h-7 rounded-[4px] flex items-center justify-center text-white font-black text-sm"
        style={{ background: 'var(--accent)' }}
      >
        F
      </div>
      <span className="font-bold text-sm tracking-wide">
        BRAND<span style={{ color: 'var(--accent)' }}>NAME</span>
      </span>
    </div>

    {/* Nav desktop */}
    <nav className="hidden sm:flex items-center gap-1">
      {NAV.map((item) => {
        const active = section === item.id
        return (
          <button
            key={item.id}
            onClick={() => setSection(item.id)}
            className="px-4 py-2 rounded-[4px] text-xs font-semibold tracking-wide uppercase transition-colors"
            style={active
              ? { background: 'var(--accent-dim)', color: 'var(--accent)', border: '1px solid rgba(229,25,26,0.25)' }
              : { background: 'transparent', color: 'var(--text-2)', border: '1px solid transparent' }
            }
            onMouseEnter={(e) => { if (!active) e.currentTarget.style.color = 'var(--text)' }}
            onMouseLeave={(e) => { if (!active) e.currentTarget.style.color = 'var(--text-2)' }}
          >
            {item.label}
          </button>
        )
      })}
    </nav>

    {/* Mobile nav (scroll-x) */}
    <div className="sm:hidden flex overflow-x-auto px-4 pb-2 gap-1 border-t" style={{ borderColor: 'var(--border)' }}>
      {NAV.map((item) => (
        <button
          key={item.id}
          onClick={() => setSection(item.id)}
          className="px-3 py-1.5 rounded-[4px] text-xs font-semibold whitespace-nowrap uppercase shrink-0"
          style={section === item.id
            ? { background: 'var(--accent-dim)', color: 'var(--accent)' }
            : { color: 'var(--text-2)' }
          }
        >
          {item.label}
        </button>
      ))}
    </div>
  </div>
</header>

Points clés

  1. sticky pas fixed : évite de sortir le header du flow, pas besoin de padding-top sur le body
  2. backdropFilter: blur(12px) : minimum 8px pour être visible, 12-16px pour un effet propre
  3. Fond rgba(..., 0.92) : pas 100% transparent (illisible) ni 100% opaque (moche)
  4. z-40 : au-dessus du contenu (z-30) mais en dessous des modals (z-50)
  5. Nav desktop + mobile : desktop = flex hidden sm:flex, mobile = flex overflow-x-auto sm:hidden

Pourquoi

  • sticky est plus performant et moins bugué que fixed sur mobile
  • backdrop-filter crée une sensation de profondeur sans masquer le contenu
  • Le pattern desktop/mobile garantit l'accessibilité sur tous les écrans

Quand l'utiliser

  • Dashboard, SaaS, applications web avec navigation
  • Sites avec du contenu scrollable long

Quand NE PAS l'utiliser

  • Landing page one-page avec ancre scroll (pas besoin de sticky)
  • Si le support navigateur doit être IE11 (pas de backdrop-filter)

Références

  • ProjectAlpha/page.tsx L33-99