Zahoďte knihovny: dropdowny, tooltipy a přechody stránek jen s HTML a CSS

Rozbalovací menu, tooltip, modální okno a plynulý přechod mezi stránkami. Ještě nedávno čtyři důvody, proč do projektu přidat několik knihoven a desítky kilobajtů JavaScriptu. V roce 2026 už to všechno zvládne prohlížeč sám. Ukážeme si, jak na to s Popover API, CSS anchor positioningem a View Transitions, a také kde mají své limity.
Kolik JavaScriptu stál jeden dropdown
Otevřete bundle analyzer skoro jakékoli React aplikace posledních let a pravděpodobně v něm najdete Floating UI nebo Popper.js na pozicování, Headless UI či Radix na přístupné menu a dialogy a Framer Motion na animace. Dohromady to jsou klidně desítky kilobajtů, které řeší tři otázky: jak něco zobrazit nad zbytkem stránky, kam to umístit, aby to nevypadlo z obrazovky, a jak plynule animovat změnu.
Na všechny tři otázky dnes existuje nativní odpověď. Popover API řeší zobrazování, zavírání a vrstvení. Anchor positioning řeší pozici vůči jinému prvku. View Transitions řeší animace mezi stavy stránky, a dokonce mezi dvěma různými stránkami.
Podstatné je načasování. Jádro anchor positioningu se stalo Baseline v lednu 2026 s Firefoxem 147 a same-document View Transitions v říjnu 2025 s Firefoxem 144. Popover API a same-document přechody tak dnes mají širokou podporu ve všech hlavních prohlížečích a základ anchor positioningu také. U cross-document přechodů a u některých novějších částí specifikací je ale pořád potřeba kontrolovat konkrétní podporu.
| API | Chrome / Edge | Firefox | Safari | Poznámka |
|---|---|---|---|---|
| Popover API | 114 | 125 | 17 | Baseline od dubna 2024 |
Atributy command a commandfor | 135 | 144 | 26.2 | nejmladší z uvedených API |
| CSS anchor positioning | 125 | 147 | 26 | částečná podpora; position-anchor plně až Chrome 151, Firefox 151, Safari 27 |
| View Transitions (same-document) | 111 | 144 | 18 | novější části jako match-element ověřte zvlášť |
| View Transitions (cross-document) | 126 | 144 (částečně) | 18.2 | ve Firefoxu jen částečná podpora |
Zdroje: Can I use: anchor positioning, Can I use: position-anchor, Can I use: cross-document View Transitions, web.dev, leden 2026, web.dev, říjen 2025, Chrome for Developers: Popover API. Stav k září 2026.
Popover API: menu bez jediného řádku JavaScriptu
Základ je triviální. Prvek označíte atributem popover, tlačítku přidáte popovertarget s jeho ID a je hotovo:
<button popovertarget="user-menu">Můj účet</button>
<nav id="user-menu" popover>
<a href="/profil">Profil</a>
<a href="/objednavky">Objednávky</a>
<a href="/odhlasit">Odhlásit se</a>
</nav>Code language: HTML, XML (xml)
Bez jediného event listeneru dostanete:
- přepínání otevřeno/zavřeno kliknutím na tlačítko,
- light dismiss, tedy zavření kliknutím mimo popover nebo klávesou Escape,
- top layer, kam se popover vykreslí nad všechno ostatní bez ohledu na
z-indexaoverflow: hiddenrodičů, - synchronizovaný stav tlačítka, protože stav rozbalení a vztah tlačítka k popoveru prohlížeč v podporovaných verzích zpřístupňuje asistivním technologiím sám. Z popoveru tím ale automaticky nevznikne přístupné menu, o tom víc v kapitole o přístupnosti.
Právě top layer je často podceňovaný. Kolik hodin jste strávili laděním z-index: 9999 nebo portalováním komponent do <body>? Prvek v top layer se ocitne mimo stacking contexty stránky, takže ho žádný rodič neořízne ani nepřekryje.
Hodnoty auto, manual a hint
Atribut popover bez hodnoty znamená auto. Takový popover má light dismiss a otevření jiného auto popoveru ho zavře, takže nikdy nejsou otevřená dvě menu najednou. Výjimkou jsou vnořená menu: popover otevřený zevnitř jiného popoveru nadřazený nezavře.
Hodnota manual light dismiss vypíná. Hodí se na toast notifikace, které mají zůstat zobrazené, dokud je uživatel nebo kód nezavře:
<div id="toast" popover="manual">
Káva byla přidána do košíku.
<button popovertarget="toast" popovertargetaction="hide">Zavřít</button>
</div>Code language: HTML, XML (xml)
Atribut popovertargetaction přijímá toggle (výchozí), show a hide. Třetí hodnota popover="hint" je určená pro tooltipy, které nezavírají otevřená menu. Její podpora je ale zatím neúplná, takže ji před použitím ověřte.
Stylování a animace otevření
Otevřený popover zachytíte pseudotřídou :popover-open, pozadí za ním pseudoelementem ::backdrop. Plynulé zobrazení i skrytí zvládne čisté CSS díky @starting-style a transition-behavior: allow-discrete:
[popover] {
opacity: 0;
translate: 0 -8px;
transition:
opacity 150ms, translate 150ms,
display 150ms allow-discrete,
overlay 150ms allow-discrete;
}
[popover]:popover-open {
opacity: 1;
translate: 0 0;
}
@starting-style {
[popover]:popover-open {
opacity: 0;
translate: 0 -8px;
}
}Code language: CSS (css)
@starting-style říká, z jakého stavu má prvek animovat při prvním vykreslení. Přechody na display a overlay s allow-discrete zase zajistí, že prvek při zavírání nezmizí dřív, než doběhne animace.
Popover, nebo <dialog>?
Oba prvky žijí v top layer, ale řeší jinou situaci. Popover je nemodální: zbytek stránky zůstává aktivní, což sedí na menu, nápovědy a notifikace. <dialog> otevřený přes showModal() je modální: zbytek stránky zneaktivní, uvězní fokus a hodí se tam, kde uživatel musí něco potvrdit.
Až dosud jste na modální dialog JavaScript potřebovali. Nové atributy command a commandfor to mění:
<button commandfor="confirm" command="show-modal">
Odstranit položku
</button>
<dialog id="confirm" closedby="any">
<p>Opravdu chcete položku odstranit z košíku?</p>
<form method="dialog">
<button value="cancel">Zrušit</button>
<button value="ok">Odstranit</button>
</form>
</dialog>Code language: HTML, XML (xml)
command umí show-modal, close a request-close pro dialogy a toggle-popover, show-popover a hide-popover pro popovery. Formulář s method="dialog" dialog zavře a hodnotu stisknutého tlačítka uloží do dialog.returnValue. Atribut closedby="any" přidá light dismiss i dialogu, jeho podpora je ale zatím užší než u command.
Anchor positioning: tooltip, který nevypadne z obrazovky
Popover z předchozí kapitoly má jednu vadu: prohlížeč ho ve výchozím stavu vycentruje doprostřed okna. Pro menu v hlavičce chceme, aby se otevřelo pod tlačítkem. Právě tady nastupuje anchor positioning.
Princip tvoří tři vlastnosti. Kotvě dáte jméno, plovoucímu prvku řeknete, ke které kotvě patří, a kam se vůči ní má postavit:
.account-button {
anchor-name: --account;
}
#user-menu {
position-anchor: --account;
position-area: bottom span-left;
/* zruší výchozí centrování popoveru */
inset: auto;
margin: 0;
margin-top: 6px;
}Code language: CSS (css)
Jména kotev začínají dvěma pomlčkami jako CSS proměnné. Popover je v top layer, takže má position: fixed už z UA stylu a nic dalšího nepotřebuje. Běžný prvek musí mít position: absolute nebo fixed.
position-area: mřížka 3 × 3
Představte si kolem kotvy mřížku o devíti polích, kotva sedí uprostřed. position-area říká, do kterého pole nebo pásu polí plovoucí prvek patří:
top/bottom/left/rightumístí prvek na danou stranu a vycentruje ho,bottom span-rightumístí prvek pod kotvu, zarovnaný k jejímu levému okraji a roztahující se doprava,bottom span-leftdělá totéž opačně, což se hodí pro menu u pravého okraje stránky,top rightumístí prvek do rohu nad kotvou vpravo.
Na většinu tooltipů a menu position-area stačí. Hodnoty podporují i logické varianty (block-end, inline-start), které se samy otočí na webech psaných zprava doleva.
Funkce anchor() pro přesnou kontrolu
Když potřebujete pixelovou přesnost, použijte v inset vlastnostech funkci anchor(). Vrací souřadnici hrany kotvy:
.price-hint {
position: absolute;
position-anchor: --price;
top: calc(anchor(bottom) + 8px);
left: anchor(center);
translate: -50% 0;
/* stejně široký jako kotva, minimálně 12rem */
width: max(12rem, anchor-size(width));
}Code language: CSS (css)
Funkce anchor-size() vrací rozměry kotvy. Typické použití je našeptávač, který má být stejně široký jako vyhledávací pole.
Když dojde místo: position-try-fallbacks
Tohle je funkce, kvůli které jsme používali Floating UI. Tooltip u spodního okraje obrazovky se má překlopit nahoru:
.tooltip {
position-area: bottom;
position-try-fallbacks: flip-block, flip-inline;
}Code language: CSS (css)
Prohlížeč nejdřív zkusí výchozí pozici. Když by prvek přetekl svůj containing block (u popoveru v top layer je to v praxi viewport), zkusí flip-block, tedy zrcadlení nahoru, a pak flip-inline. Pro vlastní náhradní pozice slouží pravidlo @position-try:
@position-try --vpravo-od-kotvy {
position-area: right;
margin: 0 0 0 8px;
}
.tooltip {
position-area: bottom;
margin-top: 8px;
position-try-fallbacks: flip-block, --vpravo-od-kotvy;
}Code language: CSS (css)
Všechno se přepočítává při scrollování i změně velikosti okna, a to bez jediného resize listeneru nebo requestAnimationFrame smyčky.
Tooltip čistě v CSS
Pro jednoduchý tooltip u ikony nepotřebujete ani popover. Stačí zobrazit prvek při najetí myší nebo fokusu:
<span class="param">
Pražení: střední
<button class="info" aria-describedby="tip-roast">?</button>
<span id="tip-roast" role="tooltip" class="tooltip">
Střední pražení zachovává ovocné tóny a hodí se na filtr i espresso.
</span>
</span>Code language: HTML, XML (xml)
.info {
anchor-name: --info;
}
.tooltip {
position: absolute;
position-anchor: --info;
position-area: top;
margin-bottom: 6px;
position-try-fallbacks: flip-block;
width: max-content;
max-width: 20rem;
display: none;
}
.info:is(:hover, :focus-visible) + .tooltip {
display: block;
}Code language: CSS (css)
Tooltip je díky aria-describedby připojený k tlačítku a čtečka obrazovky ho přečte i bez vizuálního zobrazení.
Více kotev na stránce
Jméno kotvy je globální. Když máte výpis dvaceti produktů a každý má vlastní tooltip, použije se poslední kotva s daným jménem. Řešení je vlastnost anchor-scope, která platnost jména omezí na podstrom:
.product-card {
anchor-scope: --info;
}Code language: CSS (css)
Každá karta pak má vlastní --info a tooltip se vždy naváže na kotvu ve své kartě. Podpora anchor-scope ale zaostala za zbytkem specifikace, takže ji ověřte. Spolehlivou alternativou je nastavit jméno inline stylem, třeba style="anchor-name: --info-42", a stejně tak position-anchor u tooltipu.
View Transitions: animace mezi stránkami bez SPA
Animovat změnu stavu stránky bylo vždy těžké. Starý i nový stav musí chvíli existovat současně, což v DOM znamená klonování prvků, měření pozic a knihovny typu Framer Motion. View Transitions API to obchází: prohlížeč vyfotí starý stav, provede změnu, vyfotí nový stav a mezi snímky vytvoří animaci.
Same-document: jedna funkce
V rámci jedné stránky stačí zabalit změnu DOM do document.startViewTransition():
const grid = document.querySelector('.product-grid');
function setView(mode) {
const update = () => grid.dataset.view = mode; // "grid" nebo "list"
if (!document.startViewTransition) {
update();
return;
}
document.startViewTransition(update);
}Code language: JavaScript (javascript)
Bez dalšího CSS dostanete prolínačku celé stránky. Zajímavé to začne být, až jednotlivým prvkům dáte jméno. Prohlížeč pak každý pojmenovaný prvek animuje zvlášť, včetně změny pozice a velikosti:
.product-card {
view-transition-name: match-element;
view-transition-class: card;
}
::view-transition-group(.card) {
animation-duration: 300ms;
animation-timing-function: ease-out;
}Code language: CSS (css)
view-transition-name musí být na stránce unikátní. Hodnota match-element vygeneruje jméno automaticky pro každý prvek, takže nemusíte psát style="view-transition-name: card-42" do každé karty. view-transition-class pak umožní stylovat všechny karty jedním pravidlem. Přepnutí mřížky na seznam tak vytvoří plynulé přeskládání karet, na které byste jinak potřebovali FLIP techniku.
Cross-document: žádný JavaScript
Pro klasické server-rendered weby je nejzajímavější přechod mezi dvěma samostatnými stránkami. Klasický server-rendered web s obyčejnými odkazy dostane SPA animace přidáním tří řádků CSS na obě stránky:
@view-transition {
navigation: auto;
}Code language: CSS (css)
Platí to pro navigace v rámci stejného originu. Pro efekt „obrázek produktu odletí z výpisu do detailu“ potřebujete, aby měl obrázek na obou stránkách stejné jméno:
<!-- vypis.html -->
<a href="/kava/etiopie-yirgacheffe">
<img src="etiopie.webp" alt="Etiopie Yirgacheffe"
style="view-transition-name: kava-etiopie">
</a>
<!-- /kava/etiopie-yirgacheffe -->
<img src="etiopie-velka.webp" alt="Etiopie Yirgacheffe"
style="view-transition-name: kava-etiopie">Code language: HTML, XML (xml)
Prohlížeč oba snímky spáruje a animuje změnu pozice i velikosti. Ve výpisu musí mít každý produkt jiné jméno, proto inline styl generovaný šablonou. Hodnota match-element tady nepomůže, protože jméno musí sedět napříč dvěma dokumenty.
Směr animace podle navigace
Když se uživatel vrací zpět, animace by měla jít opačně. K tomu slouží typy přechodů. Nastavíte je v události pageswap na odcházející stránce nebo pagereveal na přicházející:
window.addEventListener('pagereveal', (e) => {
if (!e.viewTransition) return;
const type = navigation.activation?.navigationType;
if (type === 'traverse') {
e.viewTransition.types.add('zpet');
}
});Code language: JavaScript (javascript)
:active-view-transition-type(zpet)::view-transition-old(root) {
animation: slide-out-right 250ms ease-in;
}
:active-view-transition-type(zpet)::view-transition-new(root) {
animation: slide-in-left 250ms ease-out;
}Code language: CSS (css)
Navigation API, ze kterého pochází navigation.activation, se stalo Baseline ve stejném vydání Firefoxu 147 jako anchor positioning. U cross-document přechodů je to jediné místo, kde JavaScript potřebujete, a i tak jde o pár řádků. Animace slide-out-right a slide-in-left jsou běžné @keyframes, které si nadefinujete sami.
Na co si dát pozor
Navigace se zablokuje, dokud nová stránka nemá první render. Pomalý server tedy uživatel pozná stejně jako bez přechodů, animace nic nezrychlí. Když přechod trvá více než zhruba čtyři sekundy, prohlížeč ho přeruší.
Další problém přinášejí velké stránky, kde se klíčový prvek načte pozdě. Pomocí <link rel="expect" href="#hlavni-obrazek" blocking="render"> řeknete prohlížeči, aby s renderem počkal, dokud daný prvek není v DOM. Používejte to opatrně, protože to zdržuje první vykreslení.
Progressive enhancement: co uvidí starší prohlížeč
Všechna tři API mají příjemnou vlastnost: když je prohlížeč nezná, stránka se nerozbije, jen přijde o vylepšení. S View Transitions je to nejsnazší. Neznámé @view-transition pravidlo prohlížeč ignoruje a navigace proběhne jako dřív, jen bez animace.
U anchor positioningu chráníte fallback přes @supports. Základní pozici nastavíte postaru a kotvy přidáte jen tam, kde fungují:
/* fallback: menu přilepené k pravému hornímu rohu */
#user-menu {
inset: 4rem 1rem auto auto;
margin: 0;
}
@supports (anchor-name: --a) {
#user-menu {
inset: auto;
position-anchor: --account;
position-area: bottom span-left;
margin-top: 6px;
}
}Code language: CSS (css)
U tooltipu je nejbezpečnější fallback zobrazit text v běžném toku dokumentu, třeba pod parametrem. Informace se tak k uživateli dostane vždy.
Popover API má dnes tak širokou podporu, že fallback většinou řešit nemusíte. Když ano, detekujete ho v JavaScriptu:
const supportsPopover = HTMLElement.prototype.hasOwnProperty('popover');Code language: JavaScript (javascript)
U command a commandfor se vyplatí opatrnost, protože jsou nejmladší. Tlačítko s command="show-modal" ve starším prohlížeči jednoduše nedělá nic. Pokud je dialog kritický, přidejte pojistku:
if (!('command' in HTMLButtonElement.prototype)) {
document.querySelectorAll('button[command="show-modal"]').forEach((btn) => {
btn.addEventListener('click', () => {
document.getElementById(btn.getAttribute('commandfor')).showModal();
});
});
}Code language: JavaScript (javascript)
Přístupnost: co dostanete zdarma a co ne
Nativní API vám v přístupnosti hodně pomůžou, ale nevyřeší všechno. Tahle kapitola je důležitá i kvůli evropskému Accessibility Actu, který se od června 2025 vztahuje mimo jiné na e-shopy.
Co dostanete zdarma. Popover se zavírá klávesou Escape. Tlačítko s popovertarget má automaticky stav rozbalení, který čtečka obrazovky oznámí. Po zavření popoveru se fokus vrátí na tlačítko, které ho otevřelo. Modální <dialog> uvězní fokus uvnitř a zbytek stránky označí jako neaktivní.
Co musíte doplnit. Popover nemá žádnou roli, je to jen chování. Když z něj děláte menu, zvolte sémantiku podle obsahu. Pro navigaci stačí <nav> se seznamem odkazů. Role menu a menuitem použijte jen tehdy, když implementujete i očekávané ovládání šipkami, a to už bez JavaScriptu nejde. Špatně použitá role menu je horší než žádná.
Tooltip zobrazený jen při :hover je nepřístupný z klávesnice i na dotykových zařízeních. Proto v ukázce reaguje i na :focus-visible a kotvou je tlačítko, ne obecný <span>. Na mobilu je lepší použít místo hover tooltipu popover otevíraný kliknutím.
Omezení pohybu. Část uživatelů má v systému vypnuté animace, třeba kvůli vestibulárním potížím. View Transitions i animace popoveru byste pro ně měli vypnout:
@media (prefers-reduced-motion: reduce) {
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
animation: none !important;
}
[popover] {
transition: none;
}
}Code language: CSS (css)
Ještě jednodušší je zapnout cross-document přechody jen pro uživatele, kteří pohyb nevypnuli:
@media (prefers-reduced-motion: no-preference) {
@view-transition {
navigation: auto;
}
}Code language: CSS (css)
Kdy knihovnu přece jen nechat
Bylo by nepoctivé tvrdit, že nativní API nahradí veškeré UI knihovny. Jsou situace, kdy knihovna pořád dává smysl:
- Combobox a našeptávač. Pozici vyřeší kotva, ale ovládání šipkami, filtrování,
aria-activedescendanta asynchronní načítání výsledků jsou pořád práce pro JavaScript. Knihovna jako Downshift nebo React Aria tu ušetří hodně chyb v přístupnosti. - Menu s klávesovou navigací. Plnohodnotné menu podle ARIA Authoring Practices potřebuje správu fokusu šipkami a typeahead. Popover vám dá základ, logiku dopíšete sami nebo použijete knihovnu.
- Složité animace. View Transitions umí přechod mezi dvěma stavy. Gesta, fyzikální pružiny, animace řízené prstem nebo přerušitelné sekvence zůstávají doménou knihoven.
- Okrajové případy anchor positioningu. Specifikace je Baseline, ale implementace se v detailech liší, třeba u kotev uvnitř scrollovacích kontejnerů, transformovaných rodičů nebo
anchor-scope. Testujte ve všech třech enginech.
Zároveň se vyplatí hlídat, co nové verze knihoven dělají. Část z nich už vnitřně přechází na Popover API a top layer, takže i když knihovnu necháte, může být menší a spolehlivější než dřív.
Závěr
Menu, modální dialog, tooltip, který se překlopí u okraje obrazovky, a animovaný přechod mezi stránkami. To všechno dnes postavíte z HTML atributů a několika desítek řádků CSS. JavaScript potřebujete jen na drobnosti, jako je směr animace při navigaci zpět nebo pojistka pro starší prohlížeče.
Výsledek je menší bundle, rychlejší načítání a hlavně kód, který za vás udržuje prohlížeč. Pozicování běží na úrovni vykreslování místo v JavaScriptové smyčce a přístupnost popoveru opravují vývojáři prohlížečů, ne vy.
Začněte postupně. Vyberte jednu komponentu, třeba uživatelské menu nebo tooltip, přepište ji na nativní API a podívejte se, kolik kódu a závislostí zmizí. Často zjistíte, že knihovnu, kterou jste v projektu táhli roky, už vůbec nepotřebujete.
Zdroje
- New to the web platform in January 2026, web.dev
- New to the web platform in October 2025, web.dev
- Same-document view transitions are now Baseline Newly available, web.dev
- Can I use pro aktuální podporu v prohlížečích