Menu Lain
Daftar BacaGanti TemaCari
Reading List

Queue · 0 items

Daftar baca Anda kosong. Simpan artikel untuk membacanya nanti.

Start Reading

Membangun Header Pil Mengambang dengan Next.js & Tailwind

Iwan Efendi2 min

Bedah teknis mendalam tentang cara membuat header mengambang responsif dengan Next.js, React Hooks, dan Tailwind CSS.

This article is available in EnglishRead in English →
Read in EnglishEN
Jika kamu menjelajahi situs ini, kamu kemungkinan besar telah berinteraksi dengan header "pil" mengambang di bagian atas halaman. Komponen ini dirancang minimalis namun bertenaga, menyatukan navigasi, pencarian, dan fitur daftar bacaan dalam satu wadah yang ringkas dan responsif. Dalam studi kasus ini, saya akan membedah bagaimana ia dibangun dengan fokus pada tantangan UX, batasan desain, dan implementasi teknisnya.

Studi Kasus: Tantangan UX & Target Desain

Sebelum masuk ke kode, mari kita bahas masalah utama yang ingin diselesaikan oleh header ini:
  1. Kepadatan Informasi vs. Ruang Layar: Header harus menyediakan akses cepat ke halaman utama, pencarian, dan daftar bacaan, tetapi tidak boleh memenuhi layar (terutama pada perangkat seluler).
  2. Transisi UI yang Dinamis: Pelebaran bilah pencarian (search bar) atau pembukaan menu dropdown harus terasa mulus tanpa pergeseran tata letak yang kasar atau patah-patah.
  3. Perilaku Berbasis Scroll: Navigasi harus menyingkir saat pengguna sedang membaca (scroll ke bawah), tetapi langsung muncul kembali saat mereka membutuhkannya (scroll ke atas).
  4. Kontras Visual: Dalam tata letak SnipGeek yang kaku dan tajam, header berfungsi sebagai elemen melayang unik yang terasa ringan dan dinamis.

Teknologi Utama

  • Next.js App Router: Untuk struktur komponen dan rendering sisi server.
  • React Hooks: Terutama useState, useEffect, dan useRef untuk mengelola state, efek samping, dan referensi DOM langsung.
  • Tailwind CSS: Untuk semua penataan gaya, memungkinkan desain cepat berbasis utilitas. Kami juga menggunakan clsx untuk penerapan class secara kondisional.
  • Lucide React: Untuk penyediaan ikon.

Langkah 1: Struktur Dasar & Posisi Mengambang

Fondasi utamanya adalah elemen <header> dengan properti position: fixed.
<header className="fixed top-4 left-4 right-4 md:left-1/2 md:-translate-x-1/2 ... z-50">
  <nav className="relative ... rounded-full bg-primary/90 backdrop-blur-sm ...">
    {/* Konten di sini */}
  </nav>
</header>
  • fixed top-4: Menempelkan header di dekat bagian atas layar.
  • left-4 right-4: Di layar seluler, header merentang hampir di seluruh lebar.
  • md:left-1/2 md:-translate-x-1/2: Di layar sedang ke atas, header memposisikan dirinya tepat di tengah secara horizontal.
  • bg-primary/90 backdrop-blur-sm: Menciptakan efek kaca buram yang semi-transparan.
  • rounded-full: Memberikan bentuk "pil" yang ikonik.

Langkah 2: Otak - Manajemen Tampilan (View State)

Bagian paling krusial adalah mengelola apa yang sedang ditampilkan. Saya menggunakan satu hook useState untuk ini:
type ActiveView = 'none' | 'search' | 'menu' | 'readingList';
const [activeView, setActiveView] = useState<ActiveView>('none');
Setiap elemen interaktif di header cukup memanggil setActiveView untuk mengubah tampilan. Contohnya, mengklik ikon pencarian akan mengubah state ke 'search', yang kemudian memicu rendering kondisional untuk input pencarian dan panel hasil. Bentuk dan gaya komponen juga bereaksi terhadap state ini. Saat isSearchOpen aktif, lebar nav melebar. Saat isMenuOpen aktif, radius sudut berubah dari rounded-full menjadi rounded-t-2xl agar menyatu dengan panel menu di bawahnya. Satu hal penting yang perlu dicatat: karena header adalah Client Component, ia berada di luar pohon React Server Component yang menggerakkan sisa halaman. Jika kamu penasaran bagaimana batas tersebut bekerja di level protokol, artikel Kenapa AI Crawler Tidak Bisa Baca Site Next.js App Router menjelaskan dengan baik bagaimana format RSC Flight memisahkan rendering server dan klien.

Langkah 3: Pengalaman Pencarian Dinamis

Fungsionalitas pencarian adalah fitur kunci.
  1. Transisi State: Mengklik ikon cari mengatur activeView = 'search'.
  2. Animasi Kondisional: Class CSS beralih menggunakan clsx. Link navigasi utama memudar (opacity-0) sementara wadah input pencarian muncul (opacity-100).
  3. Auto-focus: Hook useEffect berjalan saat tampilan pencarian aktif, secara otomatis memfokuskan kursor ke elemen <input> demi kenyamanan pengguna.
    const searchInputRef = useRef<HTMLInputElement>(null);
    
    useEffect(() => {
      if (isSearchOpen) {
        // Timeout singkat memastikan elemen terlihat sebelum difokuskan
        setTimeout(() => searchInputRef.current?.focus(), 100);
      }
    }, [isSearchOpen]);
  4. Penyaringan Real-time: useEffect lain memantau perubahan pada state query. Ia menyaring array data postingan dan catatan untuk menampilkan hasil secara instan.

Langkah 4: Visibilitas Pintar Saat Scroll

Pola UX yang umum adalah menyembunyikan navigasi saat scroll ke bawah dan menampilkannya saat scroll ke atas. Ini dicapai dengan hook useEffect lainnya.
const lastScrollY = useRef(0);
const [isVisible, setIsVisible] = useState(true);

useEffect(() => {
  const handleScroll = () => {
    const currentScrollY = window.scrollY;
    // Tampilkan jika scroll ke atas atau dekat puncak halaman
    if (currentScrollY < lastScrollY.current || currentScrollY < 10) {
      setIsVisible(true);
    } else if (currentScrollY > 100 && currentScrollY > lastScrollY.current) {
      // Sembunyikan jika scroll ke bawah melewati ambang batas tertentu
      setIsVisible(false);
    }
    lastScrollY.current = currentScrollY;
  };

  window.addEventListener('scroll', handleScroll, { passive: true });
  return () => window.removeEventListener('scroll', handleScroll);
}, []);
State isVisible kemudian menambah atau menghapus class yang mengontrol opacity dan transform pada header.

Langkah 5: Detail Kecil yang Penting

  • Tombol Escape: Event listener global disiapkan untuk menutup tampilan aktif (cari, menu, dll.) saat tombol 'Escape' ditekan.
  • Klik di Luar: Listener serupa menutup tampilan jika pengguna mengklik di area mana pun di luar komponen header.
  • Responsivitas: Komponen bertransisi mulus dari header mobile lebar penuh ke header desktop yang ringkas dan terpusat menggunakan prefix responsif Tailwind (md:).
Jika terjadi error saat build setelah mengubah komponen header ini, panduan Cara Membaca Log Build Failed dari AI adalah referensi praktis untuk membaca output error dan menemukan akar masalahnya dengan cepat.

Update Desain: Precision Tech (27 Februari 2026)

Sebagai bagian dari perombakan desain SnipGeek "Precision Tech", saya telah mengubah sebagian besar komponen situs—seperti kartu artikel, blok kode, dan gambar—menjadi sudut tajam 4px radius. Namun, saya sengaja memutuskan untuk tetap mempertahankan bentuk "Pil" (Full Rounded) pada header. Ini menciptakan kontras visual yang kuat, memastikan navigasi tetap menjadi elemen unik yang "melayang" dan terasa ringan di atas tata letak konten yang lebih teknis dan terstruktur. Ini adalah catatan sejarah evolusi desain SnipGeek.

FAQ

T: Mengapa menggunakan satu state activeView daripada beberapa boolean seperti isSearchOpen, isMenuOpen, dll.? J: Satu state berbentuk enum membuat saling-eksklusivitas menjadi eksplisit dan otomatis terjamin. Dengan beberapa flag boolean, kamu berisiko menghadapi kondisi seperti isSearchOpen: true dan isMenuOpen: true aktif bersamaan, yang menciptakan kondisi UI yang mustahil. Union type ('none' | 'search' | 'menu' | 'readingList') memastikan hanya satu tampilan yang bisa aktif setiap saat, dan TypeScript menegakkannya saat kompilasi. T: Mengapa menambahkan timeout 100ms sebelum auto-focus pada input pencarian? J: Timeout tersebut mengatasi race condition dalam siklus rendering browser. Ketika activeView berubah ke 'search', React menjadwalkan re-render, tetapi elemen mungkin belum selesai di-paint (dan karena itu belum bisa difokuskan) saat effect berjalan secara sinkron. Jeda singkat memberi waktu browser untuk menyelesaikan layout sebelum .focus() dipanggil. Alternatifnya adalah menggunakan useLayoutEffect dengan requestAnimationFrame, tetapi timeout lebih sederhana dan andal di semua browser utama. T: Apakah opsi { passive: true } pada scroll listener benar-benar penting? J: Ya, sangat penting untuk performa. Menandai listener sebagai passive memberi tahu browser sejak awal bahwa preventDefault() tidak akan pernah dipanggil, sehingga browser dapat menangani scroll di compositor thread tanpa menunggu JavaScript merespons. Mengabaikannya pada halaman dengan banyak scroll dapat menyebabkan jank, terutama di perangkat mobile. Selalu gunakan { passive: true } untuk listener scroll dan touch yang tidak perlu memblokir perilaku default. T: Bagaimana cara mencegah listener "klik di luar" agar tidak berjalan di setiap klik dan menurunkan performa? J: Listener hanya dipasang saat suatu tampilan sedang aktif dan dihapus saat semuanya tertutup. Array dependensi useEffect menyertakan activeView, sehingga listener dibersihkan dan didaftarkan ulang hanya saat state tersebut berubah—bukan di setiap render. Karena handler memeriksa headerRef.current?.contains(event.target) sebelum melakukan apapun, bahkan saat terpasang pun ia langsung berhenti untuk sebagian besar klik yang mendarat di dalam header.

Referensi

Topics

Topik dalam catatan

Jelajahi pembahasan serupa lewat topik-topik terkait berikut.

Bagikan artikel ini

Diskusi

Menyiapkan area komentar...

Anda Mungkin Juga Suka