React ile Sekmeler (Tabs) Bileşeni Nasıl Oluşturulur?
Modern web uygulamalarında kullanıcı arayüzü (UI) tasarımının temel taşlarından biri olan sekmeler (tabs), içeriği düzenli bir şekilde sunarak kullanıcı deneyimini önemli ölçüde iyileştirir. Bir sayfada birden fazla bilgi grubunu aynı anda göstermek yerine, sekmeler sayesinde kullanıcılar ilgilendikleri içeriğe odaklanabilirler. Bu rehberde, React kullanarak hem işlevsel hem de erişilebilir bir sekmeler bileşenini adım adım nasıl oluşturacağımızı öğreneceğiz.
Bu rehberin sonunda, React’in temel prensiplerini kullanarak, yeniden kullanılabilir ve klavye navigasyonuna sahip, ARIA standartlarına uygun bir Tabs bileşeni inşa etmiş olacaksınız.
Tab Bileşeninin Temel Prensipleri
Bir sekmeler bileşeni oluştururken göz önünde bulundurmamız gereken birkaç temel prensip vardır:
1. Bileşen Modeli (Compound Component Pattern): Sekmeler genellikle Tabs, TabList, Tab ve TabPanel gibi birden fazla iç içe geçmiş bileşenden oluşur. Bu bileşenlerin birbirleriyle nasıl iletişim kurduğunu yönetmek için React’in Context API’sini kullanarak bir “bileşik bileşen” (compound component) yapısı oluşturacağız. Bu sayede, Tabs bileşeni dışarıdan esnek bir şekilde kullanılabilirken, iç mantığını kendi içinde tutacaktır.
2. Erişilebilirlik (Accessibility – ARIA): Sekmeler, klavye ile gezinme ve ekran okuyucu kullanıcıları için tam erişilebilir olmalıdır. Web İçeriği Erişilebilirlik Yönergeleri (WCAG) ve WAI-ARIA standartlarına uygun olarak role, aria-selected, aria-controls, aria-labelledby ve tabIndex gibi nitelikleri kullanacağız. Klavye ile sağ/sol ok tuşlarıyla sekmeler arasında geçiş yapabilme özelliği de bu kapsamda ele alınacaktır.
3. Durum Yönetimi (State Management): Hangi sekmenin aktif olduğunu ve hangi panelin görünür olması gerektiğini yönetmek için React’in useState hook’unu kullanacağız. Bu durum, Tabs ana bileşeninde tutulacak ve Context API aracılığıyla alt bileşenlere aktarılacaktır.
Adım 1: Bağlam Oluşturma ve Ana Tabs Bileşeni
Öncelikle, sekmeler bileşenimizin durumunu ve gerekli fonksiyonları alt bileşenlere aktarabilmek için bir React Context oluşturacağız. Ardından, bu bağlamı sağlayacak ana Tabs bileşenini inşa edeceğiz.
// src/components/Tabs.jsx
import React, { createContext, useState, useContext, useId, useMemo, useCallback } from 'react';
// 1. Sekmeler Bağlamını Oluşturma
const TabsContext = createContext(null);
// 2. Ana Tabs Bileşeni
export const Tabs = ({ children, defaultActiveTabId }) => {
// Benzersiz ID'ler oluşturmak için useId hook'unu kullanıyoruz (React 18+)
const tabsId = useId();
const [activeTabId, setActiveTabId] = useState(defaultActiveTabId || null);
// activeTabId değiştiğinde alt bileşenlere yeni bir değer geçirmek için useMemo kullanıyoruz.
const contextValue = useMemo(() => ({
activeTabId,
setActiveTabId,
tabsId, // Benzersiz bir kimlik sağlamak için
}), [activeTabId, tabsId]);
return (
{children}
);
};
Açıklama:
* createContext(null): TabsContext adında bir bağlam oluştururuz. Bu bağlam, aktif sekme ID’sini ve bu ID’yi güncelleyen fonksiyonu tutacaktır.
* Tabs bileşeni:
* useId(): React 18 ile gelen bu hook, tüm bileşen ağacında benzersiz ID’ler oluşturmak için kullanılır. Bu, özellikle ARIA nitelikleri (aria-labelledby, aria-controls) için kritik öneme sahiptir.
* useState(defaultActiveTabId || null): Hangi sekmenin aktif olduğunu tutan durum değişkenidir. defaultActiveTabId prop’u ile başlangıç sekmesi belirlenebilir.
* useMemo(...): Bağlam değerini optimize etmek için kullanılır. activeTabId veya tabsId değişmediği sürece contextValue yeniden hesaplanmaz.
* TabsContext.Provider: Oluşturulan bağlam değerini altındaki tüm bileşenlere sağlar.
Adım 2: TabList ve Tab Bileşenleri
Şimdi, sekmelerin başlıklarını içeren listeyi ve her bir sekme butonunu oluşturacağız. Bu bileşenler TabsContext‘i kullanarak ana Tabs bileşeninin durumuna erişecek ve onu güncelleyecek.
// src/components/Tabs.jsx (devam)
// ... (üstteki kodlar)
// 3. TabList Bileşeni
export const TabList = ({ children }) => {
const { tabsId } = useContext(TabsContext);
if (tabsId === null) {
throw new Error("TabList must be rendered inside a Tabs component.");
}
return (
{children}
);
};
// 4. Tab Bileşeni
export const Tab = ({ children, id, ...props }) => {
const { activeTabId, setActiveTabId, tabsId } = useContext(TabsContext);
if (tabsId === null) {
throw new Error("Tab must be rendered inside a Tabs component.");
}
const isSelected = activeTabId === id;
const tabId = ${tabsId}-${id}-tab;
const panelId = ${tabsId}-${id}-panel;
const handleClick = useCallback(() => {
setActiveTabId(id);
}, [id, setActiveTabId]);
const handleKeyDown = useCallback((event) => {
// Klavye navigasyonu (sağ/sol ok tuşları)
const tabList = event.currentTarget.closest('[role="tablist"]');
if (!tabList) return;
const tabs = Array.from(tabList.querySelectorAll('[role="tab"]'));
const currentIndex = tabs.findIndex(tab => tab.id === tabId);
if (event.key === 'ArrowRight') {
const nextTab = tabs[(currentIndex + 1) % tabs.length];
nextTab.focus();
setActiveTabId(nextTab.dataset.tabId); // dataset.tabId'yi kullanacağız
} else if (event.key === 'ArrowLeft') {
const prevTab = tabs[(currentIndex - 1 + tabs.length) % tabs.length];
prevTab.focus();
setActiveTabId(prevTab.dataset.tabId);
}
}, [tabId, setActiveTabId]);
return (
}
data-tab-id={id} // Klavye navigasyonu için kolay erişim
{...props}
>
{children}
);
};
Açıklama:
* TabList bileşeni:
* useContext(TabsContext) ile tabsId‘yi alır.
* role="tablist": Ekran okuyuculara bu elemanın bir sekmeler listesi olduğunu bildirir.
* aria-orientation="horizontal": Sekmelerin yatay düzende olduğunu belirtir (varsayılan değerdir ancak belirtmek iyidir).
* Tab bileşeni:
* id prop’u: Her sekmenin benzersiz bir kimliği olmalıdır (örneğin, “profile”, “settings”).
* isSelected: activeTabId ile kendi id‘sini karşılaştırarak aktif olup olmadığını belirler.
* tabId, panelId: useId ile oluşturulan genel tabsId‘yi kullanarak her sekme ve panel için benzersiz ve ilişkili ID’ler oluştururuz.
* handleClick: Sekmeye tıklandığında setActiveTabId‘yi çağırarak aktif sekmeyi günceller. useCallback ile performans optimize edilir.
* handleKeyDown: Klavye navigasyonunu sağlar. Sağ ve sol ok tuşlarına basıldığında, sıradaki veya önceki sekmeye odaklanır ve aktif sekmeyi günceller.
* role="tab": Ekran okuyuculara bu elemanın bir sekme olduğunu bildirir.
* aria-selected={isSelected}: Sekmenin seçili olup olmadığını belirtir.
* aria-controls={panelId}: Bu sekmenin hangi panelin içeriğini kontrol ettiğini belirtir.
* tabIndex={isSelected ? 0 : -1}: Aktif sekmenin klavye ile erişilebilir olmasını (sekme tuşu ile odaklanılabilir), diğerlerinin ise atlanmasını sağlar. Ok tuşları ile diğer sekmelere geçiş yapılabilir.
* data-tab-id={id}: Klavye navigasyonu sırasında kolayca id‘ye erişmek için kullanılır.
Adım 3: TabPanel Bileşeni
Son olarak, her bir sekmenin içeriğini barındıracak TabPanel bileşenini oluşturacağız. Bu bileşen de TabsContext‘i kullanarak yalnızca aktif sekmenin içeriğini gösterecektir.
// src/components/Tabs.jsx (devam)
// ... (üstteki kodlar)
// 5. TabPanel Bileşeni
export const TabPanel = ({ children, id, ...props }) => {
const { activeTabId, tabsId } = useContext(TabsContext);
if (tabsId === null) {
throw new Error("TabPanel must be rendered inside a Tabs component.");
}
const isSelected = activeTabId === id;
const tabId = ${tabsId}-${id}-tab;
const panelId = ${tabsId}-${id}-panel;
return (
tab-panel ${isSelected ? 'active' : ''}}
{...props}
>
{isSelected && children} {/ Sadece aktif olduğunda içeriği render et (opsiyonel) /}
);
};
Açıklama:
* TabPanel bileşeni:
* id prop’u: Bağlı olduğu Tab bileşeni ile aynı id‘ye sahip olmalıdır.
* isSelected: activeTabId ile kendi id‘sini karşılaştırarak aktif olup olmadığını belirler.
* role="tabpanel": Ekran okuyuculara bu elemanın bir sekme paneli olduğunu bildirir.
* id={panelId}: Tab bileşenindeki aria-controls ile eşleşen benzersiz bir ID sağlar.
* aria-labelledby={tabId}: Bu panelin hangi sekme başlığı tarafından etiketlendiğini belirtir.
* hidden={!isSelected}: Aktif olmayan panelleri HTML’in hidden niteliği ile gizler. Bu, ekran okuyucuların da bu içeriği görmesini engeller.
* {isSelected && children}: Bu, performans optimizasyonu için isteğe bağlı bir yaklaşımdır. Yalnızca aktif sekme paneli render edildiğinde içeriğini oluşturur. Eğer her zaman tüm panel içeriklerinin DOM’da olmasını isterseniz (örneğin, hızlı geçişler için), bu koşulu kaldırabilirsiniz.
Adım 4: Bileşenleri Kullanma ve Stilize Etme
Tüm bileşenlerimizi oluşturduktan sonra, bunları App.js veya başka bir üst bileşen içinde bir araya getirebiliriz. Basit bir stilizasyon da ekleyerek görünümünü iyileştirelim.
// src/App.js
import React from 'react';
import { Tabs, TabList, Tab, TabPanel } from './components/Tabs'; // Tabs.jsx dosyasından import ediyoruz
import './App.css'; // Stil dosyamızı ekleyelim
function App() {
return (
Profil
Ayarlar
Bildirimler
Profil Bilgileri
Adınız: John Doe
E-posta: john.doe@example.com
Bu sizin profil bilgilerinizin gösterildiği alandır.
Uygulama Ayarları
Dil Seçimi: Türkçe
Tema: Koyu
Ayarlarınızı buradan değiştirebilirsiniz.
Bildirim Tercihleri
E-posta Bildirimleri: Açık
SMS Bildirimleri: Kapalı
Bildirim ayarlarınızı yönetin.
Başka Bir Tabs Bileşeni
Ürünler
Hizmetler
Ürün Kataloğu
- Laptop
- Akıllı Telefon
- Tablet
Hizmetlerimiz
- Web Tasarımı
- SEO Danışmanlığı
- Mobil Uygulama Geliştirme
);
}
export default App;
/ src/App.css /
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f4f4f4;
color: #333;
}
.App {
max-width: 800px;
margin: 0 auto;
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
h1, h2, h3 {
color: #0056b3;
}
.tabs-container {
margin-bottom: 30px;
}
.tab-list {
display: flex;
border-bottom: 2px solid #ccc;
margin-bottom: 15px;
}
.tab-button {
padding: 12px 20px;
font-size: 16px;
border: none;
background-color: transparent;
cursor: pointer;
outline: none;
transition: all 0.3s ease;
color: #555;
border-bottom: 2px solid transparent; / Varsayılan olarak şeffaf /
}
.tab-button:hover {
background-color: #e0e0e0;
}
.tab-button.active {
color: #0056b3;
border-bottom: 2px solid #0056b3;
font-weight: bold;
}
.tab-button:focus {
box-shadow: 0 0 0 2px rgba(0, 86, 179, 0.5);
border-radius: 4px 4px 0 0;
}
.tab-content-wrapper {
padding: 15px;
border: 1px solid #eee;
border-top: none;
border-radius: 0 0 8px 8px;
background-color: #fcfcfc;
}
.tab-panel h2, .tab-panel h3 {
margin-top: 0;
color: #333;
}
.tab-panel p {
line-height: 1.6;
}
.tab-panel ul {
list-style-type: disc;
margin-left: 20px;
}
Kullanım Notları:
* Tabs bileşenine defaultActiveTabId prop’u ile başlangıçta hangi sekmenin aktif olacağını belirtebilirsiniz.
* Her Tab ve TabPanel bileşeninin aynı id prop’una sahip olması gerektiğini unutmayın. Bu, ARIA niteliklerinin doğru çalışması için kritik öneme sahiptir.
* Yukarıdaki örnekte iki farklı Tabs bileşeni kullanılmıştır. useId hook’u sayesinde her bir Tabs bileşeni kendi içindeki sekmeler için benzersiz ID’ler üretecek ve çakışma yaşanmayacaktır.
Pratik İpuçları ve İleri Seviye Konular
Bu temel yapıyı kurduktan sonra, sekmeler bileşeninizin yeteneklerini geliştirmek için bazı ileri seviye konuları düşünebilirsiniz:
* Animasyonlar: Sekmeler arasında geçiş yaparken daha akıcı bir deneyim için CSS geçişleri (transitions) veya animasyon kütüphaneleri (örneğin, Framer Motion, React Spring) kullanabilirsiniz.
* Lazy Loading (Tembel Yükleme): Eğer sekme panelleri çok fazla veya ağır içerik barındırıyorsa, TabPanel bileşenindeki {isSelected && children} koşulunu kullanarak sadece aktif sekmenin içeriğini DOM’a yükleyebilirsiniz. Bu, özellikle ilk yükleme süresini ve bellek kullanımını azaltabilir.
* URL Entegrasyonu: Sekme durumunu URL’deki hash (#) ile senkronize edebilirsiniz. Böylece kullanıcılar belirli bir sekme açıkken sayfayı yenileyebilir veya başkalarıyla paylaşabilir. Bu, useEffect ve window.location.hash kullanılarak yapılabilir.
* Customization (Özelleştirme): Bileşeninizi daha da esnek hale getirmek için render props veya daha gelişmiş bir compound component deseni kullanabilirsiniz. Örneğin, Tab ve TabPanel‘in render ettiği HTML elemanlarını (div, button, a) prop olarak almak gibi.
* Performans Optimizasyonu: React.memo kullanarak Tab veya TabPanel gibi bileşenlerin gereksiz yere yeniden render edilmesini engelleyebilirsiniz, özellikle çok sayıda sekmeniz varsa faydalı olabilir.
Sonuç ve Sıkça Sorulan Sorular (SSS)
Bu rehberde, React’in temel prensiplerini, Context API’sini ve useState, useId, useCallback, useMemo gibi hook’larını kullanarak erişilebilir ve yeniden kullanılabilir bir sekmeler bileşeni oluşturmayı öğrendik. Compound component deseni sayesinde, bileşenimiz hem güçlü hem de dışarıdan kolayca yapılandırılabilir hale geldi. Erişilebilirlik odaklı yaklaşımımızla, her kullanıcının bu bileşeni sorunsuz bir şekilde kullanabilmesini sağladık.
Sıkça Sorulan Sorular (SSS)
1. Neden ARIA nitelikleri kullanmalıyız?
ARIA (Accessible Rich Internet Applications) nitelikleri, web içeriklerini ve uygulamalarını engelli kişilerin daha erişilebilir hale getirmek için kullanılır. Ekran okuyucular gibi yardımcı teknolojiler, bu nitelikleri kullanarak bir UI elemanının amacını, durumunu ve özelliklerini kullanıcıya iletir. Sekmeler gibi dinamik UI bileşenlerinde, ARIA nitelikleri olmadan ekran okuyucu kullanıcıları hangi sekmenin seçili olduğunu, hangi içeriğin gösterildiğini veya sekmeler arasında nasıl gezineceklerini anlamakta zorlanabilirler.
2. Her tab içeriğini render etmek performansı etkiler mi?
Evet, eğer tüm sekme panellerinin içeriği DOM’da her zaman mevcutsa, bu özellikle çok sayıda sekmeniz veya her panelde çok karmaşık/ağır içerik varsa performansı etkileyebilir. Bu durumda, TabPanel içinde {isSelected && children} gibi koşullu renderlama yaparak sadece aktif sekmenin içeriğini DOM’a yüklemek (lazy loading) performansı artırabilir. Ancak, çok sık sekme değiştiren kullanıcılar için içeriklerin anında görünmesi daha iyi bir deneyim sunabilir. Bu bir denge meselesidir ve uygulamanızın özel ihtiyaçlarına göre karar verilmelidir.
3. Tab bileşenini nasıl stilize edebilirim?
Sekmeler bileşenini CSS modülleri, Styled Components, Emotion, Tailwind CSS veya plain CSS gibi çeşitli yöntemlerle stilize edebilirsiniz. Bu rehberde App.css ile basit bir stilizasyon örneği verilmiştir. Önemli olan, .tab-list, .tab-button, .tab-button.active, .tab-panel gibi sınıflar kullanarak ilgili elemanlara stiller uygulamaktır.
4. Tab içeriğini dışarıdan (API’den) nasıl yükleyebilirim?
Eğer sekme içeriği bir API’den geliyorsa, TabPanel bileşeninizin içinde useEffect hook’unu kullanarak veri çekme işlemini gerçekleştirebilirsiniz. useEffect içinde, activeTabId değiştiğinde (yani ilgili sekme aktif olduğunda) API çağrısını tetikleyebilir ve veriyi bileşenin yerel durumuna kaydedebilirsiniz. Bu, “lazy loading” yaklaşımıyla birleştirilerek yalnızca ihtiyaç duyulduğunda veri çekilmesini sağlar.
5. Hangi durumlarda tab yerine başka bir UI elemanı tercih etmeliyim?
Sekmeler, birbirinden bağımsız ancak tematik olarak ilişkili içerik bloklarını düzenlemek için idealdir. Ancak:
* Eğer içerikler arasında sıkça geçiş yapılması gerekiyorsa ve kullanıcıların tüm içeriği aynı anda görmesi faydalıysa, akordeon (accordion) veya doğrudan bölümler (sections) daha iyi olabilir.
* Eğer içerikler çok az ve tek bir ekranda kolayca sığabiliyorsa, sekmeler gereksiz bir karmaşıklık yaratabilir.
* Eğer içerikler hiyerarşik bir yapıya sahipse, ağaç görünümü (tree view) veya navigasyon menüleri daha uygun olabilir.
Unutmayın, en iyi UI elemanını seçmek her zaman kullanıcı ihtiyaçlarını ve içerik yapısını anlamaktan geçer.