# Use dvh Instead of h-screen

**NEVER** · **ID:** `layout-ibelick-viewport-height` · **Category:** layout
**Source:** [@Ibelick](https://www.ui-skills.com/)
**Interactive version:** https://ui-guides-agent-rules.netlify.app/principles/layout-ibelick-viewport-height

> NEVER: Use h-screen or 100vh on mobile. Use h-dvh (dynamic viewport height) instead to account for mobile browser chrome (address bar, navigation).

Never use h-screen or 100vh on mobile - use h-dvh for dynamic viewport height

On mobile, 100vh is the LARGE viewport — the height the page would have if the browser chrome were hidden. It does not shrink when the address bar is showing, so a "full height" screen is taller than the visible area and its bottom row (usually the primary action) sits under the browser UI. dvh tracks the viewport as the chrome shows and hides. The tradeoff worth knowing: because dvh changes during scroll, a dvh-sized element can resize mid-gesture — where that reflow is worse than the clipping, svh (the small viewport, i.e. chrome always visible) is the stable choice.

## Bad — do not do this

`layout-ibelick-viewport-height-bad`

```tsx
export function IbelickViewportHeightBad() {
  return (
    <div className="space-y-4">
      <div className="relative border rounded-lg overflow-hidden" style={{ height: '200px' }}>
        {/* Simulated mobile browser */}
        <div className="absolute inset-x-0 top-0 h-6 bg-muted flex items-center px-2 text-xs">
          <span className="text-muted-foreground">Browser Chrome</span>
        </div>
        <div
          className="bg-primary/10 flex items-center justify-center"
          style={{ height: '100%', marginTop: '24px' }}
        >
          <div className="text-center">
            <code className="text-xs bg-muted px-2 py-1 rounded">h-screen (100vh)</code>
            <p className="text-xs text-muted-foreground mt-2">Content cut off at bottom</p>
          </div>
        </div>
        <div className="absolute inset-x-0 bottom-0 h-5 bg-muted flex items-center justify-center">
          <div className="w-24 h-1 bg-muted-foreground/30 rounded-full" />
        </div>
      </div>
      <p className="text-xs text-destructive">
        100vh includes browser chrome on mobile - content gets cut off
      </p>
    </div>
  );
}
```

## Good — do this

`layout-ibelick-viewport-height-good`

```tsx
export function IbelickViewportHeightGood() {
  return (
    <div className="space-y-4">
      <div className="relative border rounded-lg overflow-hidden" style={{ height: '200px' }}>
        {/* Simulated mobile browser */}
        <div className="absolute inset-x-0 top-0 h-6 bg-muted flex items-center px-2 text-xs">
          <span className="text-muted-foreground">Browser Chrome</span>
        </div>
        <div
          className="bg-primary/10 flex items-center justify-center"
          style={{ height: 'calc(100% - 44px)', marginTop: '24px' }}
        >
          <div className="text-center">
            <code className="text-xs bg-muted px-2 py-1 rounded">h-dvh</code>
            <p className="text-xs text-muted-foreground mt-2">Adjusts to visible area</p>
          </div>
        </div>
        <div className="absolute inset-x-0 bottom-0 h-5 bg-muted flex items-center justify-center">
          <div className="w-24 h-1 bg-muted-foreground/30 rounded-full" />
        </div>
      </div>
      <p className="text-xs text-success">
        dvh (dynamic viewport height) adjusts as browser chrome shows/hides
      </p>
    </div>
  );
}
```

## References

- [ibelick — baseline-ui SKILL.md](https://github.com/ibelick/ui-skills/blob/main/skills/baseline-ui/SKILL.md)
- [Dynamic Viewports](https://web.dev/blog/viewport-units)
