Position
Position utilities control how an element is placed in the layout: static (default, in-flow), relative (in-flow + offset), absolute (out-of-flow, positioned to nearest positioned ancestor), fixed (positioned to viewport), and sticky (relative until a scroll threshold). Combine with top/right/bottom/left/inset + z-* for stacking.
static, relative, absolute, fixed, sticky
EXAMPLE
<!-- 1) static — the default; ignores top/left/right/bottom -->
<div class="static">Static</div>
<!-- 2) relative — in-flow but offsetable; doubles as a positioning context for absolute children -->
<div class="relative">
<span class="absolute -top-2 -right-2 size-5 rounded-full bg-red-500"></span> <!-- badge in top-right -->
<button class="px-4 py-2">Notifications</button>
</div>
<!-- 3) absolute — out of normal flow, positioned to NEAREST positioned ancestor -->
<div class="relative h-64">
<img class="absolute inset-0 w-full h-full object-cover" src="..." />
<div class="absolute bottom-4 left-4 text-white">Caption</div>
</div>
<!-- 4) inset shorthand -->
<div class="absolute inset-0">covers parent</div>
<div class="absolute inset-y-0 right-0 w-64">right sidebar</div>
<div class="absolute inset-x-0 top-0 h-12">top bar</div>
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">centered</div>
<!-- 5) fixed — viewport-relative; scrolls with the page only on iframes / nested scrollers -->
<header class="fixed top-0 inset-x-0 h-14 bg-white border-b z-50">
Site nav
</header>
<main class="pt-14">…</main>
<!-- 6) sticky — relative until you scroll past a threshold, then sticks -->
<aside class="sticky top-4 self-start">
Table of contents
</aside>
<!-- Sticky requires:
• An explicit top/left/right/bottom
• A parent that's tall enough to scroll within
• The parent NOT having 'overflow: hidden' (clips sticky)
-->
<!-- 7) Modal / overlay pattern -->
<div class="fixed inset-0 bg-black/50 grid place-items-center z-50">
<div class="bg-white rounded-lg p-6 max-w-md w-full m-4">
<h2>Confirm</h2>
<p>Are you sure?</p>
<div class="mt-4 flex justify-end gap-2">
<button class="px-4 py-2">Cancel</button>
<button class="px-4 py-2 bg-indigo-600 text-white rounded">OK</button>
</div>
</div>
</div>
<!-- 8) Tooltip — absolute child of inline-block parent -->
<button class="group relative">
Help
<span class="absolute hidden group-hover:block bg-black text-white text-xs rounded px-2 py-1
bottom-full left-1/2 -translate-x-1/2 mb-2 whitespace-nowrap">
Click for help
</span>
</button>
<!-- 9) Floating action button -->
<button class="fixed bottom-6 right-6 size-14 rounded-full bg-indigo-600 text-white shadow-lg z-40">
+
</button>
<!-- 10) Stacking context — z-index requires position other than static -->
<div class="relative z-0">Layer 0</div>
<div class="relative z-10">Layer 10 — above</div>
<div class="relative z-50">Layer 50</div>
<!-- Without 'relative' (or another non-static position), z-* has no effect. -->
<!-- 11) Sticky table headers -->
<div class="max-h-96 overflow-auto">
<table class="w-full">
<thead class="sticky top-0 bg-white border-b">
<tr><th>Name</th><th>Email</th></tr>
</thead>
<tbody>...</tbody>
</table>
</div>
<!-- 12) Drawer / side panel -->
<aside class="fixed top-0 right-0 h-full w-80 bg-white shadow-xl
translate-x-full data-[open=true]:translate-x-0 transition-transform" data-open="false">
Drawer content
</aside>
<!-- 13) Container queries + position — modern composition -->
<div class="@container relative">
<div class="absolute @md:right-0 @md:top-0 @sm:bottom-0 @sm:left-0">…</div>
</div>
<!-- 14) Sticky with negative inset — useful for safe-area handling -->
<header class="sticky top-[env(safe-area-inset-top)] bg-white">…</header>
<!-- 15) Logical inset properties (RTL-safe) -->
<div class="absolute start-0 end-0">left to right OR right to left (auto)</div>
<div class="absolute inset-y-0 ms-2">margin-inline-start</div>
<!-- 16) Arbitrary positioning -->
<div class="absolute top-[37px] left-[calc(50%-100px)]">precise</div>
<div class="sticky top-[var(--header-height)]">CSS var</div>
<!-- 17) Common bugs -->
<!-- • Child 'absolute' but no positioned ancestor → positions to <html>; surprise -->
<!-- • z-* without 'relative' / 'absolute' / 'sticky' → no effect -->
<!-- • sticky inside 'overflow-hidden' → broken; remove or move sticky -->
<!-- • fixed inside transform/filter parent → behaves like absolute due to containing-block change -->
<!-- • Negative margins instead of inset for offset — works but harder to reason about -->
<!-- • Forgetting safe-area-inset on iOS notch — fixed elements overlap status bar -->
<!-- • Modal underneath toast because z-* not high enough → use a documented z-index scale -->
<!-- • Sticky element jumping at scroll boundaries — collapsing margins; use padding instead -->
Why it matters
Position utilities open up layered UI: relative creates a positioning context, absolute + inset-0 fills it, fixed pins to the viewport, sticky + top-* sticks once you scroll past. Combine with z-* on a positioned element, mind the overflow-hidden + sticky trap, and honour iOS safe-area insets on fixed bars.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…