Platforma webowa dogoniła biblioteki – co możesz dziś usunąć z projektu
W poprzednim wpisie o kalendarzu napisałem zdanie, które samo prosi się o osobny artykuł: „platforma webowa dogoniła i wyprzedziła to, po co kiedyś sięgaliśmy po jQuery”. To nie był przypadek dotyczący jednej biblioteki. To reguła całej dekady front-endu – i właśnie teraz, w 2026, widać ją najwyraźniej.
Przez lata pisaliśmy łatki i importowaliśmy zależności, bo przeglądarka nie umiała czegoś sama: jQuery zasypywało niespójności DOM, moment.js ratował przed Date, lodash dawał porządne operacje na kolekcjach, Popper.js pozycjonował tooltipy, jQuery UI dokładało datepicker. Schemat był zawsze ten sam: łatka → biblioteka → standard → Baseline. Hack stawał się paczką npm, paczka inspirowała specyfikację, specyfikacja lądowała w przeglądarkach. Nowość polega na tym, że w latach 2023–2026 ostatni etap odpalił lawinowo – i to często tak, że CSS wyprzedził JavaScript: rzeczy, które robiliśmy skryptem, robi dziś sam arkusz stylów.
Ten wpis to przegląd „co możesz dziś usunąć z projektu” – z przykładami before/after. Na końcu jest sekcja uczciwości: gdzie platforma jeszcze nie dogoniła i czego nie wrzucać na produkcję w ciemno.
Najpierw: czym jest Baseline
Żeby ten wpis nie był listą życzeń, każdą funkcję opisuję przez pryzmat Baseline – wspólnego standardu Google, Mozilli, Apple i Microsoftu, który mówi, od kiedy dana funkcja działa „wszędzie”. Są dwa progi:
- Newly available – funkcja działa w najnowszych wersjach wszystkich głównych silników (Chrome/Edge, Firefox, Safari).
- Widely available – minęło ~2,5 roku od „newly”, więc można jej używać bez zastanowienia.
Zanim dorzucisz do projektu kolejną zależność, sprawdź na MDN lub caniuse.com status Baseline tego, co chcesz nią załatać. Często okazuje się, że paczka jest już zbędna.
Stan na czerwiec 2026. Daty i statusy Baseline podaję według stanu z chwili pisania. Front-end zmienia się szybko – przy wdrożeniu zawsze potwierdź aktualny status na MDN/caniuse, zwłaszcza dla funkcji oznaczonych niżej jako „świeże".
Część 1. CSS, który zjadł JavaScript
:has() – selektor rodzica, czyli koniec klas dorzucanych skryptem
Najczęstszy powód, dla którego pisaliśmy JavaScript do stylowania, brzmiał: „gdy element ma w środku X, ostyluj jego rodzica”. CSS nie potrafił patrzeć w górę drzewa, więc dorzucaliśmy klasę skryptem:
// Kiedyś: JS tylko po to, żeby ostylować rodzica
document.querySelectorAll('.card').forEach((card) => {
if (card.querySelector('img')) card.classList.add('has-image');
});Dziś robi to jedna reguła – :has() to pełnoprawny selektor rodzica (Baseline newly available od grudnia 2023):
/* Karta, która zawiera obrazek */
.card:has(img) { padding-top: 0; }
/* Formularz z błędem – bez ani jednej linijki JS */
form:has(:invalid) button[type="submit"] { opacity: 0.5; }
/* "Light/dark" zależne od checkboxa, czysto w CSS */
body:has(#dark-toggle:checked) { color-scheme: dark; }To pojedyncza zmiana, która kasuje całe klasy mikro-skryptów do togglowania stanu.
Container queries – komponent responsywny względem siebie, nie okna
Media queries reagują na okno. Ale prawdziwie reużywalny komponent powinien reagować na miejsce, w którym go umieszczono. Kiedyś znaczyło to ResizeObserver (a wcześniej window.resize) i ręczne przeliczanie szerokości w JS. Dziś:
.card-list { container-type: inline-size; }
/* Gdy kontener (nie okno!) jest szerszy niż 400px */
@container (min-width: 400px) {
.card { grid-template-columns: auto 1fr; }
}Container queries są Baseline (newly available od 2023, a w ramach Interop 2025 dociągnięte do „widely available”). Ten sam komponent w wąskim sidebarze i w szerokim mainie układa się inaczej – bez jednej linijki skryptu.
Scroll-driven animations – efekty scrolla bez listenerów
Pasek postępu czytania, parallax, „pojaw się przy wejściu w viewport” – to był klasyczny addEventListener('scroll', …) z przeliczaniem pozycji (i problemami z wydajnością). Teraz animację można podpiąć pod oś przewijania:
/* Pasek postępu czytania artykułu – zero JS */
@keyframes grow { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.progress-bar {
transform-origin: left;
animation: grow auto linear;
animation-timeline: scroll(root block);
}To funkcja świeża (patrz sekcja uczciwości): Chrome/Edge i Safari 26 mają ją na pokładzie, Firefox jeszcze za flagą. Dlatego owijamy ją w @supports jako progresywne ulepszenie:
@supports (animation-timeline: scroll()) {
.progress-bar { /* … */ }
}Drobiazgi, które wcześniej były JS-em albo Sassem
Te są już od dawna Baseline i każdy z osobna kasował kiedyś hack albo narzędzie:
.box {
aspect-ratio: 16 / 9; /* koniec hacka z padding-top: 56.25% */
font-size: clamp(1rem, 4vw, 2rem);/* fluid typography bez JS mierzącego okno */
}
.grid { display: flex; gap: 1rem; } /* gap we flexie – koniec marginesowych sztuczek */
.menu { /* natywny CSS nesting – część roli Sass */
& a { color: inherit; }
&:hover { background: #eee; }
}
:is(h1, h2, h3) { text-wrap: balance; } /* :is()/:where() + ładne łamanie nagłówków */A position: sticky, scroll-snap i scroll-behavior: smooth zastąpiły całe biblioteki „sticky header” i „smooth scroll”.
field-sizing – auto-rosnący textarea jedną linijką (z gwiazdką)
Sztandarowy mikro-skrypt: textarea, który rośnie razem z tekstem (przeliczanie scrollHeight w input). Docelowo to też jedna linijka CSS:
textarea { field-sizing: content; }Tu jednak ostrożnie: na czerwiec 2026 działa w Chrome/Edge i częściowo Safari, ale brak Firefoksa trzyma to poza Baseline. Traktuj jako progresywne ulepszenie, nie zamiennik 1:1.
Część 2. HTML, który zastąpił komponenty
<dialog> – modal bez biblioteki
Modal to był rytuał: overlay, blokada scrolla, pułapka focusa, zamykanie Escape. Dziś to natywny element z ::backdrop i poprawną dostępnością w pakiecie (Baseline widely available):
<dialog id="confirm">
<form method="dialog">
<p>Na pewno usunąć?</p>
<button value="cancel">Anuluj</button>
<button value="ok">Usuń</button>
</form>
</dialog>confirm.showModal(); // focus-trap i ::backdrop za darmo
confirm.addEventListener('close', () => console.log(confirm.returnValue));Popover API – tooltipy, menu i dropdowny deklaratywnie
Tooltipy i menu kontekstowe to było JS-owe zarządzanie widocznością, warstwami (z-index) i „kliknij poza, żeby zamknąć”. Popover API (Baseline newly available od stycznia 2025) robi to dwoma atrybutami – bez ani jednej linijki skryptu:
<button popovertarget="menu">Menu</button>
<div id="menu" popover>
<a href="/blog">Blog</a>
<a href="/portfolio">Portfolio</a>
</div>„Light dismiss” (zamknięcie kliknięciem obok), warstwa top-layer i obsługa klawiatury są wbudowane.
<details> i loading="lazy" – akordeon i lazy-load bez skryptu
<details>
<summary>Pokaż szczegóły</summary>
<p>Akordeon bez JS – rozwijanie obsługuje przeglądarka.</p>
</details>
<img src="foto.webp" loading="lazy" decoding="async" alt="…">loading="lazy" zastąpił całą gałąź IntersectionObserver-ów pisanych pod lazy-load obrazów.
Natywne pickery – tu zamyka się historia kalendarza
To najlepsze domknięcie wątku z serii o kalendarzu. Kiedyś wybór daty znaczył jQuery UI datepicker – osobna biblioteka i jej style. Dziś:
<input type="date"> <!-- natywny kalendarz przeglądarki -->
<input type="color"> <!-- natywny color picker -->
<input type="range" min="0" max="100"> <!-- suwak bez biblioteki -->A gdy potrzebujesz pełnej kontroli nad wyglądem i logiką (jak w moim kalendarzu), piszesz własny Web Component – nadal bez żadnej zależności zewnętrznej.
Część 3. JavaScript, który wyparł biblioteki
jQuery → natywny DOM
To temat osobnego wpisu, więc tylko skrót: querySelector/querySelectorAll, classList, addEventListener z delegacją przez closest() oraz fetch pokrywają praktycznie wszystko, po co kiedyś sięgało się po $.
moment.js → Intl, a niedługo Temporal
Formatowanie i lokalizacja dat to dawniej moment.js (~70 KB). Dziś nazwy, formaty i daty względne daje wbudowane Intl – zero zależności:
new Intl.DateTimeFormat('pl-PL', { dateStyle: 'long' }).format(new Date());
// "17 czerwca 2026"
const rtf = new Intl.RelativeTimeFormat('pl-PL', { numeric: 'auto' });
rtf.format(-1, 'day'); // "wczoraj"Następcą problematycznego Date jest Temporal – niemutowalny, ze strefami czasu i bez pułapki „miesiące od zera”. To jednak funkcja świeża: na czerwiec 2026 jest w Firefoksie (139+) i Chrome (144+), ale Safari jeszcze jej nie wydało, a globalny zasięg to ~64%. Temporal osiągnął w TC39 Stage 4 (część ES2026) i jest spodziewany jako Baseline w 2026 – do tego czasu używaj go z polyfillem (temporal-polyfill).
// Temporal – gdy już bezpiecznie dostępny / z polyfillem
const data = Temporal.PlainDate.from('2026-06-17');
data.add({ days: 30 }).toString(); // "2026-07-17", bez mutacji i bez UTC-pułapeklodash → natywne metody języka
Większość „codziennego lodasha” to dziś standard ES. Grupowanie (Object.groupBy, Baseline od końca 2024), niemutujące sortowanie (toSorted, Baseline 2023), głęboka kopia (structuredClone), bezpieczny dostęp (?.) i wartości domyślne (??):
// _.groupBy → Object.groupBy
const byTag = Object.groupBy(posts, (p) => p.tag);
// _.cloneDeep → structuredClone (radzi sobie z Date/Map/Set)
const copy = structuredClone(state);
// niemutujące sortowanie – nie psuje oryginału
const sorted = posts.toSorted((a, b) => b.date - a.date);
// _.get(obj, 'a.b.c') → optional chaining + nullish
const city = user?.address?.city ?? 'brak';axios → fetch + AbortController
const ctrl = new AbortController();
const timeout = setTimeout(() => ctrl.abort(), 5000);
const res = await fetch('/api/posts', { signal: ctrl.signal });
clearTimeout(timeout);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();Anulowanie żądań, nagłówki, strumienie – wszystko jest w fetch/Request/Response. UUID? crypto.randomUUID(). Animacje (kiedyś $.animate)? Web Animations API: el.animate(keyframes, options).
Popper.js / Floating UI → anchor positioning (świeże)
Pozycjonowanie „przyklej tooltip do przycisku i odbij się od krawędzi okna” to był sztandarowy use-case Popper.js. CSS anchor positioning robi to natywnie:
.tooltip {
position: absolute;
position-anchor: --btn;
top: anchor(bottom);
justify-self: anchor-center;
}To jednak świeże: Chrome i Safari 26 wspierają, Firefox jest w drodze (jeden z celów Interop 2026). Na razie – progresywne ulepszenie lub Floating UI jako fallback.
View Transitions – animowane przejścia bez SPA-frameworka
Płynne przejście między stanami/stronami brało się kiedyś z całego frameworka SPA. View Transitions API daje to natywnie:
document.startViewTransition(() => updateTheDOM());Wariant same-document jest Baseline (2025). Wariant cross-document (przejścia między pełnymi stronami, np. w klasycznym MPA) to dopiero cel Interop 2026 – ląduje w przeglądarkach w tym roku, więc na produkcji jeszcze z @supports.
Część 4. Gdzie platforma jeszcze NIE dogoniła (sekcja uczciwości)
Ten wpis nie jest zachętą do wyrzucania wszystkiego. To zachęta do rewizji zależności – a uczciwość wymaga wskazania granic:
- Świeże funkcje z nierównym wsparciem.
Temporal, anchor positioning, scroll-driven animations, cross-document View Transitions,field-sizing– wszystkie wymienione wyżej jako „świeże” – na czerwiec 2026 nie są jeszcze pełnym Baseline (zwykle brakuje jednego silnika). Używaj ich przez@supports, z polyfillem albo jako progresywne ulepszenie. Nie usuwaj fallbacku, dopóki MDN nie pokaże „widely available”. - Złożony stan UI i routing. Reaktywny stan, komponenty, router, SSR/hydration – tu React/Vue/Svelte/Astro nadal wygrywają. Platforma nie ma natywnego data-grida, virtual-listy ani systemu toastów.
- Natywne ≠ automatycznie dostępne.
<dialog>czy Popover dają dobrą bazę a11y, ale wciąż trzeba zadbać o etykiety, kolejność focusa iprefers-reduced-motion. Natywne to mocny fundament, nie zwolnienie z myślenia. - Polyfill bywa sensowny. Czasem najlepszą decyzją jest świadomy polyfill świeżej funkcji (np.
Temporal) zamiast pełnej biblioteki – dostajesz docelowe API i wyrzucisz tylko polyfill, gdy wsparcie dojdzie.
Checklista: rewizja zależności
Zanim npm install, zadaj cztery pytania:
- Czy platforma już to ma? Sprawdź MDN/caniuse pod kątem Baseline – zacznij od
:has(), container queries,<dialog>, Popover,Intl,Object.groupBy,fetch. - Czy to robota dla CSS, nie JS? Stan zależny od interakcji (
:has(),:checked), layout, animacje scrolla – często wcale nie potrzebują skryptu. - Jaki jest status Baseline? „Widely available” – bierz. „Newly available” – bierz z testem. „Świeże/za flagą” –
@supports/polyfill. - Co realnie usuwam? Policz wagę i liczbę zależności, które znikają. Mniej kodu to mniej powierzchni na błędy i szybsza strona.
Zakończenie
jQuery, moment, lodash, Popper, jQuery UI – każde z nich rozwiązywało prawdziwy problem swojej epoki. Ale ich zadaniem było załatać brak w platformie, a platforma w końcu te braki uzupełniła – i w przypadku CSS często wyszła przed JavaScript. :has() kasuje skrypty do stylowania, container queries kasują pomiary szerokości, Popover i <dialog> kasują biblioteki UI, Intl/Temporal i natywne metody tablic kasują moment i lodash.
Morał nie brzmi „frameworki są złe”. Brzmi: zanim dołożysz zależność, sprawdź, czy nie dokładasz łatki na coś, co przeglądarka już umie. Coraz częściej umie – i to lepiej niż łatka.
Jeśli chcesz zobaczyć tę zasadę w działaniu na konkretnym projekcie, zajrzyj do wpisu Kalendarz w czystym JavaScript – bez jQuery, z którego wziął się ten temat.