react-infinite-scroll-component vs react-virtuoso vs react-window
Efficient List Rendering and Infinite Scrolling in React
react-infinite-scroll-componentreact-virtuosoreact-windowSimilar Packages:

Efficient List Rendering and Infinite Scrolling in React

react-infinite-scroll-component, react-virtuoso, and react-window are all React libraries designed to improve performance when rendering large lists or implementing infinite scroll behavior. They address the common problem of slow rendering, high memory usage, and poor user experience that occurs when trying to display thousands of items at once in the DOM. While they share this goal, their underlying approaches, APIs, and use cases differ significantly. react-window provides low-level virtualization primitives focused on performance and minimalism. react-virtuoso offers a higher-level, feature-rich virtualized list with built-in support for dynamic item sizes, headers, footers, and grouping. react-infinite-scroll-component is not a virtualization library but rather a wrapper that triggers load-more callbacks as the user scrolls near the bottom of a container, typically used alongside pagination strategies.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
react-infinite-scroll-component03,082217 kB134a month agoMIT
react-virtuoso06,419243 kB5416 days agoMIT
react-window017,195209 kB25 months agoMIT

Efficient List Rendering in React: react-infinite-scroll-component vs react-virtuoso vs react-window

When building modern web apps, you’ll often face a choice: how do you efficiently render long lists without slowing down the browser? The three libraries — react-infinite-scroll-component, react-virtuoso, and react-window — each offer different strategies. One isn’t universally “better”; the right pick depends on your data, UX needs, and performance constraints.

🎯 Core Philosophy: What Problem Are You Solving?

react-infinite-scroll-component assumes you’re loading data in chunks (e.g., from a paginated API) and just need a way to detect when the user has scrolled near the bottom to fetch the next page. It does not virtualize — it renders every item you give it. So if you load 10 pages of 50 items each, all 500 DOM nodes stay in memory.

// react-infinite-scroll-component: Simple infinite scroll
import InfiniteScroll from 'react-infinite-scroll-component';

function MyList({ items, loadMore, hasMore }) {
  return (
    <InfiniteScroll
      dataLength={items.length}
      next={loadMore}
      hasMore={hasMore}
      loader={<div>Loading...</div>}
    >
      {items.map(item => <div key={item.id}>{item.name}</div>)}
    </InfiniteScroll>
  );
}

react-window takes the opposite approach: it only renders what’s visible (plus a small buffer), no matter how large your dataset. But it requires you to know or fix the height of each item ahead of time.

// react-window: Fixed-size list
import { FixedSizeList as List } from 'react-window';

const Row = ({ index, style }) => (
  <div style={style}>Row {index}</div>
);

function MyList({ itemCount }) {
  return (
    <List
      height={600}
      itemCount={itemCount}
      itemSize={50}
      width="100%"
    >
      {Row}
    </List>
  );
}

react-virtuoso sits in the middle: it virtualizes like react-window, but automatically measures item heights and supports dynamic content, headers, and more — all with less boilerplate.

// react-virtuoso: Auto-sizing list
import { Virtuoso } from 'react-virtuoso';

function MyList({ items }) {
  return (
    <Virtuoso
      style={{ height: 600 }}
      data={items}
      itemContent={(index, item) => <div>{item.name}</div>}
    />
  );
}

📏 Handling Dynamic Item Heights

This is where the libraries diverge sharply.

react-window forces you to choose between two components:

  • FixedSizeList: all items same height (fastest)
  • VariableSizeList: you must provide an itemSize function that returns the height for each index

If your content height depends on text length or images, you’ll need to pre-calculate or cache sizes — which adds complexity.

// react-window: Variable size list
import { VariableSizeList as List } from 'react-window';

const getItemSize = (index) => {
  // Must return exact pixel height for index
  return Math.random() * 50 + 30; // Not realistic — you’d use real logic
};

const Row = ({ index, style }) => (
  <div style={style}>Dynamic row {index}</div>
);

function MyList({ itemCount }) {
  return (
    <List
      height={600}
      itemCount={itemCount}
      itemSize={getItemSize}
      width="100%"
    >
      {Row}
    </List>
  );
}

react-virtuoso measures items automatically after they render. No need to guess heights — it works out of the box with variable content.

// react-virtuoso: Handles dynamic heights automatically
import { Virtuoso } from 'react-virtuoso';

function MyList({ messages }) {
  return (
    <Virtuoso
      style={{ height: 600 }}
      data={messages}
      itemContent={(index, message) => (
        <div>
          <p>{message.text}</p>
          <small>{message.timestamp}</small>
        </div>
      )}
    />
  );
}

react-infinite-scroll-component doesn’t care about heights — it renders everything. So dynamic heights are trivial to implement, but performance suffers as the list grows.

🔁 Combining Infinite Scroll with Virtualization

What if you want both infinite loading and virtualization? Only react-virtuoso and react-window support this natively.

react-virtuoso makes it easy with the endReached prop:

// react-virtuoso: Infinite scroll + virtualization
import { Virtuoso } from 'react-virtuoso';

function InfiniteVirtuosoList({ items, loadMore, hasMore }) {
  return (
    <Virtuoso
      style={{ height: 600 }}
      data={items}
      itemContent={(index, item) => <div>{item.name}</div>}
      endReached={() => hasMore && loadMore()}
      components={{
        Footer: () => hasMore ? <div>Loading more...</div> : null
      }}
    />
  );
}

react-window requires manual scroll tracking using onItemsRendered and managing your own loading state:

// react-window: Manual infinite scroll
import { FixedSizeList as List } from 'react-window';
import { useEffect, useState } from 'react';

function InfiniteWindowList({ items, loadMore, hasMore, totalItemCount }) {
  const [loading, setLoading] = useState(false);

  const handleItemsRendered = ({ visibleStopIndex }) => {
    if (!hasMore || loading) return;
    // Trigger load when near the end
    if (visibleStopIndex >= items.length - 5) {
      setLoading(true);
      loadMore().finally(() => setLoading(false));
    }
  };

  return (
    <List
      height={600}
      itemCount={totalItemCount}
      itemSize={50}
      width="100%"
      onItemsRendered={handleItemsRendered}
    >
      {({ index, style }) => <div style={style}>{items[index]?.name || 'Loading...'}</div>}
    </List>
  );
}

react-infinite-scroll-component cannot be combined with virtualization — it’s either/or. If you try to wrap a virtualized list inside it, you’ll break scrolling detection.

🧱 Advanced Layouts: Headers, Footers, Grouping

Need sticky section headers? Or a “Load More” button at the bottom?

react-virtuoso supports this via the components prop:

// react-virtuoso: Custom header and footer
<Virtuoso
  data={items}
  itemContent={(index, item) => <Item {...item} />}
  components={{
    Header: () => <div className="sticky-header">Top</div>,
    Footer: () => <button onClick={loadMore}>Load More</button>
  }}
/>

It also has built-in support for grouped lists with sticky group headers.

react-window requires you to manually compose headers/footers outside the list or use react-window-infinite-loader (a separate package) for more complex scenarios.

react-infinite-scroll-component lets you put anything inside its children, so headers and footers are easy — but again, everything stays in the DOM.

⚙️ Performance and Control

  • react-window is the fastest because it avoids layout thrashing and uses pure components. But you trade convenience for control.
  • react-virtuoso is slightly slower due to runtime measurements, but the difference is negligible for most apps, and it saves you from writing error-prone sizing logic.
  • react-infinite-scroll-component has no performance optimizations — it’s just a scroll listener. Fine for short lists (<100 items), unusable for long ones.

🛑 When to Avoid Each

  • Avoid react-infinite-scroll-component if your list can grow beyond a few hundred items. It will cause jank, memory bloat, and slow re-renders.
  • Avoid react-window if your items have unpredictable heights and you can’t pre-compute them reliably. You’ll spend more time debugging sizing than building features.
  • Avoid react-virtuoso only if you’re in an extreme performance scenario (e.g., rendering 10k+ rows in a trading dashboard) and can guarantee fixed heights — then react-window might edge it out.

✅ Summary Table

Featurereact-infinite-scroll-componentreact-virtuosoreact-window
Virtualization❌ No✅ Yes✅ Yes
Dynamic Item Heights✅ (but renders all)✅ Automatic⚠️ Manual (itemSize fn)
Infinite Scroll Built-in✅ Yes✅ Via endReached❌ Manual implementation
Headers / Footers✅ Easy (but not virtualized)✅ Via components prop⚠️ Manual composition
Best ForShort paginated listsChat, feeds, dynamic contentFixed-height grids, dashboards

💡 Final Recommendation

  • If you’re loading pages from an API and showing <200 total items: react-infinite-scroll-component is quick and simple.
  • If your list has variable content, unknown heights, or needs rich UI (stickers, groups): react-virtuoso is the smoothest experience.
  • If you’re building a high-performance table or timeline with uniform rows: react-window gives you raw speed and control.

Don’t mix infinite scroll with non-virtualized rendering — it’s a common anti-pattern that leads to degraded performance over time. Choose virtualization early if your dataset could grow large.

How to Choose: react-infinite-scroll-component vs react-virtuoso vs react-window

  • react-infinite-scroll-component:

    Choose react-infinite-scroll-component if you need a simple way to trigger data fetching when the user scrolls to the end of a list, and you're already handling rendering (e.g., with standard React components or another list library). It’s best suited for paginated APIs where you append new items to an existing list. Avoid it if you’re rendering thousands of items at once — it doesn’t virtualize content, so performance will degrade as the list grows.

  • react-virtuoso:

    Choose react-virtuoso when you need a full-featured virtualized list with minimal setup, especially if your items have dynamic or unknown heights, or if you require features like sticky headers, grouped items, or custom scroll containers. It handles complex scenarios out of the box and provides a clean, declarative API. It’s ideal for chat logs, activity feeds, or any list where content size varies.

  • react-window:

    Choose react-window when you need maximum performance and fine-grained control over virtualization, and your list items have fixed or predictable heights. It’s a lower-level tool that requires more manual configuration but gives you direct access to the rendering logic. Use it in performance-critical applications like dashboards or data grids where every millisecond counts and you can afford to manage item sizing yourself.

README for react-infinite-scroll-component

react-infinite-scroll-component npm npm bundlephobia

All Contributors

Infinite scroll for React. Zero runtime dependencies, IntersectionObserver-based, TypeScript-first. ~4 kB gzipped.

Works with window scroll, fixed-height containers, and custom scrollable parents. Pull-to-refresh and inverse (chat) scroll included. React 17, 18, and 19 compatible.

Install

npm install react-infinite-scroll-component
# or
yarn add react-infinite-scroll-component
# or
pnpm add react-infinite-scroll-component

Two APIs

APIWhen to use
InfiniteScroll componentMost cases, handles loader, endMessage, pull-to-refresh, inverse scroll UI
useInfiniteScroll hookCustom UI, you own the markup, the hook manages the observer

InfiniteScroll component

Basic usage (TypeScript)

import { useState } from 'react';
import InfiniteScroll from 'react-infinite-scroll-component';

type Item = { id: number; name: string };

function Feed() {
  const [items, setItems] = useState<Item[]>(initialItems);
  const [hasMore, setHasMore] = useState(true);

  const fetchMore = async () => {
    const next = await api.getItems({ offset: items.length });
    if (next.length === 0) {
      setHasMore(false);
      return;
    }
    setItems((prev) => [...prev, ...next]);
  };

  return (
    <InfiniteScroll
      dataLength={items.length}
      next={fetchMore}
      hasMore={hasMore}
      loader={<p>Loading...</p>}
      endMessage={<p style={{ textAlign: 'center' }}>All items loaded.</p>}
    >
      {items.map((item) => (
        <div key={item.id}>{item.name}</div>
      ))}
    </InfiniteScroll>
  );
}

Scroll inside a fixed-height container

<div id="scrollableDiv" style={{ height: 400, overflow: 'auto' }}>
  <InfiniteScroll
    dataLength={items.length}
    next={fetchMore}
    hasMore={hasMore}
    loader={<p>Loading...</p>}
    scrollableTarget="scrollableDiv"
  >
    {items.map((item) => (
      <div key={item.id}>{item.name}</div>
    ))}
  </InfiniteScroll>
</div>

Pass a ref value directly instead of a string id:

const containerRef = useRef<HTMLDivElement>(null);

<div ref={containerRef} style={{ height: 400, overflow: 'auto' }}>
  <InfiniteScroll
    dataLength={items.length}
    next={fetchMore}
    hasMore={hasMore}
    loader={<p>Loading...</p>}
    scrollableTarget={containerRef.current}
  >
    {items.map((item) => (
      <div key={item.id}>{item.name}</div>
    ))}
  </InfiniteScroll>
</div>;

Inverse scroll (chat / messaging UIs)

<div
  id="chatBox"
  style={{
    height: 500,
    overflow: 'auto',
    display: 'flex',
    flexDirection: 'column-reverse',
  }}
>
  <InfiniteScroll
    dataLength={messages.length}
    next={loadOlderMessages}
    hasMore={hasMore}
    loader={<p>Loading older messages...</p>}
    inverse={true}
    scrollableTarget="chatBox"
    style={{ display: 'flex', flexDirection: 'column-reverse' }}
  >
    {messages.map((msg) => (
      <div key={msg.id}>{msg.text}</div>
    ))}
  </InfiniteScroll>
</div>

Pull-to-refresh

<InfiniteScroll
  dataLength={items.length}
  next={fetchMore}
  hasMore={hasMore}
  loader={<p>Loading...</p>}
  pullDownToRefresh
  pullDownToRefreshThreshold={50}
  refreshFunction={refreshList}
  pullDownToRefreshContent={
    <h3 style={{ textAlign: 'center' }}>&#8595; Pull down to refresh</h3>
  }
  releaseToRefreshContent={
    <h3 style={{ textAlign: 'center' }}>&#8593; Release to refresh</h3>
  }
>
  {items.map((item) => (
    <div key={item.id}>{item.name}</div>
  ))}
</InfiniteScroll>

useInfiniteScroll hook

For when you need full control over your markup. Place the sentinelRef div at the end of your list, the hook fires next() when it enters the viewport.

import { useState } from 'react';
import { useInfiniteScroll } from 'react-infinite-scroll-component';

type Item = { id: number; name: string };

function CustomFeed() {
  const [items, setItems] = useState<Item[]>(initialItems);
  const [hasMore, setHasMore] = useState(true);

  const { sentinelRef, isLoading } = useInfiniteScroll({
    next: async () => {
      const more = await api.getItems({ offset: items.length });
      if (more.length === 0) {
        setHasMore(false);
        return;
      }
      setItems((prev) => [...prev, ...more]);
    },
    hasMore,
    dataLength: items.length,
  });

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
      <li ref={sentinelRef} aria-hidden="true" />
      {isLoading && <li>Loading...</li>}
      {!hasMore && <li>All items loaded.</li>}
    </ul>
  );
}

Framework recipes

Next.js App Router

InfiniteScroll is a client component. Fetch initial data in a Server Component, pass it down.

// app/feed/page.tsx, Server Component
import { FeedClient } from './feed-client';
import { db } from '@/lib/db';

export default async function FeedPage() {
  const initialItems = await db.items.findMany({
    take: 20,
    orderBy: { id: 'desc' },
  });
  return <FeedClient initialItems={initialItems} />;
}
// app/feed/feed-client.tsx, Client Component
'use client';

import { useState } from 'react';
import InfiniteScroll from 'react-infinite-scroll-component';

type Item = { id: string; title: string };

export function FeedClient({ initialItems }: { initialItems: Item[] }) {
  const [items, setItems] = useState(initialItems);
  const [hasMore, setHasMore] = useState(true);

  const fetchMore = async () => {
    const res = await fetch(`/api/items?cursor=${items[items.length - 1].id}`);
    const next: Item[] = await res.json();
    if (next.length === 0) {
      setHasMore(false);
      return;
    }
    setItems((prev) => [...prev, ...next]);
  };

  return (
    <InfiniteScroll
      dataLength={items.length}
      next={fetchMore}
      hasMore={hasMore}
      loader={<p>Loading...</p>}
      endMessage={<p>You have seen everything.</p>}
    >
      {items.map((item) => (
        <article key={item.id}>{item.title}</article>
      ))}
    </InfiniteScroll>
  );
}

With TanStack Query

import { useInfiniteQuery } from '@tanstack/react-query';
import InfiniteScroll from 'react-infinite-scroll-component';

function PostFeed() {
  const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
    useInfiniteQuery({
      queryKey: ['posts'],
      queryFn: ({ pageParam = 0 }) => fetchPosts(pageParam),
      getNextPageParam: (lastPage, pages) =>
        lastPage.length === 20 ? pages.length : undefined,
    });

  const posts = data?.pages.flat() ?? [];

  return (
    <InfiniteScroll
      dataLength={posts.length}
      next={fetchNextPage}
      hasMore={!!hasNextPage}
      loader={isFetchingNextPage ? <p>Loading...</p> : null}
      endMessage={<p>All posts loaded.</p>}
    >
      {posts.map((post) => (
        <article key={post.id}>{post.title}</article>
      ))}
    </InfiniteScroll>
  );
}

With SWR

import useSWRInfinite from 'swr/infinite';
import InfiniteScroll from 'react-infinite-scroll-component';

const PAGE_SIZE = 20;

function PostList() {
  const { data, size, setSize } = useSWRInfinite(
    (index) => `/api/posts?page=${index}&limit=${PAGE_SIZE}`,
    fetcher
  );

  const posts = data ? data.flat() : [];
  const hasMore = data ? data[data.length - 1].length === PAGE_SIZE : true;

  return (
    <InfiniteScroll
      dataLength={posts.length}
      next={() => setSize(size + 1)}
      hasMore={hasMore}
      loader={<p>Loading...</p>}
    >
      {posts.map((post) => (
        <div key={post.id}>{post.title}</div>
      ))}
    </InfiniteScroll>
  );
}

Three scroll modes

ModeHow to useUse case
Window scrollOmit height and scrollableTargetSocial feeds, blogs, product listings
Fixed-height containerPass height propEmbedded lists, sidebars
Custom scrollable parentPass scrollableTarget (element or id)Existing overflow containers

Props, InfiniteScroll

PropTypeRequiredDefaultDescription
dataLengthnumberyes-Current count of rendered items. The component resets its load guard each time this value changes, which allows next() to fire again on the next scroll.
next() => voidyes-Called once when the sentinel enters the viewport. Append new items to your list state inside this callback; do not replace the existing items.
hasMorebooleanyes-When false, the observer is disconnected and next() will not be called again. Set it to false when your data source has no more pages.
loaderReactNodeyes-Rendered below the list while the next page is loading. Displayed between the last item and the bottom sentinel.
endMessageReactNodeno-Rendered below the list when hasMore is false. Use it for an "all caught up" or "no more items" message.
heightnumber | stringno-Creates a fixed-height scroll container wrapping the list. Accepts a pixel number or any CSS length string. Omit this prop to scroll the window instead.
scrollableTargetHTMLElement | string | nullno-The scrollable ancestor that already provides overflow scrollbars. Pass the element's id string or a direct HTMLElement reference. Required when the scroll container is neither the window nor the height wrapper.
scrollThresholdnumber | stringno0.8How close to the bottom the user must scroll before next() is called. A fraction like 0.8 means 80% scrolled; a string like "200px" means within 200 px of the bottom edge.
inversebooleannofalseReverse scroll direction for chat or messaging UIs. The sentinel moves to the top of the list. Use together with flexDirection: column-reverse on the scroll container.
pullDownToRefreshbooleannofalseEnable pull-to-refresh gesture on touch and mouse. Requires refreshFunction to also be set.
refreshFunction() => voidno-Called once when the user pulls down past pullDownToRefreshThreshold pixels and releases. Only active when pullDownToRefresh is true.
pullDownToRefreshThresholdnumberno100How many pixels the user must pull down before refreshFunction is triggered on release.
pullDownToRefreshContentReactNodeno-Content shown inside the pull-to-refresh area while the user is pulling but has not yet reached the threshold.
releaseToRefreshContentReactNodeno-Content shown inside the pull-to-refresh area once the threshold is passed and the user can release to refresh.
onScroll(e: UIEvent) => voidno-Callback fired on every scroll event on the container. Receives the native UIEvent. Useful for syncing UI state with scroll position.
classNamestringno''CSS class name applied to the inner scroll container div.
styleCSSPropertiesno-Inline style object applied to the inner scroll container div. Merged with the component's default layout styles.
roleAriaRoleno-Semantic role for the scroll container. Use "list" for item lists, "feed" for activity streams.
tabIndexnumberno-Makes the scroll container focusable. Pass 0 to include it in the natural tab sequence.
idstringno-DOM id for the container. Useful when other elements reference it via aria-labelledby.
aria-*AriaAttributesno-Any React aria-* prop (aria-label, aria-labelledby, aria-describedby, etc.) forwarded to the scroll container.
hasChildrenbooleanno-Set to true when children is a single element or a fragment rather than an array. Helps the component detect whether visible content exists to determine scroll state.
initialScrollYnumberno-Scrolls the window to this Y offset on mount. Useful for restoring a user's scroll position when navigating back to a page.

Accessibility

Pass role and a label so screen readers can announce the container and its item count correctly:

<InfiniteScroll
  role="list"
  aria-label="Search results"
  dataLength={items.length}
  next={fetchMore}
  hasMore={hasMore}
  loader={<p>Loading...</p>}
>
  {items.map((item) => (
    <div role="listitem" key={item.id}>
      {item.name}
    </div>
  ))}
</InfiniteScroll>

Or reference an existing heading via aria-labelledby:

<h2 id="results-heading">Search results</h2>
<InfiniteScroll
  role="list"
  aria-labelledby="results-heading"
  dataLength={items.length}
  next={fetchMore}
  hasMore={hasMore}
  loader={<p>Loading...</p>}
>

Props, useInfiniteScroll

PropTypeRequiredDefaultDescription
dataLengthnumberyes-Current count of rendered items. The hook resets its load guard whenever this value changes, allowing next() to fire again on the next intersection.
next() => voidyes-Called once when the sentinel enters the viewport. Append new items to your list state inside this callback; do not replace the existing items.
hasMorebooleanyes-When false, the IntersectionObserver is disconnected and next() will not be called again. Set it to false when your data source has no more pages.
scrollThresholdnumber | stringno0.8How close to the edge the sentinel must be before next() fires. A fraction like 0.8 means 80% scrolled; a string like "200px" means within 200 px of the edge.
scrollableTargetHTMLElement | string | nullno-The scrollable ancestor to use as the observer root. Pass a DOM id string or an HTMLElement reference. When omitted, the observer uses the browser viewport.
inversebooleannofalseWhen true, the rootMargin is applied to the top edge instead of the bottom. Place the sentinel at the top of your list and use flexDirection: column-reverse for chat UIs.

Returns { sentinelRef, isLoading }.


What's new in v7

  • IntersectionObserver-based triggering, next() fires once when the sentinel enters the viewport, not on every scroll tick. No missed triggers, better performance.
  • useInfiniteScroll hook, low-level hook for building fully custom UIs.
  • Zero runtime dependencies, throttle-debounce removed.
  • scrollableTarget accepts HTMLElement, pass a ref value directly, not just a string id.
  • Function component rewrite, same public API, no migration needed.
  • React 17, 18, 19 compatible.

live examples

  • infinite scroll (never ending), window scroll
    • Edit yk7637p62z
  • infinite scroll till 500 elements, window scroll
    • Edit 439v8rmqm0
  • infinite scroll in an element (height 400px)
    • Edit w3w89k7x8
  • infinite scroll with scrollableTarget
    • Edit r7rp40n0zm

Contributors ✨

Thanks goes to these wonderful people (emoji key):

Ankeet Maini
Ankeet Maini

💬 📖 💻 👀 🚧
Darsh Shah
Darsh Shah

🚇 💻 👀 🚧
Eliya Cohen
Eliya Cohen

💻
Nitin Kukreja
Nitin Kukreja

💻
Bruno Sabetta
Bruno Sabetta

💻 📖
Osmar Pérez Bautista
Osmar Pérez Bautista

💻
Shreya Dahal
Shreya Dahal

💻
Vlad Harahan
Vlad Harahan

💻 📖
Daniel Caldas
Daniel Caldas

💻
Alaeddine Douagi
Alaeddine Douagi

💻
Carlos
Carlos

💻
Championrunner
Championrunner

📖
Daniel Sogl
Daniel Sogl

💻
Darren Oster
Darren Oster

💻
Illia Panasenko
Illia Panasenko

💻
Kiko Beats
Kiko Beats

💻
Matt Trussler
Matt Trussler

💻
Nimit Suwannagate
Nimit Suwannagate

💻
Rajat
Rajat

💻
Rich
Rich

💻
Ritesh Goyal
Ritesh Goyal

💻
babycannotsay
babycannotsay

💻
cesco
cesco

💻
Harry
Harry

💻
ludwig404
ludwig404

💻
Karl Johansson
Karl Johansson

💻
Geoffrey Teng
Geoffrey Teng

💻
Sayed Risat
Sayed Risat

💻 📖

This project follows the all-contributors specification. Contributions of any kind are welcome!

LICENSE

MIT