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.
Read in EnglishEN

Zoom
Studi Kasus: Tantangan UX & Target Desain
- 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).
- 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.
- Perilaku Berbasis Scroll: Navigasi harus menyingkir saat pengguna sedang membaca (scroll ke bawah), tetapi langsung muncul kembali saat mereka membutuhkannya (scroll ke atas).
- 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, danuseRefuntuk mengelola state, efek samping, dan referensi DOM langsung. - Tailwind CSS: Untuk semua penataan gaya, memungkinkan desain cepat berbasis utilitas. Kami juga menggunakan
clsxuntuk 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 hookuseState untuk ini:
type ActiveView = 'none' | 'search' | 'menu' | 'readingList';
const [activeView, setActiveView] = useState<ActiveView>('none');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.- Transisi State: Mengklik ikon cari mengatur
activeView = 'search'. - Animasi Kondisional: Class CSS beralih menggunakan
clsx. Link navigasi utama memudar (opacity-0) sementara wadah input pencarian muncul (opacity-100). - Auto-focus: Hook
useEffectberjalan 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]); - Penyaringan Real-time:
useEffectlain memantau perubahan pada statequery. 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 hookuseEffect 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);
}, []);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:).
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
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
- Tailwind CSS — Responsive Design — Dokumentasi resmi yang mencakup prefix
md:dan sistem breakpoint yang digunakan di seluruh header. - React Docs — useRef — Referensi untuk
useRef, mencakup DOM refs (pelacakan scroll, fokus input) dan variabel instance yang bisa diubah. - MDN — Opsi passive pada EventTarget.addEventListener() — Menjelaskan opsi
{ passive: true }dan dampaknya terhadap performa scroll. - clsx di npm — Utilitas ringan untuk membangun string
classNamesecara kondisional, digunakan secara luas dalam logika animasi header.
Topics
Topik dalam catatan
Jelajahi pembahasan serupa lewat topik-topik terkait berikut.
Bagikan artikel ini
Diskusi
Menyiapkan area komentar...