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/ycoordinates - Popover API integration: Uses native
showPopover()/hidePopover()with automatic fallback for unsupported browsers - CSS anchor generation: Dynamically creates
positionAnchor,positionArea, andpositionTryFallbacksstyles
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
LayerContextcontaining toast configuration and viewport settings - mounts a
<ToastViewport>that renders toast notifications from anyuseToastcall - Prevents duplicate provider nesting via an
isProviderflag
Dependency Relationship
LayerProvider is required for:
useToasthook 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:
- Once: Wrap your app with
LayerProviderto enable toasts - Many times: Call
useLayerin 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
showPopoveris 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
useLayercreates individual overlay layers with imperativeshow/hidecontrol and CSS-anchor or fixed positioning—use it for tooltips, popovers, and dropdownsLayerProvidersupplies global toast configuration and mounts a sharedToastViewport—place it once near your app root- They operate independently:
useLayerrequires no provider;useToastrequiresLayerProviderfor optimal behavior - Source locations:
packages/core/src/Layer/useLayer.tsx,packages/core/src/Layer/LayerProvider.tsx, andpackages/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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →