useLayer Hook vs LayerProvider Component in Astryx: Key Differences Explained

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 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

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 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

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

// 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 architecture

The hook defines separate option types for each mode:

// 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 context structure

The provider creates LayerContextValue with:

// 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

The toast hook demonstrates the provider's purpose:

// 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

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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →