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

react-instagram-stories

v1.1.1

Published

High-performance, fully customizable Instagram-style Stories component for React with TypeScript support

Readme

React Instagram Stories

A high-performance, fully customizable Instagram-style Stories component for React with TypeScript support. Build engaging story experiences with images, videos, text, and custom components.

NPM Version License: MIT

Preview

Features

  • Multiple Content Types: Images, videos (with audio), text, and fully custom components
  • Fully Customizable: Style every aspect of the stories
  • High Performance: Optimized rendering with intelligent preloading
  • Touch & Gestures: Tap, swipe, and hold interactions
  • Keyboard Navigation: Full keyboard support for accessibility
  • TypeScript: Complete type definitions included
  • Accessible: ARIA labels and keyboard navigation
  • Tailwind CSS Support: Customize every sub-element via classNames prop with Tailwind or custom CSS classes
  • 3D Cube Transition: Instagram-style 3D cube drag transition when switching between users
  • Story Resume: Dragging back to a previous user resumes their story from where you left off
  • Lightweight: ~30 KB with zero runtime dependencies
  • No Router Required: Works with native browser history API
  • URL Navigation: Built-in query parameter support (?user=userId&story=storyId)
  • Auto Progress: Smart progress bar that pauses during video buffering
  • Smooth Transitions: 3D cube drag transitions between users, smooth animations between stories

Installation

npm install react-instagram-stories
# or
yarn add react-instagram-stories
# or
pnpm add react-instagram-stories

Note: No additional dependencies required! Works without react-router-dom.

Quick Start

Simple Usage (Recommended)

import { Stories, demoUsers } from "react-instagram-stories";
import "react-instagram-stories/styles.css";

function App() {
  return <Stories users={demoUsers} />;
}

export default App;

That's it! Click on any avatar to open the story viewer. The URL automatically updates to ?user=userId&story=storyId format.

Using Separate Components

import {
  AvatarList,
  StoryViewer,
  demoUsers,
  navigateWithParams,
} from "react-instagram-stories";
import "react-instagram-stories/styles.css";

function App() {
  const handleAvatarClick = (userIndex: number) => {
    const user = demoUsers[userIndex];
    // Navigate using user ID and story ID
    navigateWithParams({ user: user.id, story: user.stories[0].id });
  };

  return (
    <div>
      <h1>My App</h1>
      <AvatarList users={demoUsers} onAvatarClick={handleAvatarClick} />
      <StoryViewer users={demoUsers} />
    </div>
  );
}

URL Navigation

The StoryViewer supports URL-based navigation using query parameters with user ID and story ID:

| URL | Result | | ---------------------------------- | ----------------------------------- | | ?user=user-travel&story=travel-1 | Opens Travel user's first story | | ?user=user-polls&story=poll-1 | Opens Interactive user's poll story | | ?user=user-launch&story=launch-2 | Opens Events user's second story | | No query params | Viewer stays closed |

Navigation Helpers

import { navigateWithParams, clearQueryParams } from "react-instagram-stories";

// Open story viewer with user ID and story ID
navigateWithParams({ user: "user-travel", story: "travel-1" });

// Close story viewer (clear params)
clearQueryParams();

API Reference

<Stories /> Component

The all-in-one component with avatar list and story viewer.

import { Stories } from "react-instagram-stories";

<Stories users={myUsers} />;

| Prop | Type | Default | Description | | ------------ | ------------------- | ------------ | ---------------------------------------------------------------- | | users | User[] | required | Array of user objects with their stories | | classNames | StoriesClassNames | - | Object to customize sub-element CSS classes (Tailwind or custom) |

<StoryViewer /> Component

The story viewer component. Supports two modes:

Query Param Mode (Default)

When isOpen is not provided, reads from URL query params:

<StoryViewer users={myUsers} />

Controlled Mode

When isOpen is provided, you control the viewer state:

<StoryViewer
  users={myUsers}
  isOpen={true}
  initialUserIndex={0}
  initialStoryIndex={0}
  onClose={() => console.log("closed")}
  onStoryChange={(userIndex, storyIndex) => console.log(userIndex, storyIndex)}
/>

| Prop | Type | Default | Description | | ------------------- | --------------------------------- | ------------ | ---------------------------------------------------------------- | | users | User[] | required | Array of user objects | | isOpen | boolean | - | Controls viewer visibility (enables controlled mode) | | initialUserIndex | number | 0 | Starting user index | | initialStoryIndex | number | 0 | Starting story index | | onClose | () => void | - | Called when viewer closes | | onStoryChange | (userIndex, storyIndex) => void | - | Called when story changes | | classNames | StoryViewerClassNames | - | Object to customize sub-element CSS classes (Tailwind or custom) |

<AvatarList /> Component

The horizontal scrollable avatar list.

<AvatarList
  users={myUsers}
  onAvatarClick={(userIndex) => console.log(userIndex)}
/>

| Prop | Type | Description | | --------------- | ----------------------------- | ---------------------------------------------------------------- | | users | User[] | Array of user objects | | onAvatarClick | (userIndex: number) => void | Called when avatar is clicked | | classNames | AvatarListClassNames | Object to customize sub-element CSS classes (Tailwind or custom) |

Story Types

1. Image Story

{
  id: 'unique-id',
  type: 'image',
  src: 'https://example.com/image.jpg',
  alt: 'Description', // Optional
  duration: 5000, // Optional, default: 5000ms
}

2. Video Story

{
  id: 'unique-id',
  type: 'video',
  src: 'https://example.com/video.mp4',
  duration: 10000, // Optional, auto-detected from video
}

Features:

  • Audio enabled by default
  • Progress bar pauses during buffering
  • Auto-detects video duration

3. Text Story

{
  id: 'unique-id',
  type: 'text',
  text: 'Hello World!',
  backgroundColor: '#FF6B6B', // Optional, default: '#000'
  textColor: '#FFFFFF', // Optional, default: '#fff'
  duration: 5000,
}

4. Custom Component Story

Add ANY custom React component as a story!

const MyCustomStory: React.FC<StoryItemControls> = ({
  pause,
  resume,
  next,
  prev,
  setDuration
}) => {
  return (
    <div style={{ height: '100%', background: '#667eea', padding: '20px' }}>
      <h1>Custom Content</h1>
      <button onClick={next}>Next Story</button>
    </div>
  );
};

// In your stories array:
{
  id: 'unique-id',
  type: 'custom_component',
  component: MyCustomStory,
  duration: 5000,
}

Control Methods Available:

  • pause() - Pause the story timer
  • resume() - Resume the story timer
  • next() - Go to next story
  • prev() - Go to previous story
  • setDuration(ms: number) - Update story duration dynamically

Custom Component Examples

Build interactive experiences! Here are examples included in demoUsers:

Poll Component

const PollComponent: React.FC<StoryItemControls> = ({
  pause,
  resume,
  next,
}) => {
  const [selected, setSelected] = React.useState<number | null>(null);
  const [votes, setVotes] = React.useState([42, 28, 18, 12]);
  const options = ["React", "Vue", "Angular", "Svelte"];

  React.useEffect(() => {
    pause(); // Pause timer during interaction
    return () => resume();
  }, [pause, resume]);

  const handleVote = (index: number) => {
    setSelected(index);
    const newVotes = [...votes];
    newVotes[index] += 1;
    setVotes(newVotes);

    setTimeout(() => {
      resume();
      next();
    }, 2000);
  };

  const total = votes.reduce((a, b) => a + b, 0);

  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        height: "100%",
        background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
        padding: "20px",
      }}
    >
      <h2 style={{ color: "white", marginBottom: "20px" }}>
        What's your favorite framework?
      </h2>
      {options.map((option, index) => (
        <button
          key={index}
          onClick={() => handleVote(index)}
          disabled={selected !== null}
          style={{
            margin: "8px 0",
            padding: "15px",
            background:
              selected === index ? "#4CAF50" : "rgba(255,255,255,0.2)",
            color: "white",
            border: "none",
            borderRadius: "12px",
            fontSize: "16px",
            cursor: selected !== null ? "default" : "pointer",
          }}
        >
          <div style={{ display: "flex", justifyContent: "space-between" }}>
            <span>{option}</span>
            {selected !== null && (
              <span>{((votes[index] / total) * 100).toFixed(0)}%</span>
            )}
          </div>
        </button>
      ))}
    </div>
  );
};

Tip: All these examples are included in demoUsers! Import and use them to see how they work.

Styling

Import the default styles:

import "react-instagram-stories/styles.css";

With Tailwind CSS: Import the library styles before your Tailwind CSS so that utility classes can override them:

/* Your global CSS file */
@import "react-instagram-stories/styles.css";

@tailwind base;
@tailwind components;
@tailwind utilities;

Or in JS, import the library CSS before your app's CSS:

// main.tsx
import "react-instagram-stories/styles.css"; // first
import "./index.css"; // your Tailwind CSS — after

Override with custom CSS:

/* Override story viewer background */
.story-viewer {
  background: rgba(0, 0, 0, 0.95);
}

/* Customize progress bars */
.story-progress-bar {
  background: rgba(255, 255, 255, 0.3);
}

.story-progress-bar-fill {
  background: linear-gradient(to right, #ff6b6b, #ee5a6f);
}

/* Style avatars */
.story-avatar-list {
  padding: 20px;
  gap: 16px;
}

.story-avatar-image-wrapper {
  width: 80px;
  height: 80px;
}

Customization with classNames

Every component accepts a classNames prop for fine-grained styling using Tailwind CSS or custom CSS classes. The prop is a typed object that maps to specific sub-elements.

<Stories
  users={users}
  classNames={{
    avatarList: {
      root: "bg-gray-900 p-4",
      avatar: { ring: "border-pink-500", username: "text-xs text-gray-400" },
    },
    storyViewer: {
      overlay: "bg-black/90",
      closeButton: "hover:text-gray-300",
      progressBars: {
        bar: { fill: "bg-gradient-to-r from-pink-500 to-purple-500" },
      },
    },
  }}
/>

You can also pass classNames directly to StoryViewer or AvatarList when using them separately:

<AvatarList
  users={users}
  onAvatarClick={handleClick}
  classNames={{
    root: "bg-gray-900 p-4",
    avatar: { ring: "border-pink-500", username: "text-xs text-gray-400" },
  }}
/>

<StoryViewer
  users={users}
  classNames={{
    overlay: "bg-black/90",
    closeButton: "hover:text-gray-300",
    progressBars: { bar: { fill: "bg-gradient-to-r from-pink-500 to-purple-500" } },
  }}
/>

All classNames types are exported from the package. See the TypeScript Types section for the full list.

Advanced Usage

Using Demo Data

import { Stories, demoUsers } from "react-instagram-stories";
import "react-instagram-stories/styles.css";

function App() {
  return <Stories users={demoUsers} />;
}

The demoUsers includes examples of all story types including interactive polls, quizzes, countdowns, and sliders!

Generate Demo Users

import { generateDemoUsers } from "react-instagram-stories";

const users = generateDemoUsers(10); // 10 users with random stories

Create Custom Stories

import type { User } from "react-instagram-stories";

const myUsers: User[] = [
  {
    id: "1",
    username: "johndoe",
    avatarUrl: "https://example.com/avatar.jpg",
    hasUnreadStories: true, // Shows ring around avatar
    stories: [
      {
        id: "story-1",
        type: "image",
        src: "https://example.com/photo.jpg",
        alt: "Beach sunset",
        duration: 5000,
      },
      {
        id: "story-2",
        type: "video",
        src: "https://example.com/video.mp4",
        // duration auto-detected
      },
      {
        id: "story-3",
        type: "text",
        text: "Hello from my story!",
        backgroundColor: "#FF6B6B",
        textColor: "#FFFFFF",
        duration: 5000,
      },
      {
        id: "story-4",
        type: "custom_component",
        component: MyPollComponent,
        duration: 10000,
      },
    ],
  },
];

Controlled Mode (Without URL)

If you don't want URL navigation, use controlled mode:

import { useState } from "react";
import { AvatarList, StoryViewer } from "react-instagram-stories";

function App() {
  const [viewerState, setViewerState] = useState({
    isOpen: false,
    userIndex: 0,
  });

  return (
    <>
      <AvatarList
        users={myUsers}
        onAvatarClick={(index) =>
          setViewerState({ isOpen: true, userIndex: index })
        }
      />
      <StoryViewer
        users={myUsers}
        initialUserIndex={viewerState.userIndex}
        isOpen={viewerState.isOpen}
        onClose={() => setViewerState({ isOpen: false, userIndex: 0 })}
      />
    </>
  );
}

Keyboard Controls

  • - Navigate stories
  • Space - Pause/Resume
  • Esc - Close viewer

Mouse & Touch

  • Tap Left/Right - Navigate stories
  • Swipe Left/Right - Change users
  • Drag Left/Right - 3D cube transition between users (peek at next/previous user, snaps on release)
  • Swipe Down - Close
  • Hold/Hover - Pause

TypeScript Types

import type {
  User,
  StoryItem,
  StoryItemType,
  StoryItemControls,
  ImageStoryItem,
  VideoStoryItem,
  TextStoryItem,
  CustomComponentStoryItem,
  StoriesClassNames,
  StoryViewerClassNames,
  AvatarListClassNames,
  AvatarClassNames,
  StoryProgressBarsClassNames,
  ProgressBarClassNames,
  StoryItemClassNames,
} from "react-instagram-stories";

// Core Types
type StoryItemType = "image" | "video" | "text" | "custom_component";

interface StoryItemControls {
  pause: () => void;
  resume: () => void;
  next: () => void;
  prev: () => void;
  setDuration: (ms: number) => void;
}

interface User {
  id: string;
  username: string;
  avatarUrl: string;
  stories: StoryItem[];
  hasUnreadStories?: boolean;
}

// ClassNames Types (for Tailwind / custom CSS customization)
interface StoriesClassNames {
  avatarList?: AvatarListClassNames;
  storyViewer?: StoryViewerClassNames;
}

interface AvatarListClassNames {
  root?: string;
  avatar?: AvatarClassNames;
}

interface AvatarClassNames {
  root?: string;
  ring?: string;
  image?: string;
  username?: string;
}

interface StoryViewerClassNames {
  overlay?: string;
  closeButton?: string;
  progressBars?: StoryProgressBarsClassNames;
  storyItem?: StoryItemClassNames;
}

interface StoryProgressBarsClassNames {
  root?: string;
  bar?: ProgressBarClassNames;
}

interface ProgressBarClassNames {
  root?: string;
  fill?: string;
}

interface StoryItemClassNames {
  root?: string;
}

Package Exports

// Components
import { Stories, StoryViewer, AvatarList } from "react-instagram-stories";

// Navigation helpers
import { navigateWithParams, clearQueryParams } from "react-instagram-stories";

// Demo data
import { demoUsers, generateDemoUsers } from "react-instagram-stories";

// Types
import type {
  User,
  StoryItem,
  StoryItemControls,
  StoryItemType,
  ImageStoryItem,
  VideoStoryItem,
  TextStoryItem,
  CustomComponentStoryItem,
  StoriesClassNames,
  StoryViewerClassNames,
  AvatarListClassNames,
  AvatarClassNames,
  StoryProgressBarsClassNames,
  ProgressBarClassNames,
  StoryItemClassNames,
} from "react-instagram-stories";

// Styles
import "react-instagram-stories/styles.css";

Performance

  • Bundle Size: ~30 KB (minified)
  • Gzipped: ~10 KB
  • Zero Runtime Dependencies: No production dependencies at all (framer-motion and tailwindcss-animate are dev-only)
  • Smart Preloading: Preloads adjacent stories
  • Optimized Rendering: Uses React.memo
  • Video Buffering Detection: Pauses progress during buffering

Tech Stack

  • React 18+
  • TypeScript
  • CSS 3D Transforms (cube transition between users)
  • Native Browser History API (no router needed)
  • tsup (bundler)

Contributing

Contributions welcome! Open an issue or PR.

License

MIT © Ankit Jangir

Support


Made with love by Ankit Jangir