# Fixed Elements Need a Space Budget

**SHOULD** · **ID:** `layout-fixed-element-budget` · **Category:** layout
**Source:** Custom
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/layout-fixed-element-budget

> SHOULD: A fixed/sticky element is out of flow, so reserve its space: offset content by the bar height (padding-top) and add scroll-padding-top so anchors and focus land clear. Do not position multiple fixed elements independently — budget them together with each other and env(safe-area-inset-*) so they never collide or hide content.

Reserve room for a fixed or sticky bar by offsetting content, and don't stack multiple fixed elements into collisions

A `position: fixed` (or sticky) element is lifted out of normal flow, so the layout no longer reserves space for it — and by default the first section of content renders UNDERNEATH a fixed header, its top lines hidden behind the bar. Pay the space back: offset the content by the bar's height with `padding-top` on the scroll container, and add `scroll-padding-top` so anchored jumps and keyboard focus also land clear of it (that focus case is interactions-focus-not-obscured). The second failure is stacking: a fixed header, a fixed cookie banner, and a fixed chat button, each positioned on its own, will overlap one another and the device notch because none of them knows the others exist. Budget them together — collapse them into one fixed region, or give each an explicit offset that accounts for the others and for `env(safe-area-inset-*)` (layout-ibelick-safe-areas). The principle under both halves: taking an element out of flow is borrowing space you now have to repay by hand.

## Rule snippet

```tsx
.scroll { padding-top: var(--nav-h); scroll-padding-top: var(--nav-h); }
```

## Bad — do not do this

`layout-fixed-element-budget-bad`

```tsx
export function FixedElementBudgetBad() {
  return (
    <div className="w-full max-w-sm">
      {/* Scroll container standing in for the viewport */}
      <div className="relative h-40 overflow-hidden rounded-lg border border-border bg-card">
        {/* "Fixed" bar, taken out of flow */}
        <div className="absolute inset-x-0 top-0 z-10 bg-primary px-3 py-2 text-xs font-medium text-primary-foreground">
          Fixed nav
        </div>
        {/* Content with no offset — first lines sit under the bar */}
        <div className="space-y-2 p-3 text-sm text-foreground">
          <p>First item — hidden under the nav</p>
          <p>Second item</p>
          <p>Third item</p>
        </div>
      </div>
      <p className="mt-4 text-xs text-destructive">
        The fixed bar is out of flow, so the first content line renders underneath it — no space was reserved.
      </p>
    </div>
  );
}
```

## Good — do this

`layout-fixed-element-budget-good`

```tsx
export function FixedElementBudgetGood() {
  return (
    <div className="w-full max-w-sm">
      <div className="relative h-40 overflow-hidden rounded-lg border border-border bg-card">
        <div className="absolute inset-x-0 top-0 z-10 bg-primary px-3 py-2 text-xs font-medium text-primary-foreground">
          Fixed nav
        </div>
        {/* Content offset by the bar's height — first line clears it */}
        <div className="space-y-2 px-3 pb-3 pt-12 text-sm text-foreground">
          <p>First item — fully visible</p>
          <p>Second item</p>
          <p>Third item</p>
        </div>
      </div>
      <p className="mt-4 text-xs text-success">
        Content offset by the nav’s height (and scroll-padding-top for anchors/focus) — nothing hides behind it.
      </p>
    </div>
  );
}
```

## References

- [nextlevelbuilder — ui-ux-pro-max](https://skills.sh/nextlevelbuilder/ui-ux-pro-max-skill/ui-ux-pro-max)
- [MDN — position: fixed](https://developer.mozilla.org/en-US/docs/Web/CSS/position)
- [MDN — scroll-padding](https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-padding)
