@uselay/sdk
v1.4.3
Published
Drop-in user feedback widget with AI triage for React
Downloads
1,575
Maintainers
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/sdkNo 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
- Enter comment mode — Press
Cor click the toggle button. Elements highlight on hover. - Click an element — A comment input anchors to your selection. The SDK generates a CSS selector and fingerprint to track it.
- Write your comment — Type what you see, or tap a starter chip.
- AI enrichment runs — Element metadata is captured and enriched with context, intent detection, and suggested actions.
- Comment appears in the dashboard — Grouped by page and element, with the AI context card and a viewport screenshot.
- 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:
data-feedback-id— If present, always used. Most stable. Add to elements that receive frequent feedback.- CSS selector — Generated from IDs,
data-testid, semantic attributes. Skips auto-generated class names. - 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-idto your most important UI elements
License
MIT
