Wzorzec 10
Zakładki bez windy
Każda zakładka niesie inną ilość treści, więc naiwny panel zmienia wysokość przy każdym przełączeniu i wszystko pod nim faluje. Wystarczy położyć wszystkie panele w jednej komórce siatki: nieaktywne są niewidzialne, ale wciąż liczą się do wysokości.
Te same zakładki, dwa panele
Jeden pasek steruje oboma panelami. Lewy renderuje tylko aktywną treść i faluje, prawy trzyma wysokość najdłuższej zakładki.
✕ Wysokość za aktywną zakładką
Rebranding studia Atlas: nowa identyfikacja, strona i system szablonów dla zespołu marketingu.
Obserwuj tę linię przy przełączaniu
✓ Rezerwa najwyższego panelu
Rebranding studia Atlas: nowa identyfikacja, strona i system szablonów dla zespołu marketingu.
- Księga znaku z wariantami monochromatycznymi.
- Strona na Astro z wyspami interaktywności.
- Biblioteka Figmy ze zmiennymi zmapowanymi na tokeny CSS.
- Szablony ofert i prezentacji w trzech formatach.
- Testy Core Web Vitals w CI jako budżet wydajności.
Start w marcu 2026, warsztaty tożsamości w kwietniu, publikacja biblioteki w maju. Wdrożenie strony zaplanowane na sierpień.
Obserwuj tę linię przy przełączaniu
Reguły
- Wszystkie panele mieszkają w jednej komórce grid, aktywny jest widoczny, reszta niewidzialna.
- Wysokość sekcji to maksimum ze wszystkich paneli, policzone przez przeglądarkę, nie przez skrypt.
- Nieaktywne panele mają visibility hidden oraz inert, więc są poza fokusem i czytnikami.
- Przełączenie zakładki nie przesuwa niczego poza zawartością panelu.
Wzorzec w kodzie
<div className="grid">
{tabs.map((tab) => (
<div
key={tab.id}
role="tabpanel"
inert={active !== tab.id}
className={
active === tab.id
? "col-start-1 row-start-1"
: "col-start-1 row-start-1 invisible"
}
>
{tab.content}
</div>
))}
</div><button
role="tab"
aria-selected={active === tab.id}
aria-controls={"panel-" + tab.id}
onClick={() => setActive(tab.id)}
>
{tab.name}
</button>