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

@uselay/sdk

v1.4.3

Published

Drop-in user feedback widget with AI triage for React

Downloads

1,575

Readme

@uselay/sdk

v1.3 — New orb-in-pill toggle with first-visit discovery animation. See changelog.

The feedback layer for React. Drop one component into your app and let anyone point at what's wrong and say why — no more screenshots in Slack with "the thing on the right."

Comments are pinned to the exact DOM element, not a screenshot coordinate. Your team sees what was clicked, reads the comment, and understands the problem without a follow-up call.

Quick start

1. Install

npm install @uselay/sdk

No CSS imports needed. Styles are injected at runtime.

2. Wrap your app

import { LayProvider } from '@uselay/sdk';

function App() {
  return (
    <LayProvider projectId="your-project-id">
      <YourApp />
    </LayProvider>
  );
}

Get your projectId from the dashboard. The toggle button and comment UI render automatically inside the provider.

Want to identify who's commenting? Add the optional user prop:

<LayProvider
  projectId="your-project-id"
  user={{ id: 'user-1', name: 'Jane' }}
>
  <YourApp />
</LayProvider>

3. Press C

Run your app and press C to enter comment mode. Hover to highlight elements. Click to pin a comment.

How it works

  1. Enter comment mode — Press C or click the toggle button. Elements highlight on hover.
  2. Click an element — A comment input anchors to your selection. The SDK generates a CSS selector and fingerprint to track it.
  3. Write your comment — Type what you see, or tap a starter chip.
  4. AI enrichment runs — Element metadata is captured and enriched with context, intent detection, and suggested actions.
  5. Comment appears in the dashboard — Grouped by page and element, with the AI context card and a viewport screenshot.
  6. Your team understands what you mean — The comment is attached to the element itself. Navigate, resolve, and archive from the dashboard.

Configuration

All props are passed directly to LayProvider:

| Prop | Type | Default | Description | |------|------|---------|-------------| | projectId | string | required | Project ID from the Lay dashboard | | user | { id, name, avatar? } | — | Comment author identity. Omit for anonymous. | | userHash | string | — | HMAC-SHA256 hash for verified identity. Use identifyUser() to generate. | | mode | 'review' \| 'support' | dashboard setting | Override project mode | | active | boolean | dashboard setting | Override active state. false = widget hidden. | | ai | boolean | true | Enable AI enrichment on comments | | screenshots | boolean | true | Capture viewport screenshots on comment creation | | starterChips | StarterChip[] | ["Visual bug", "Copy issue", "Love this"] | Quick-feedback chips. [] to disable. | | version | string | — | Version tag. Changing this archives previous comments. | | adapter | LayAdapter | hosted | Custom data adapter. Defaults to hosted backend. | | toggleLabel | string | "Feedback" | Label shown in the discovery pill on first visit | | apiUrl | string | https://uselay.com | API base URL override |

Most projects need only projectId.

Modes

Review — Team feedback on prototypes and staging. Comments are threaded. AI generates element descriptions and fix suggestions. Best for staging URLs, design reviews, QA passes.

<LayProvider projectId="..." mode="review">
  <StagingApp />
</LayProvider>

Support — End-user feedback on live sites. Comments are standalone. AI detects intent (bug report, confusion, feature request, praise). Best for production apps, beta programs, user research.

<LayProvider projectId="..." mode="support">
  <ProductionApp />
</LayProvider>

Identified users

Use identifyUser to verify commenter identity server-side. It returns spread-friendly props — { user, userHash } for a valid user, or {} for null/undefined input.

import { identifyUser } from '@uselay/sdk/server';

// Set LAY_SECRET_KEY in your environment (from dashboard → project settings)
const identified = identifyUser(session?.user);
// → { user: { id, name, avatar }, userHash: '...' }  or  {}

Spread the result into the provider:

<LayProvider projectId="..." {...identifyUser(session?.user)}>
  <YourApp />
</LayProvider>

Never expose LAY_SECRET_KEY to the client. identifyUser must run on your server.

Stable anchors

Comments are pinned to DOM elements via CSS selectors. The SDK resolves elements in three layers:

  1. data-feedback-id — If present, always used. Most stable. Add to elements that receive frequent feedback.
  2. CSS selector — Generated from IDs, data-testid, semantic attributes. Skips auto-generated class names.
  3. Element fingerprint — Hash of tag, text, attributes, position. Used as fallback when the selector stops matching.
<button data-feedback-id="checkout-submit">Place Order</button>

If a comment can't be resolved to any element, it appears in the Detached Comments panel in the dashboard.

Custom adapter

Implement the LayAdapter interface to use your own backend:

import type { LayAdapter } from '@uselay/sdk';

const myAdapter: LayAdapter = {
  getConfig: async (projectId) => { /* ... */ },
  getComments: async (projectId, urlPath, options) => { /* ... */ },
  addComment: async (comment, options) => { /* ... */ },
  updateComment: async (id, update, options) => { /* ... */ },
  uploadScreenshot: async (projectId, commentId, blob, bounds, options) => { /* ... */ },
  subscribe: (projectId, callback, options) => { /* return unsubscribe fn */ },
};
<LayProvider projectId="..." adapter={myAdapter}>
  <YourApp />
</LayProvider>

The SDK also exports createMemoryAdapter() for testing and prototyping.

Keyboard shortcuts

| Key | Action | |-----|--------| | C | Toggle comment mode | | Escape | Exit comment mode / dismiss input |

Shortcuts are disabled when focus is inside an input, textarea, or contenteditable element.

Framework examples

Next.js App Router

// app/layout.tsx
import { identifyUser } from '@uselay/sdk/server';
import { LayProvider } from '@uselay/sdk';

export default async function RootLayout({ children }) {
  const session = await auth();

  return (
    <html lang="en">
      <body>
        <LayProvider
          projectId={process.env.NEXT_PUBLIC_LAY_PROJECT_ID!}
          {...identifyUser(session?.user)}
        >
          {children}
        </LayProvider>
      </body>
    </html>
  );
}

Next.js Pages Router

// pages/_app.tsx
import { identifyUser } from '@uselay/sdk/server';
import { LayProvider } from '@uselay/sdk';

export async function getServerSideProps({ req }) {
  const session = await getSession(req);
  return { props: { identified: identifyUser(session?.user) } };
}

export default function App({ Component, pageProps }) {
  return (
    <LayProvider
      projectId={process.env.NEXT_PUBLIC_LAY_PROJECT_ID!}
      {...pageProps.identified}
    >
      <Component {...pageProps} />
    </LayProvider>
  );
}

Vite + React

// src/main.tsx
import { LayProvider } from '@uselay/sdk';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')!).render(
  <LayProvider projectId={import.meta.env.VITE_LAY_PROJECT_ID}>
    <App />
  </LayProvider>
);

Remix

// app/root.tsx
import { LayProvider } from '@uselay/sdk';
import { identifyUser } from '@uselay/sdk/server';

export async function loader({ request }) {
  const user = await getUser(request);
  return json({
    layProjectId: process.env.LAY_PROJECT_ID,
    ...identifyUser(user),
  });
}

export default function Root() {
  const { layProjectId, user, userHash } = useLoaderData();
  return (
    <html lang="en">
      <body>
        <LayProvider projectId={layProjectId!} user={user} userHash={userHash}>
          <Outlet />
        </LayProvider>
      </body>
    </html>
  );
}

Troubleshooting

Widget does not appear — Check that LayProvider wraps your app with a valid projectId. The project must be active in the dashboard (or pass the active prop).

Comments disappear after deploy — CSS selectors changed. Add data-feedback-id to critical elements. Detached comments appear in the Detached Comments panel.

AI context cards not showing — Verify ai is not set to false. AI processing takes a few seconds after comment creation.

identifyUser throws "LAY_SECRET_KEY not set" — Set the LAY_SECRET_KEY environment variable on your server. Never expose it client-side.

Widget re-renders frequently — If you pass object props like user inline on every render, it creates a new reference each time. Define objects outside the component or use useMemo.

Next steps

  • Open the dashboard and create your first project
  • Invite your team from project settings
  • Add data-feedback-id to your most important UI elements

License

MIT