liquiddesign

Wzorzec 26

Scroll, który zna granice

Gest przewijania, który dojedzie do końca zagnieżdżonej listy, domyślnie płynie dalej i rusza stroną pod spodem. W dialogu, menu i czacie ten łańcuch odbiera użytkownikowi kontekst, a blokowanie go skryptem dopisującym overflow: hidden do body psuje pozycję przewinięcia. Jedna deklaracja overscroll-behavior przecina łańcuch tam, gdzie panel się kończy.

Koniec listy to koniec gestu

Przewiń obie listy do samego końca i nie przerywaj gestu. Lewa przekazuje resztę ruchu stronie, która ucieka w dół. Prawa ma overscroll-behavior: contain, więc gest zatrzymuje się na krawędzi panelu.

✕ Łańcuch przewijania

  • Skeleton dzieli layout z treścią, którą zapowiada.
  • Slot na błąd czeka pod polem od pierwszego renderu.
  • Przycisk trzyma szerokość podczas wysyłki.
  • Kolumny siatki wynikają z minmax, nie z breakpointów.
  • Subgrid wyrównuje stopki sąsiednich kart.
  • Dialog otwiera showModal, nie div z z-index.
  • Toast żyje w swojej warstwie nad treścią.
  • Debounce ogranicza zapytania wyszukiwarki.
  • Obraz deklaruje wymiary, zanim przyjdzie plik.
  • Panel zakładek trzyma wysokość najwyższej treści.
  • Akordeon animuje grid-template-rows, nie height.
  • Cyfry tabelaryczne trzymają szerokość licznika.
  • Karuzela jedzie na scroll-snap bez biblioteki.
  • Walidację formatu robi :user-invalid, nie skrypt.
  • Dymek trzyma się kotwicy czystym CSS.

✓ overscroll-behavior: contain

  • Skeleton dzieli layout z treścią, którą zapowiada.
  • Slot na błąd czeka pod polem od pierwszego renderu.
  • Przycisk trzyma szerokość podczas wysyłki.
  • Kolumny siatki wynikają z minmax, nie z breakpointów.
  • Subgrid wyrównuje stopki sąsiednich kart.
  • Dialog otwiera showModal, nie div z z-index.
  • Toast żyje w swojej warstwie nad treścią.
  • Debounce ogranicza zapytania wyszukiwarki.
  • Obraz deklaruje wymiary, zanim przyjdzie plik.
  • Panel zakładek trzyma wysokość najwyższej treści.
  • Akordeon animuje grid-template-rows, nie height.
  • Cyfry tabelaryczne trzymają szerokość licznika.
  • Karuzela jedzie na scroll-snap bez biblioteki.
  • Walidację formatu robi :user-invalid, nie skrypt.
  • Dymek trzyma się kotwicy czystym CSS.

Ta sama deklaracja chroni dialogi, menu i czaty. Strona pod spodem nie potrzebuje skryptu dopisującego overflow: hidden do body, bo łańcuch jest przecięty u źródła.

Reguły

  • Każdy przewijany panel w warstwie — dialog, menu, szuflada, czat — ma overscroll-behavior: contain.
  • Dojechanie do krawędzi listy zatrzymuje gest, nie przekazuje go stronie pod spodem.
  • contain zamiast none, dopóki pull-to-refresh i efekty krawędzi mają działać w samym panelu.
  • Strona pod modalem nie potrzebuje skryptu blokującego body, wystarczy przecięty łańcuch przewijania.

Wzorzec w kodzie

Łańcuch przecina jedna deklaracja na panelu
dialog,
[popover],
.panel-czatu,
.menu-przewijane {
  overscroll-behavior: contain;
}
Antywzorzec: skrypt blokujący body gubi pozycję przewinięcia
function openModal() {
  document.body.style.overflow = "hidden";
}

function closeModal() {
  document.body.style.overflow = "";
}

Prompt dla Claude

Wklej do Claude Code w swoim projekcie. Prompt niesie komplet reguł tej lekcji i ogólne zasady Liquid Design, więc implementacja trafia w metodologię bez tłumaczenia jej od zera.

Prompt do wklejenia
Zaimplementuj w moim projekcie wzorzec „Scroll, który zna granice" z metodologii Liquid Design.

overscroll-behavior: contain zatrzymuje przewijanie na krawędzi panelu, więc koniec listy w dialogu nie przewija strony pod spodem.

Wymagania:
- Każdy przewijany panel w warstwie — dialog, menu, szuflada, czat — ma overscroll-behavior: contain.
- Dojechanie do krawędzi listy zatrzymuje gest, nie przekazuje go stronie pod spodem.
- contain zamiast none, dopóki pull-to-refresh i efekty krawędzi mają działać w samym panelu.
- Strona pod modalem nie potrzebuje skryptu blokującego body, wystarczy przecięty łańcuch przewijania.

Ogólne reguły Liquid Design, których implementacja nie może złamać:
- HTML jest semantyczny: button, a, nav, form, label, dialog, details, ul, dl i nagłówki h1-h6 zamiast div z onClick i ARIA dopisywanym ręcznie. div i span służą wyłącznie do layoutu, nigdy do interakcji ani struktury treści.
- Layout jest umową: treść, która dociera później, ma miejsce zarezerwowane od pierwszego renderu. Nic nie skacze.
- Stany ładowania, pustki, błędu i treści dzielą jeden layout.
- Typografia pochodzi z nazwanej, płynnej skali opartej o clamp(), nie z gołych rozmiarów.
- Animacje dotyczą wyłącznie transform i opacity, a prefers-reduced-motion redukuje je do natychmiastowej zmiany stanu.
- Breakpoint jest ostatecznością: najpierw container queries i repeat(auto-fit, minmax(...)).

Sposób pracy — zanim napiszesz pierwszą linię kodu:
- Sprawdź w plikach projektu, jak stylowane są komponenty (Tailwind, CSS Modules, vanilla-extract, styled-components, Sass, czysty CSS...) i pisz wyłącznie w tej konwencji. Niczego nie zakładaj z góry i nie dodawaj nowych zależności.
- Wymagania powyżej opisują właściwości CSS i atrybuty HTML, nie klasy narzędziowe. Przełóż je na system stylowania zastany w projekcie.
- Sprawdź framework komponentów, wersję i konwencje nazewnictwa, zamiast zakładać konkretny stack.
- Używaj istniejących tokenów projektu (kolory, typografia, odstępy). Jeśli czegoś brakuje, zaproponuj minimalne uzupełnienie w duchu istniejącego kodu, nie osobny system.
- Nie używaj klas, tokenów ani API, których nie znalazłeś w tym projekcie.

Pełny opis z żywym demo i kodem: https://liquid-design.website/wzorce/scroll-bez-lancucha