npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

layout-pusher

v1.0.4

Published

A React library for stack-based layout navigation with mobile-like animations and transitions

Readme

Layout Pusher

A React library for stack-based layout navigation with smooth, mobile-like animations. Inspired by iOS and Telegram navigation patterns.

npm version npm downloads license


Features

  • Stack-based Navigation - Push and pop layouts like native mobile apps
  • Smooth Animations - Fade-focused transitions with subtle transforms (Telegram-style)
  • Gesture Support - Swipe from left edge to go back
  • State Preservation - Component state persists when navigating back
  • TypeScript Ready - Full type definitions included
  • Lightweight - ~7KB gzipped, zero dependencies (except peer deps)
  • Customizable - Multiple animation presets and custom configurations

Installation

npm install layout-pusher framer-motion
yarn add layout-pusher framer-motion
pnpm add layout-pusher framer-motion

Peer Dependencies

Layout Pusher requires the following peer dependencies:

  • react >= 18.0.0
  • react-dom >= 18.0.0
  • framer-motion >= 10.0.0

Quick Start

import { LayoutPusherProvider, LayoutStack, LayoutViewProps } from 'layout-pusher';

// Define your layouts
function HomeScreen({ push }: LayoutViewProps) {
  return (
    <div>
      <h1>Home</h1>
      <button onClick={() => push(DetailScreen, { id: 123 })}>
        View Details
      </button>
    </div>
  );
}

function DetailScreen({ data, goBack }: LayoutViewProps<{ id: number }>) {
  return (
    <div>
      <button onClick={goBack}>Back</button>
      <h1>Detail #{data?.id}</h1>
    </div>
  );
}

// Set up your app
function App() {
  return (
    <LayoutPusherProvider initialLayout={HomeScreen}>
      <LayoutStack style={{ width: '100%', height: '100vh' }} />
    </LayoutPusherProvider>
  );
}

Core Concepts

The Layout Stack

Layout Pusher manages a stack of layouts. When you "push" a new layout, it animates in on top of the current one. When you "pop", the top layout animates out, revealing the previous one.

┌─────────────────┐
│   Layout C      │  ← Top (visible)
├─────────────────┤
│   Layout B      │  ← Faded/dimmed
├─────────────────┤
│   Layout A      │  ← More faded
└─────────────────┘

Layout Props

Every layout component receives these props automatically:

interface LayoutViewProps<T = unknown> {
  layoutId: string;        // Unique ID for this layout instance
  data?: T;                // Props passed when pushing this layout
  goBack: () => void;      // Pop this layout
  push: (Component, props?) => string;   // Push a new layout
  replace: (Component, props?) => string; // Replace current layout
  popTo: (id: string) => void;   // Pop to a specific layout
  popToRoot: () => void;   // Pop all layouts except the first
  canGoBack: boolean;      // Whether there's a layout to go back to
  depth: number;           // Current stack depth
}

API Reference

Components

<LayoutPusherProvider>

The root provider component that manages the navigation stack.

<LayoutPusherProvider
  initialLayout={HomeScreen}      // Initial layout component
  initialProps={{ user: 'John' }} // Props for initial layout
  config={{
    animation: 'slide',           // 'slide' | 'fade' | 'scale' | 'none'
    animationDuration: 300,       // Duration in milliseconds
    gestureEnabled: true,         // Enable swipe-back gesture
    preserveState: true,          // Preserve component state
  }}
>
  {children}
</LayoutPusherProvider>

<LayoutStack>

The container that renders the stack of layouts with animations.

<LayoutStack
  className="my-stack"
  style={{ width: '100%', height: '100vh' }}
/>

<LayoutHeader>

An optional header component with built-in back button.

<LayoutHeader
  title="Settings"
  showBackButton={true}
  onBack={() => console.log('Going back')}
  rightAction={<button>Save</button>}
/>

Hooks

useLayoutPusher()

The main hook for navigation actions.

function MyComponent() {
  const {
    push,       // Push a new layout
    pop,        // Go back one layout
    popTo,      // Go back to a specific layout by ID
    popToRoot,  // Go back to the first layout
    replace,    // Replace current layout
    canGoBack,  // Boolean: can we go back?
    depth       // Current stack depth
  } = useLayoutPusher();

  return (
    <button onClick={() => push(OtherScreen, { foo: 'bar' })}>
      Navigate
    </button>
  );
}

useLayoutStack()

Read-only access to the stack state.

function MyComponent() {
  const {
    stack,          // Array of all layouts in the stack
    currentLayout,  // The top layout
    depth           // Stack depth
  } = useLayoutStack();

  return <div>Current depth: {depth}</div>;
}

useLayoutAnimation()

Access to animation state (useful for custom animations).

function MyComponent() {
  const {
    isPushing,        // Currently pushing a layout
    isPopping,        // Currently popping a layout
    direction,        // 'forward' | 'backward' | null
    animationType,    // Current animation type
    animationDuration // Current duration
  } = useLayoutAnimation();

  return null;
}

Navigation Methods

push()

Push a new layout onto the stack.

// Basic push
push(ProfileScreen);

// Push with data
push(ProfileScreen, { userId: 123 });

// Push returns the layout ID
const layoutId = push(ProfileScreen, { userId: 123 });

pop()

Remove the top layout from the stack.

goBack(); // or pop() from useLayoutPusher

popTo()

Pop to a specific layout by its ID.

const homeId = push(HomeScreen);
push(SettingsScreen);
push(ProfileScreen);

// Later: go directly back to home
popTo(homeId);

popToRoot()

Pop all layouts except the first one.

// From any depth, go back to the root
popToRoot();

replace()

Replace the current layout without animation.

// Replace current layout
replace(NewScreen, { data: 'value' });

Configuration

Animation Types

<LayoutPusherProvider config={{ animation: 'slide' }}>

| Type | Description | |------|-------------| | slide | Slide from right with fade (default, Telegram-style) | | fade | Simple opacity fade | | scale | Scale up/down with fade | | none | No animation |

Custom Timing

<LayoutPusherProvider
  config={{
    animationDuration: 400, // milliseconds
  }}
>

Gesture Control

<LayoutPusherProvider
  config={{
    gestureEnabled: true, // Enable swipe-back from left edge
  }}
>

Styling

CSS Variables

Customize the appearance using CSS variables:

:root {
  /* Stack container background (visible during transitions) */
  --layout-pusher-bg: #000000;

  /* Individual layout background */
  --layout-pusher-layout-bg: #ffffff;

  /* Header styles */
  --layout-pusher-header-bg: #ffffff;
  --layout-pusher-header-border: #e0e0e0;
  --layout-pusher-header-text: #333333;
  --layout-pusher-header-hover: rgba(0, 0, 0, 0.05);
  --layout-pusher-header-active: rgba(0, 0, 0, 0.1);
}

Dark Mode Example

:root {
  --layout-pusher-bg: #0a0a0a;
  --layout-pusher-layout-bg: #1a1a1a;
  --layout-pusher-header-bg: #1a1a1a;
  --layout-pusher-header-border: #333333;
  --layout-pusher-header-text: #ffffff;
}

TypeScript

Layout Pusher is written in TypeScript and exports all types:

import type {
  LayoutViewProps,
  LayoutItem,
  LayoutPusherConfig,
  LayoutPusherContextValue,
  LayoutHeaderProps,
  LayoutStackProps,
  AnimationType,
} from 'layout-pusher';

// Type your layout data
interface UserData {
  userId: number;
  name: string;
}

function UserProfile({ data }: LayoutViewProps<UserData>) {
  // data is typed as UserData | undefined
  return <div>Hello, {data?.name}</div>;
}

// Push with type safety
push(UserProfile, { userId: 1, name: 'John' });

Advanced Usage

Nested Navigation

You can nest multiple LayoutPusherProviders for complex navigation:

function MainApp() {
  return (
    <LayoutPusherProvider initialLayout={TabNavigator}>
      <LayoutStack />
    </LayoutPusherProvider>
  );
}

function TabNavigator() {
  const [tab, setTab] = useState('home');

  return (
    <div>
      <div className="tab-content">
        {tab === 'home' && (
          <LayoutPusherProvider initialLayout={HomeScreen}>
            <LayoutStack />
          </LayoutPusherProvider>
        )}
        {tab === 'settings' && (
          <LayoutPusherProvider initialLayout={SettingsScreen}>
            <LayoutStack />
          </LayoutPusherProvider>
        )}
      </div>
      <TabBar value={tab} onChange={setTab} />
    </div>
  );
}

Programmatic Navigation

Use the hook outside of layout components:

function NavigationButton() {
  const { push, canGoBack, goBack } = useLayoutPusher();

  return (
    <div>
      {canGoBack && <button onClick={goBack}>Back</button>}
      <button onClick={() => push(NextScreen)}>Next</button>
    </div>
  );
}

Animation Presets

Access built-in animation presets:

import {
  slidePreset,
  fadePreset,
  scalePreset,
  telegramSpring,
  telegramTween,
} from 'layout-pusher';

// Use in custom Framer Motion components
<motion.div
  initial={slidePreset.initial}
  animate={slidePreset.animate}
  exit={slidePreset.exit}
  transition={telegramSpring}
/>

Examples

Basic Chat App Structure

function ChatList({ push }: LayoutViewProps) {
  const chats = [...];

  return (
    <div>
      {chats.map(chat => (
        <div
          key={chat.id}
          onClick={() => push(ChatView, { chatId: chat.id })}
        >
          {chat.name}
        </div>
      ))}
    </div>
  );
}

function ChatView({ data, goBack, push }: LayoutViewProps<{ chatId: number }>) {
  return (
    <div>
      <header onClick={() => push(ChatInfo, { chatId: data?.chatId })}>
        <button onClick={goBack}>Back</button>
        <span>Chat {data?.chatId}</span>
      </header>
      <MessageList chatId={data?.chatId} />
    </div>
  );
}

function ChatInfo({ data, goBack, popToRoot }: LayoutViewProps<{ chatId: number }>) {
  return (
    <div>
      <button onClick={goBack}>Back</button>
      <h1>Chat Info</h1>
      <button onClick={popToRoot}>Back to Chat List</button>
    </div>
  );
}

With Header Component

function SettingsScreen({ goBack }: LayoutViewProps) {
  return (
    <div>
      <LayoutHeader
        title="Settings"
        rightAction={
          <button onClick={handleSave}>Save</button>
        }
      />
      <div className="content">
        {/* Settings content */}
      </div>
    </div>
  );
}

Browser Support

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)
  • iOS Safari (latest)
  • Android Chrome (latest)

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting a PR.


License

MIT License - see LICENSE for details.


Changelog

1.0.2

  • Initial public release
  • Telegram-style fade animations
  • Swipe-back gesture support
  • TypeScript definitions
  • CSS variable theming