Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@howells/stacksheet

Package Overview
Dependencies
Maintainers
1
Versions
22
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@howells/stacksheet

Typed, animated sheet stack system. Zustand store + Motion animations with Apple-style depth stacking.

Source
npmnpm
Version
1.3.0
Version published
Weekly downloads
24
-82.09%
Maintainers
1
Weekly downloads
 
Created
Source

Stacksheet

Typed, animated sheet stacking for React.

Stacksheet gives you a provider and a hook for opening, pushing, replacing, and navigating sheet panels from anywhere in your app. It handles the motion, layering, focus management, scroll locking, and mobile/desktop presentation details for you.

npm install @howells/stacksheet

Peer dependencies: react >= 18, react-dom >= 18.

What it includes

  • Direct-component API: open(Component, props)
  • Optional type registry for large apps: open("user-profile", "u1", data)
  • Stacked navigation: open, push, replace, swap, navigate, pop, close
  • Desktop side panels and mobile bottom sheets from one API
  • Built-in focus trapping, Escape handling, scroll lock, and Android back gesture support
  • Drag-to-dismiss with configurable snap points
  • Classic mode with auto header, or composable mode with Sheet.* parts

Quick start

Create a sheet instance once and use it across your app:

import { createStacksheet } from "@howells/stacksheet";
import "@howells/stacksheet/styles.css";

export const { StacksheetProvider, useSheet } = createStacksheet();

Wrap your app:

import { StacksheetProvider } from "./sheets";

export function App() {
  return (
    <StacksheetProvider>
      <YourRoutes />
    </StacksheetProvider>
  );
}

Open a sheet from any component:

import { useSheet } from "./sheets";

function UserProfile({ userId }: { userId: string }) {
  const { close } = useSheet();

  return (
    <div>
      <h2>User {userId}</h2>
      <button onClick={close}>Done</button>
    </div>
  );
}

function ViewProfileButton() {
  const { open } = useSheet();

  return (
    <button onClick={() => open(UserProfile, { userId: "u_abc" })}>
      View profile
    </button>
  );
}

Typed registry mode

If your app has a fixed set of known sheet types, you can pre-register them for string-keyed, compile-time checked actions:

import { createStacksheet } from "@howells/stacksheet";

const { StacksheetProvider, useSheet } = createStacksheet<{
  "user-profile": { userId: string };
  settings: { tab?: string };
}>();

function UserProfile({ userId }: { userId: string }) {
  return <div>User {userId}</div>;
}

function Settings({ tab }: { tab?: string }) {
  return <div>Settings: {tab}</div>;
}

function App() {
  return (
    <StacksheetProvider
      sheets={{
        "user-profile": UserProfile,
        settings: Settings,
      }}
    >
      <YourRoutes />
    </StacksheetProvider>
  );
}

function OpenSettingsButton() {
  const { open } = useSheet();
  return (
    <button onClick={() => open("settings", "settings-root", { tab: "billing" })}>
      Open settings
    </button>
  );
}

Composable layout

Classic mode is the default: Stacksheet renders the panel chrome and header for you.

If you want full control over the panel structure, use layout="composable" and build the panel with exported parts:

import { Sheet } from "@howells/stacksheet";

function App() {
  return (
    <StacksheetProvider layout="composable">
      <YourRoutes />
    </StacksheetProvider>
  );
}

function SettingsSheet() {
  return (
    <>
      <Sheet.Handle />
      <Sheet.Header>
        <Sheet.Back />
        <Sheet.Title>Settings</Sheet.Title>
        <Sheet.Close />
      </Sheet.Header>
      <Sheet.Body>
        <div className="p-4">Panel content</div>
      </Sheet.Body>
    </>
  );
}

For custom controls and panel metadata inside a sheet, use useSheetPanel().

Accessibility

The default panel is rendered as a dialog with focus management, keyboard navigation, and focus restoration built in.

You can set a global label:

createStacksheet({ ariaLabel: "Settings panel" });

Or override it per sheet:

const { open } = useSheet();

open(UserProfile, { userId: "u_abc" }, { ariaLabel: "User profile for Jane" });

In composable mode, use Sheet.Title and Sheet.Description so the panel gets aria-labelledby and aria-describedby automatically.

Documentation

Full docs, interactive playground, and API reference:

stacksheet.danielhowells.com

License

MIT

Keywords

sheet

FAQs

Package last updated on 30 Apr 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts