> ## Documentation Index
> Fetch the complete documentation index at: https://setup.despia.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Safe Areas

> Handle notches, status bars, home indicators, and gesture bars using CSS variables injected by the Despia runtime, paired with a defensive env() fallback.

<iframe src="https://www.youtube.com/embed/e4xN7eZW4NU" title="YouTube video player" frameborder="0" className="w-full aspect-video rounded-xl" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen />

<Note>
  The video uses a specific AI coding tool to demonstrate the setup, but the configuration works 1:1 with Cursor, Claude Code, or any other tool. Despia is web framework and tooling agnostic, so the only thing that matters is the CSS pattern you apply to your fixed and sticky elements.
</Note>

The Despia runtime injects four CSS variables into every WebView: `--safe-area-top`, `--safe-area-bottom`, `--safe-area-left`, `--safe-area-right`. Each one measures the inset for the matching edge, accounting for the status bar, notch, Dynamic Island, home indicator, and gesture bar on iOS, and the equivalent system chrome on Android. The variables are available before your JavaScript runs and update in real time when the device rotates.

There is no SDK call. The variables are always present in the runtime, and you reference them from CSS exactly like any custom property.

***

## Installation

No package installation is required for the CSS variables themselves. The runtime injects them automatically into every WebView. Install `despia-native` only if your page also calls SDK schemes for other features.

<Tabs>
  <Tab title="Bundle">
    <CodeGroup>
      ```bash npm theme={null}
      npm install despia-native
      ```

      ```bash pnpm theme={null}
      pnpm add despia-native
      ```

      ```bash yarn theme={null}
      yarn add despia-native
      ```
    </CodeGroup>

    ```javascript theme={null}
    import despia from 'despia-native';
    ```
  </Tab>

  <Tab title="CDN">
    <CodeGroup>
      ```html UMD theme={null}
      <script src="https://cdn.jsdelivr.net/npm/despia-native/index.min.js"></script>
      ```

      ```html ESM theme={null}
      <script type="module">
          import despia from 'https://cdn.jsdelivr.net/npm/despia-native/+esm'
      </script>
      ```
    </CodeGroup>
  </Tab>
</Tabs>

***

## The canonical pattern

Safe areas are insets that stack on top of your existing padding, never replacements for it. Always combine them with your base padding using `calc()`, and always pair the runtime variable with `env(safe-area-inset-*)` as a fallback. The full pattern is:

```css theme={null}
/* Top, status bar and notch region */
padding-top: calc(1rem + var(--safe-area-top, env(safe-area-inset-top, 0px)));

/* Bottom, home indicator and gesture bar region */
padding-bottom: calc(1rem + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)));

/* Left and right, landscape on notched devices */
padding-left:  calc(1rem + var(--safe-area-left,  env(safe-area-inset-left,  0px)));
padding-right: calc(1rem + var(--safe-area-right, env(safe-area-inset-right, 0px)));
```

Each piece of the chain serves a purpose:

* `calc(BASE + ...)` because safe areas are insets layered on top of your existing padding, not replacements for it. Without `calc()`, your element loses its base padding on the safe-area edge.
* `var(--safe-area-X, ...)` first because the Despia runtime can override the native value. This is the source of truth inside the native container.
* `env(safe-area-inset-X, ...)` second because that is the native WebKit value, used during web preview and before the runtime injects its variables.
* `0px` last because the `calc()` becomes invalid if both lookups resolve to nothing. The `0px` collapses the safe area cleanly to zero and leaves the base padding intact.

Never write the bare `var(--safe-area-top)`, the bare `env(safe-area-inset-top)`, or any form without `calc()` and a base padding.

***

## Fixed header below the status bar

A fixed header at the top of the screen needs to sit *below* the status bar and notch, not under them. Apply the inset to `padding-top` so the header's background extends up to the top of the screen while its content stays in the safe region.

```html theme={null}
<style>
  .header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    background: #1a1a2e;
    color: #fff;
    padding-top:    calc(0.75rem + var(--safe-area-top, env(safe-area-inset-top, 0px)));
    padding-left:   1rem;
    padding-right:  1rem;
    padding-bottom: 0.75rem;
  }
</style>

<header class="header">
  <h1>My App</h1>
</header>
```

The same pattern works in JSX. Inline the calc expression or move it to a class, both render identically.

```jsx theme={null}
function Header() {
    return (
        <header style={{
            position:      'fixed',
            top:           0,
            left:          0,
            right:         0,
            background:    '#1a1a2e',
            color:         '#fff',
            paddingTop:    'calc(0.75rem + var(--safe-area-top, env(safe-area-inset-top, 0px)))',
            paddingLeft:   '1rem',
            paddingRight:  '1rem',
            paddingBottom: '0.75rem',
        }}>
            <h1>My App</h1>
        </header>
    )
}
```

***

## Fixed bottom navigation above the home indicator

A bottom tab bar or navigation needs to clear the home indicator on iOS and the gesture bar on Android. Apply the inset to `padding-bottom`.

```html theme={null}
<style>
  .tab-bar {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    background: #fff;
    border-top: 1px solid #e0e0e0;
    padding-top:    0.5rem;
    padding-bottom: calc(0.5rem + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)));
    padding-left:   1rem;
    padding-right:  1rem;
    display: flex;
    justify-content: space-around;
  }
</style>

<nav class="tab-bar">
  <button>Home</button>
  <button>Search</button>
  <button>Profile</button>
</nav>
```

***

## Scrollable content between fixed bars

When you have both a fixed header and a fixed bottom bar, your scrollable content needs `padding-top` and `padding-bottom` large enough to clear both. Combine the bar height with the safe-area inset in a single `calc()`.

```css theme={null}
.content {
    /* clear fixed header (60px tall) plus its safe area */
    padding-top: calc(60px + var(--safe-area-top, env(safe-area-inset-top, 0px)));

    /* clear fixed bottom nav (64px tall) plus its safe area */
    padding-bottom: calc(64px + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)));
}
```

The header's own `padding-top` already accounts for the safe area inside the header, but the *content* needs to clear the entire visible header (its base height plus the inset), so the inset shows up in both places. This is correct, not a duplication.

***

## Why pair var() with env()

The runtime variable and the native env value are not redundant. They cover different cases.

| Source                     | Available when                                                                                            |
| :------------------------- | :-------------------------------------------------------------------------------------------------------- |
| `var(--safe-area-top)`     | Inside the Despia runtime, after the variables are injected. Source of truth                              |
| `env(safe-area-inset-top)` | In any WebKit-based browser (Safari, iOS WKWebView), including web preview before the runtime initializes |
| `0px` fallback             | Anywhere the calc() would otherwise be invalid, e.g. desktop browsers without WebKit                      |

In the native runtime, both lookups resolve, but `var()` wins because it comes first. In Safari preview, `var()` resolves to undefined and the chain falls through to `env()`. On desktop Chrome or Firefox, neither resolves and the chain ends at `0px`. The pattern is correct in every environment, which is why this is the only form the docs recommend.

<Warning>
  Without these insets, top elements render under the status bar and bottom elements are partially hidden behind the home indicator on modern iPhones. Always verify safe area behavior on a physical device or simulator running your Despia-built app, not in a desktop browser. Desktop preview shows zero insets, which makes the layout look fine when it would be broken on a real device.
</Warning>

***

## Resources

<CardGroup cols={2}>
  <Card title="NPM Package" icon="npm" href="https://www.npmjs.com/package/despia-native">
    despia-native
  </Card>

  <Card title="Support" icon="envelope" href="mailto:support@despia.com">
    [support@despia.com](mailto:support@despia.com)
  </Card>
</CardGroup>
