Lekcja 34

Container queries

Container queries pozwalają stylować komponent zależnie od rozmiaru jego kontenera, a nie tylko od szerokości okna przeglądarki. Dzięki temu ten sam komponent może wyglądać inaczej w sidebarze, siatce kart i szerokim panelu dashboardu.

Cele lekcji

Po ukończeniu tej lekcji będziesz umieć:

  • wyjaśnić różnicę między @media i @container,
  • ustawić element jako kontener przez container-type,
  • użyć nazwanego kontenera przez container-name,
  • pisać zapytania @container oparte o szerokość rodzica,
  • stosować jednostki cqi, cqb, cqmin i cqmax,
  • projektować komponenty bardziej niezależne od konkretnego layoutu strony.

Kontekst praktyczny

Media queries odpowiadają na pytanie: „jak szeroki jest viewport?”. Container queries odpowiadają na pytanie: „ile miejsca ma ten komponent?”. To ważna różnica, bo karta produktu może mieć 280px w szerokim desktopie, jeśli stoi w sidebarze, albo 620px w tym samym viewportcie, jeśli stoi w głównej kolumnie.

W praktyce: container queries pomagają pisać komponenty, które są przenośne. Komponent nie musi wiedzieć, czy jest na stronie głównej, w dashboardzie czy w modalu. Reaguje na przestrzeń, którą faktycznie dostał.

Media vs container

@media nadal jest potrzebne do decyzji na poziomie całej strony: głównej siatki, globalnej nawigacji czy dużych zmian layoutu. @container lepiej pasuje do komponentów wielokrotnego użycia.

/* Decyzja strony */
@media (min-width: 64rem) {
  .page-layout {
    grid-template-columns: 18rem 1fr;
  }
}

/* Decyzja komponentu */
@container (min-width: 32rem) {
  .product-card {
    grid-template-columns: 12rem 1fr;
  }
}

Dobry podział: layout strony reaguje na viewport, a komponent reaguje na swój kontener.

container-type

Element musi zostać oznaczony jako kontener, zanim jego dzieci będą mogły reagować przez @container. Najczęściej użyjesz inline-size, czyli obserwacji szerokości w osi inline.

.card-shell {
  container-type: inline-size;
}

.card {
  display: grid;
  gap: 1rem;
}

Kontenerem jest zwykle wrapper komponentu albo region layoutu. Sam element stylowany wewnątrz @container musi być potomkiem tego kontenera.

container-name

Gdy w komponencie lub stronie masz kilka kontenerów, możesz nadać im nazwy. Dzięki temu zapytanie odnosi się do konkretnego kontekstu, a nie do najbliższego kontenera.

.dashboard-panel {
  container-type: inline-size;
  container-name: dashboard-panel;
}

@container dashboard-panel (min-width: 42rem) {
  .metric-card {
    grid-template-columns: auto 1fr auto;
  }
}

Nazwy są przydatne w bardziej złożonych layoutach, ale w małym komponencie często wystarczy najbliższy kontener bez nazwy.

@container

Reguła @container działa podobnie do @media, ale warunek dotyczy kontenera. Najczęstszy wzorzec to komponent, który domyślnie jest pionowy, a po przekroczeniu szerokości kontenera przechodzi w układ poziomy.

.profile-card {
  display: grid;
  gap: 1rem;
}

@container (min-width: 30rem) {
  .profile-card {
    grid-template-columns: 7rem 1fr;
    align-items: center;
  }
}

To pozwala użyć tej samej karty w wąskiej kolumnie i szerokim panelu bez dopisywania klas typu .profile-card--wide zależnych od strony.

Jednostki container query

Container queries mają własne jednostki. cqi oznacza 1% rozmiaru kontenera w osi inline, cqb w osi block, a cqmin i cqmax biorą mniejszy albo większy wymiar kontenera.

.card-title {
  font-size: clamp(1rem, 6cqi, 1.6rem);
}

.card-media {
  border-radius: clamp(.5rem, 3cqi, 1.25rem);
}

Jednostki kontenerowe są przydatne, gdy chcesz skalować typografię, promień albo odstępy względem miejsca dostępnego dla komponentu.

Fallback i wsparcie

Projektuj komponent tak, aby domyślny styl był poprawny bez zapytania kontenerowego. Potem użyj @container jako progresywnego ulepszenia. Dzięki temu komponent nie rozpada się, jeśli zapytanie nie zadziała.

.media-card {
  display: grid;
  gap: 1rem;
}

@container (min-width: 36rem) {
  .media-card {
    grid-template-columns: minmax(10rem, .4fr) 1fr;
  }
}

Najbezpieczniejszy fallback to wersja mobile-first: pionowa, czytelna i bez założeń o szerokości.

Demo container queries

Ten sam komponent znajduje się w dwóch różnych kontenerach. Wąski kontener pokazuje układ pionowy, a szeroki kontener uruchamia regułę @container i zmienia kartę na układ poziomy.

Kontener 280px

Karta kursu

Komponent nie ma miejsca, więc zachowuje pionowy układ.

Zobacz zadanie
Kontener 560px

Karta kursu

Ten sam komponent dostaje więcej miejsca i sam przełącza układ przez @container.

Zobacz zadanie

Najczęstsze błędy początkujących

  • Próba użycia @container bez ustawienia container-type na rodzicu.
  • Stylowanie samego kontenera w zapytaniu, zamiast jego potomków.
  • Używanie container queries do decyzji globalnych, które nadal powinny być w @media.
  • Nadawanie nazw wszystkim kontenerom, nawet gdy wystarcza najbliższy kontener.
  • Brak sensownego stylu bazowego przed zapytaniem kontenerowym.
  • Tworzenie zbyt wielu progów dla jednego komponentu zamiast prostych, czytelnych zmian.

Mini zadanie

Zbuduj kartę profilu, która reaguje na rozmiar kontenera.

  • Utwórz wrapper .profile-card-shell i ustaw na nim container-type: inline-size.
  • Zaprojektuj bazową kartę jako pionowy układ mobile-first.
  • Dodaj @container (min-width: 32rem), który przełączy kartę na dwie kolumny.
  • Użyj cqi do płynnego rozmiaru tytułu lub odstępów.
  • Sprawdź kartę w sidebarze i w szerokiej sekcji bez dodawania klas wariantów.
  • Zostaw styl bazowy czytelny nawet wtedy, gdy zapytanie kontenerowe się nie uruchomi.

Checklista

  • Rozumiem, że @container reaguje na kontener, a nie viewport.
  • Potrafię wskazać element, na którym trzeba ustawić container-type.
  • Wiem, kiedy użyć nazwanego kontenera.
  • Potrafię zbudować komponent z bazowym układem i ulepszeniem przez @container.
  • Rozumiem podstawowe jednostki cqi, cqb, cqmin i cqmax.
  • Nie zastępuję wszystkich media queries przez container queries.

Podsumowanie lekcji

Container queries przenoszą responsywność bliżej komponentu. Dzięki nim komponent może być odporny na różne konteksty layoutu i reagować na realną przestrzeń, którą otrzymuje od rodzica.

Kolejny krok roadmapy to logical properties.

Przejdź do Lekcji 35