# useLayer Hook vs LayerProvider Component in Astryx: Key Differences Explained

> Discover the key differences between Astryx useLayer hook for individual overlays and LayerProvider for app-wide context. Learn when to use each.

- Repository: [Meta/astryx](https://github.com/facebook/astryx)
- Tags: deep-dive
- Published: 2026-08-04

---

**`useLayer` creates individual overlay instances with imperative control, while `LayerProvider` supplies application-wide context for shared resources like toasts.**

The Astryx design system (`facebook/astryx`) provides two distinct primitives for managing layered UI elements: the `useLayer` hook and the `LayerProvider` component. Understanding when to use each is essential for building performant, accessible overlays in React applications. This guide breaks down their architectural differences, implementation details, and practical use cases based on the source code in `packages/core/src/Layer/`.

## What `useLayer` Does: Local Overlay Management

`useLayer` is a **React hook** defined in [`packages/core/src/Layer/useLayer.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Layer/useLayer.tsx) that creates a single, self-contained overlay layer. It returns an imperative API for showing, hiding, and rendering positioned UI elements.

### Core Capabilities

- **Two positioning modes**: **context mode** for CSS-anchor positioning relative to a trigger element, and **fixed mode** for manual `x`/`y` coordinates
- **Popover API integration**: Uses native `showPopover()`/`hidePopover()` with automatic fallback for unsupported browsers
- **CSS anchor generation**: Dynamically creates `positionAnchor`, `positionArea`, and `positionTryFallbacks` styles

### Returned API

The hook returns an object with these properties:

| Property | Purpose |
|----------|---------|
| `ref` | Attach to trigger element for anchor positioning |
| `show()` / `hide()` | Imperative visibility control |
| `isOpen` | Boolean state of the layer |
| `id` | Unique layer identifier |
| `render(children, options)` | Render function for the overlay content |

### `useLayer` Example: Building a Custom Tooltip

```tsx
import {useLayer} from '@astryxdesign/core/Layer';

export function TooltipDemo() {
  const layer = useLayer({mode: 'context'});

  return (
    <>
      <button
        ref={layer.ref}
        onMouseEnter={layer.show}
        onMouseLeave={layer.hide}
      >
        Hover me
      </button>

      {layer.render(
        <div className="tooltip-content">I am a tooltip</div>,
        {placement: 'above', alignment: 'center'}
      )}
    </>
  );
}

```

**Key implementation detail**: The `ref` attaches a CSS anchor name, enabling the browser's native anchor positioning. The `render` method accepts `ContextRenderProps` or `FixedRenderOptions` depending on the configured mode. Each call to `useLayer` creates an independent layer instance—no provider required.

## What `LayerProvider` Does: Global Context Distribution

`LayerProvider` is a **React component** defined in [`packages/core/src/Layer/LayerProvider.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Layer/LayerProvider.tsx) that establishes application-wide layer infrastructure. Unlike `useLayer`, it does not create individual overlays; it makes shared resources available to descendant components.

### Responsibilities

- Creates `LayerContext` containing toast configuration and viewport settings
- mounts a `<ToastViewport>` that renders toast notifications from any `useToast` call
- Prevents duplicate provider nesting via an `isProvider` flag

### Dependency Relationship

`LayerProvider` is **required** for:
- `useToast` hook to locate the shared toast viewport
- Any component reading `useLayerContext()` for global layer data

When absent, toast-related hooks fall back to a lazily self-mounted viewport (with a development warning).

### `LayerProvider` Example: Configuring App-Wide Toasts

```tsx
import {LayerProvider} from '@astryxdesign/core/Layer';
import {App} from './App';

export default function Root() {
  return (
    <LayerProvider
      toast={{
        position: 'topEnd',
        maxVisible: 3,
        inset: {top: 12, end: 12}
      }}
    >
      <App />
    </LayerProvider>
  );
}

```

This configuration sets toast position, limits visible toasts to three, and applies edge insets. The provider renders no visible UI of its own—only the internal `ToastViewport`.

## Direct Comparison: useLayer vs LayerProvider

| Aspect | `useLayer` | `LayerProvider` |
|--------|-----------|-----------------|
| **Type** | React hook (`export function useLayer`) | React component (`export function LayerProvider`) |
| **Scope** | Local—each call creates one layer | Global—single instance near app root |
| **Primary use** | Popovers, tooltips, dropdowns, fixed UI | Toast configuration, shared overlay context |
| **Positioning** | Context (CSS anchor) or fixed (x/y) coordinates | None—provides configuration only |
| **Dependencies** | Independent; works without any provider | Required by `useToast`, optional for `useLayer` |
| **Rendering** | You call `layer.render()` to output UI | Mounts `<ToastViewport>` automatically |

## How They Work Together

These primitives are **complementary**, not competing. A typical Astryx application uses both:

1. **Once**: Wrap your app with `LayerProvider` to enable toasts
2. **Many times**: Call `useLayer` in individual components that need positioned overlays

### Combined Usage Example

```tsx
// Root.tsx — LayerProvider configures global toast behavior
import {LayerProvider} from '@astryxdesign/core/Layer';

export default function Root() {
  return (
    <LayerProvider toast={{position: 'bottomCenter', maxVisible: 5}}>
      <App />
    </LayerProvider>
  );
}

// UserCard.tsx — useLayer creates a local dropdown
import {useLayer} from '@astryxdesign/core/Layer';
import {useToast} from '@astryxdesign/core/Toast';

export function UserCard() {
  const dropdown = useLayer({mode: 'context'});
  const toast = useToast();

  const handleDelete = () => {
    toast({title: 'User deleted', tone: 'negative'});
    dropdown.hide();
  };

  return (
    <>
      <button ref={dropdown.ref} onClick={dropdown.show}>
        Options
      </button>
      
      {dropdown.render(
        <menu>
          <button onClick={handleDelete}>Delete</button>
        </menu>,
        {placement: 'below', alignment: 'end'}
      )}
    </>
  );
}

```

The `useLayer` call manages the dropdown overlay independently. The `useToast` call finds the `LayerProvider` context and renders in the shared viewport.

## Implementation Highlights from Source

### [`useLayer.tsx`](https://github.com/facebook/astryx/blob/main/useLayer.tsx) architecture

The hook defines separate option types for each mode:

```typescript
// From packages/core/src/Layer/useLayer.tsx
type ContextLayerOptions = {
  mode: 'context';
  // trigger reference handled via returned ref
};

type FixedLayerOptions = {
  mode: 'fixed';
  x: number;
  y: number;
};

```

It generates CSS anchor styles dynamically and handles the Popover API lifecycle:

- Calls `element.showPopover({source: triggerElement})` for context positioning
- Falls back to attribute-based positioning when `showPopover` is unavailable

### [`LayerProvider.tsx`](https://github.com/facebook/astryx/blob/main/LayerProvider.tsx) context structure

The provider creates `LayerContextValue` with:

```typescript
// From packages/core/src/Layer/LayerContext.ts
interface LayerContextValue {
  toast?: ToastConfig;
  isProvider: boolean;
}

```

The `isProvider` flag prevents nested providers from mounting duplicate viewports. If a descendant `LayerProvider` detects `isProvider: true` from context, it renders children without creating a new context.

### Context consumption in [`useToast.tsx`](https://github.com/facebook/astryx/blob/main/useToast.tsx)

The toast hook demonstrates the provider's purpose:

```typescript
// From packages/core/src/Toast/useToast.tsx
const context = useLayerContext();

if (!context?.isProvider) {
  // Lazily mount fallback viewport with warning
}

```

This graceful degradation ensures toasts work even without `LayerProvider`, though with potentially suboptimal UX.

## Summary

- **`useLayer`** creates **individual overlay layers** with imperative `show`/`hide` control and CSS-anchor or fixed positioning—use it for tooltips, popovers, and dropdowns
- **`LayerProvider`** supplies **global toast configuration** and mounts a shared `ToastViewport`—place it once near your app root
- **They operate independently**: `useLayer` requires no provider; `useToast` requires `LayerProvider` for optimal behavior
- **Source locations**: [`packages/core/src/Layer/useLayer.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Layer/useLayer.tsx), [`packages/core/src/Layer/LayerProvider.tsx`](https://github.com/facebook/astryx/blob/main/packages/core/src/Layer/LayerProvider.tsx), and [`packages/core/src/Layer/LayerContext.ts`](https://github.com/facebook/astryx/blob/main/packages/core/src/Layer/LayerContext.ts)

## Frequently Asked Questions

### Does `useLayer` require `LayerProvider` to work?

No. `useLayer` operates independently and creates fully functional overlay layers without any surrounding provider. It only reads `LayerContext` for optional toast-related fallback behavior, which most `useLayer` consumers never encounter.

### What happens if I use `useToast` without `LayerProvider`?

The hook falls back to a lazy self-mounting viewport. You'll see a development warning encouraging you to add `LayerProvider`. The toast will display, but each call may create its own viewport instance instead of using a centralized stack.

### Can I use multiple `LayerProvider` instances?

Nesting providers is handled safely—the second provider detects `isProvider: true` from context and renders children without duplicating the viewport. However, only the outermost provider's configuration applies; nested toast configs are ignored.

### When should I choose fixed mode over context mode in `useLayer`?

Use **context mode** when you have a reference DOM element to anchor against (tooltips, dropdowns). Use **fixed mode** for positioning at absolute viewport coordinates—such as context menus triggered at mouse position or draggable elements that need precise pixel control.