feat(library): redesign the site around the block catalog (#50372)

## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Feature — the visual redesign itself.

Part 5 of 6 in a stack that splits the library redesign into reviewable
pieces. The four PRs beneath it carry the build, content and Markdown
work; what's left here is layout, navigation and styling.

## What is the current behavior?

The library is laid out like a documentation site: a sidebar tree of
framework folders, a homepage that lists links, and a guide page that
opens with prose. That shape suits reference material, but the library's
job is to help someone find a block and install it — and the sidebar is
the only way to discover one.

## What is the new behavior?

The homepage is the catalog itself — blocks grouped by what they do
(authentication, database, storage, realtime, messaging, AI,
foundations) rather than by framework, each with a preview of what it
renders, filterable by category.

Navigation moves into a site header whose Explore menu opens the same
categories, so the catalog is reachable from any page and the per-page
sidebar tree is gone.

A guide opens with what the reader came for: the block's name, the
install command, and a preview pane with tabs — the running component
and its files — before any prose. The file tree that used to sit
mid-page under "Folder structure" is one of those tabs. Every guide also
offers a copy of the agent prompt that points at its Markdown.

Getting-started pages get the same treatment: the quickstart is now a
framework-tabbed walkthrough rather than a wall of setup links.

## Additional context

`BlockOverviewTabs` renders Preview and Files here. #50369, stacked on
top of this one, adds the third "What's added" tab — it is the only part
of the redesign that depends on the new resource analyzer, which is why
it sits above this PR rather than below it.

Also removes what the redesign orphaned: the table-of-contents component
and its `remark` / `mdast-util-toc` dependencies, and the sidebar nav
and command-item configuration the new header replaced.

The block source changes are typography only — auth card titles move
from `text-2xl` to `font-medium text-lg tracking-normal` — which is what
regenerates the auth registry artifacts.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added a redesigned Supabase Library catalog with categorized blocks,
framework-aware navigation, previews, file views, and installation
actions.
* Added framework-specific quickstart guides for Next.js, React, Vue,
Nuxt, React Router, and TanStack Start.
* Added copy-to-clipboard prompts, “Open in v0” actions, starter
templates, and richer visual previews.

* **Improvements**
* Updated documentation layouts, FAQ content, typography, navigation,
accessibility, and responsive behavior.
* Improved mobile navigation, framework selection, and standardized
block installation guidance.
* Refined authentication and social-login block presentation with more
consistent heading styles.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Danny White <3104761+dnywh@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-09-29 10:45:23 +10:00
1 parent 80761d2521
commit 3f205627e0
161 files changed
+2654 -1947

No files matched your search

+1 -1
View File
@@ -51,7 +51,7 @@ jobs:
- run: pnpm --filter library build:registry
- name: Check generated registry
run: |
registry_changes="$(git status --porcelain --untracked-files=all -- apps/ui-library/public/r)"
registry_changes="$(git status --porcelain --untracked-files=all -- apps/ui-library/public/r apps/ui-library/__registry__)"
if [ -n "$registry_changes" ]; then
printf '%s\n' "$registry_changes"
echo 'Run pnpm --filter library build:registry and commit the generated registry files.'
+28 -23
View File
@@ -1,44 +1,49 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
# Supabase Library
## Getting Started
The library is the documentation and shadcn registry app for Supabase blocks and starter apps. Its workspace package name is `library`.
First, run the development server:
## Development and checks
Run these commands from the repository root with the repository's Node and pnpm versions:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
pnpm --filter library dev
pnpm --filter library test
pnpm --filter library typecheck
pnpm --filter library lint
pnpm --filter library build
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
The development server runs at [localhost:3004/library](http://localhost:3004/library). `build` and `dev` build the registry first, then generate the HTML content, Markdown guides, and `llms.txt`. Markdown generation reads the completed registry, so these steps must retain that order. Type checking generates Next.js types before running TypeScript and works without a previous development build.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
Registry JSON in `public/r` and the preview index in `__registry__` are generated and committed. Regenerate them with `pnpm --filter library build:registry`; do not edit them by hand. `.velite`, `public/markdown`, and `public/llms.txt` are generated build artifacts. Library CI runs the test suite, checks for registry drift, and builds the app.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Authoring guides
## Learn More
Write documentation in `content/docs/<framework>/<slug>.mdx`, or `content/docs/starters/<slug>.mdx` for starter apps. Keep frontmatter for page metadata. Installation headings, prerequisites, commands, and follow-up instructions belong in the MDX body, in the order readers should follow them.
To learn more about Next.js, take a look at the following resources:
Use `BlockItem` for a registry installation command. Its name is the published registry item ID. React is the default; Vue and Nuxt pages must select the Vue CLI explicitly:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
```mdx
## Installation
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
<BlockItem name="password-based-auth-nextjs" />
## Deploy on Vercel
<BlockItem name="infinite-query-composable" framework="vue" />
```
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
The shared command helper supplies both the page and its Markdown export. Vue commands use an absolute registry URL so a fresh project does not need an `@supabase` alias. Put other commands in ordinary fenced shell blocks. Keep notes next to the command they explain. Use `showOpenInV0` on `BlockItem` when a block supports that action. For Vue and Nuxt blocks that need an existing Supabase client, link the client guide and include a conditional client-install command; let readers reuse an existing client.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
The primary Copy prompt action points agents to the canonical `/library/docs/<framework>/<slug>.md` guide and asks them to prefer the guide's shadcn install command over copying files from registry JSON. It does not repeat installation commands. Starter prompts describe creating a new application; block prompts describe integrating into the existing project. The page also exposes a Markdown link.
The Markdown exporter reads the same MDX body, expands registry commands and file trees, and links each block's registry JSON as its source. Instruction-bearing MDX components need an explicit Markdown handler and a fixture in `lib/library-mdx-to-markdown.test.ts`. Missing registry files, document references, or unsupported components fail generation.
Register discoverable blocks and starter apps in `config/library.ts` and the existing navigation definitions in `config/docs.ts`. The catalog test compares these routes with the actual content directory. Keep intentional omissions explicit in that test.
## Supabase types
To regenerate the Supabase database types, run
From this directory, regenerate local database types with:
```
```bash
supabase gen types --local > registry/default/fixtures/database.types.ts
```
@@ -1,20 +1,21 @@
import '@/styles/code-block-variables.css'
import '@/styles/mdx.css'
import '@/styles/library-doc.css'
import { ChevronRight } from 'lucide-react'
import { ArrowRight, Blocks } from 'lucide-react'
import type { Metadata } from 'next'
import Link from 'next/link'
import { notFound } from 'next/navigation'
import Balancer from 'react-wrap-balancer'
import { ScrollArea } from 'ui'
import { allDocs } from '@/.velite'
import { metadata as mainMetadata } from '@/app/layout'
import { CopyDocPrompt } from '@/components/copy-doc-prompt'
import { FrameworkSelector } from '@/components/framework-selector'
import { Mdx } from '@/components/mdx-components'
import { OpenInV0Button } from '@/components/open-in-v0-button'
import { SourcePanel } from '@/components/source-panel'
import { DashboardTableOfContents } from '@/components/toc'
import { getTableOfContents } from '@/lib/toc'
import { libraryBlocks } from '@/config/library'
import { absoluteUrl, cn } from '@/lib/utils'
interface DocPageProps {
@@ -78,46 +79,80 @@ export default async function DocPage(props: DocPageProps) {
notFound()
}
const toc = await getTableOfContents(doc.raw)
const isGuide = doc.slugAsParams.startsWith('getting-started/')
const isStarter = doc.slugAsParams.startsWith('starters/')
const libraryBlock = libraryBlocks.find((block) => block.href === `/docs/${doc.slugAsParams}`)
const markdownPath = `${process.env.NEXT_PUBLIC_BASE_PATH ?? '/library'}/docs/${doc.slugAsParams}.md`
return (
<main className="relative lg:gap-10 xl:grid xl:grid-cols-[1fr_200px] px-8 md:px-16 py-20">
<div className="mx-auto w-full min-w-0 max-w-4xl">
<div className="mb-4 flex items-center space-x-1 text-sm text-foreground-muted">
<div className="overflow-hidden text-ellipsis whitespace-nowrap">Docs</div>
<ChevronRight className="h-4 w-4 text-foreground-muted" />
<div className="text-foreground-lighter">{doc.title}</div>
</div>
<div className="flex flex-col lg:flex-row lg:items-end justify-between mb-5">
<div className="space-y-2">
<h1 className={cn('scroll-m-20 text-2xl lg:text-4xl tracking-tight')}>{doc.title}</h1>
{doc.description && (
<p className="text-base lg:text-lg text-foreground-light">
<Balancer>{doc.description}</Balancer>
</p>
<main className="isolate px-4 pb-10 pt-4 md:px-8 md:pb-16 md:pt-6">
<header
className={cn(
'relative z-20 flex flex-col gap-5 py-8 md:py-12',
isGuide ? 'mx-auto max-w-2xl items-start text-left' : 'items-center text-center'
)}
>
{!isGuide && (
<div className="flex flex-wrap items-center justify-center gap-2">
<span className="inline-flex h-7 items-center gap-1.5 rounded-md border bg-surface-75 px-2 text-xs text-foreground-light">
<Blocks size={12} />
{isStarter ? 'Starter' : 'Block'}
</span>
{libraryBlock?.frameworkLabel ? (
<span className="inline-flex h-7 items-center rounded-md border bg-surface-75 px-2 text-xs text-foreground-light">
{libraryBlock.frameworkLabel}
</span>
) : (
<FrameworkSelector />
)}
</div>
<div className="flex items-center gap-2">
<FrameworkSelector />
)}
<h1 className="-mb-2 max-w-2xl scroll-m-24 text-balance font-heading text-4xl font-medium leading-tight tracking-tight sm:text-5xl sm:leading-none">
{doc.title}
</h1>
{doc.description && (
<p className="max-w-xl text-balance text-lg font-medium text-foreground-muted">
<Balancer>{doc.description}</Balancer>
</p>
)}
{!isGuide && (
<div className="flex flex-wrap items-start justify-center gap-2">
<CopyDocPrompt
title={doc.title}
markdownPath={markdownPath}
intent={isStarter ? 'create-app' : 'add-to-project'}
/>
{doc.v0Name && <OpenInV0Button name={doc.v0Name} />}
</div>
</div>
)}
</header>
<div className="mx-auto max-w-2xl">
<SourcePanel doc={doc} />
<div className="pb-12">
<Mdx code={doc.code} />
</div>
</div>
{doc.toc && (
<div className="hidden text-sm xl:block">
<div className="sticky top-16 -mt-10 pt-4">
<ScrollArea className="pb-10">
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] py-12">
<DashboardTableOfContents toc={toc} />
</div>
</ScrollArea>
</div>
{doc.preview && (
<div className="library-doc-preview mx-auto mb-16 max-w-5xl">
<Mdx code={doc.preview} />
</div>
)}
<article className="library-doc-content relative z-0 min-w-0 pb-12">
<Mdx code={doc.code} />
</article>
<div className="mx-auto max-w-2xl">
<Link
href="/"
className="group flex w-full items-center justify-between border-t py-8 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
>
<span className="text-left">
<span className="block text-xs text-foreground-lighter">Explore next</span>
<span className="mt-2 block text-sm">Browse all library blocks</span>
</span>
<ArrowRight
size={16}
className="text-foreground-lighter transition-transform group-hover:translate-x-1"
/>
</Link>
</div>
</main>
)
}
+6 -15
View File
@@ -1,6 +1,6 @@
import { TelemetryWrapper } from './telemetry-wrapper'
import { Sidebar } from '@/components/sidebar'
import { SiteFooter } from '@/components/site-footer'
import { SiteHeader } from '@/components/site-header'
interface AppLayoutProps {
children: React.ReactNode
@@ -9,20 +9,11 @@ interface AppLayoutProps {
export default function AppLayout({ children }: AppLayoutProps) {
return (
<>
{/* main container */}
<div className="pt-10 md:pt-0">
{/* main content */}
<main className="flex-1 max-w-site mx-auto w-full p-0">
{/* {children} */}
<div className="border-b">
<div className="flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] lg:grid-cols-[240px_minmax(0,1fr)]">
<Sidebar />
<div vaul-drawer-wrapper="">
<div className="relative flex min-h-screen flex-col bg-background">{children}</div>
</div>
</div>
</div>
</main>
<SiteHeader />
<div className="border-b">
<div id="main-content" tabIndex={-1} className="mx-auto min-h-screen w-full max-w-site">
{children}
</div>
</div>
<SiteFooter />
<TelemetryWrapper />
+5 -205
View File
@@ -1,211 +1,11 @@
import Link from 'next/link'
import { Button_Shadcn_ } from 'ui'
import { Suspense } from 'react'
import { BlockPreview } from '@/components/block-preview'
import { ComponentPreview } from '@/components/component-preview'
// Horizontal grid line component
const HorizontalGridLine = () => <div className="col-span-12 h-px bg-border/30" />
export const dynamic = 'force-dynamic'
export const revalidate = 0
import { LibraryOverview } from '@/components/library-overview'
export default function Home() {
const roomName = `room-${Math.floor(Math.random() * 1000)}`
return (
<main className="relative lg:-ml-10">
<div className="mx-auto w-full min-w-0 flex flex-col gap-16">
{/* Component Showcase with Grid */}
<div className="relative z-10 h-full w-full overflow-y-auto">
{/* Grid Container */}
<div className="relative">
{/* Grid Lines - Vertical (Columns) */}
{Array.from({ length: 13 }).map((_, i) => (
<div
key={`col-line-${i}`}
className="absolute top-0 bottom-0 w-px bg-border/30 z-10 first:hidden last:hidden"
style={{
left: `${(i / 12) * 100}%`,
height: '100%',
}}
/>
))}
{/* Grid Content */}
<div className="grid grid-cols-12 gap-0 relative z-20 pb-32">
{/* Heading Section */}
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-8 pb-8">
<div className="flex flex-col gap-8 justify-start pt-16 md:pt-32">
<div className="max-w-2xl">
<h1 className="text-4xl text-foreground mb-3 font-medium tracking-tight">
Blocks for Supabase Projects
</h1>
<h2 className="text-lg text-foreground-light mb-4">
A collection of components, blocks, and developer tools that connect your
project to Supabase via a single command.
</h2>
<div className="flex gap-3 mt-4">
<Button_Shadcn_ variant="secondary" size="lg">
<Link href="/docs/getting-started/quickstart">Get Started</Link>
</Button_Shadcn_>
<Button_Shadcn_ variant="outline" size="lg">
<a href="/docs/guides/ai-tools/ai-skills">Install Skills</a>
</Button_Shadcn_>
</div>
</div>
</div>
</div>
{/* Password-based Authentication */}
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
<span>Password-based Authentication</span>
<Link
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
href="/docs/nextjs/password-based-auth"
>
Go to block ➔
</Link>
</div>
<HorizontalGridLine />
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
<div className="-mt-4">
<BlockPreview name="password-based-auth/auth/sign-up" />
</div>
</div>
<HorizontalGridLine />
{/* Social Authentication */}
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
<span>Social Authentication</span>
<Link
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
href="/docs/nextjs/social-auth"
>
Go to block ➔
</Link>
</div>
<HorizontalGridLine />
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
<div className="-mt-4">
<BlockPreview name="social-auth/auth/login" />
</div>
</div>
<HorizontalGridLine />
{/* Realtime Cursors */}
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
<span>Realtime Cursors</span>
<Link
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
href="/docs/nextjs/realtime-cursor"
>
Go to block ➔
</Link>
</div>
<HorizontalGridLine />
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
<div className="-mt-4 grid md:flex rounded-lg overflow-hidden">
<BlockPreview name="realtime-cursor-demo" isPair />
<BlockPreview name="realtime-cursor-demo" isPair />
</div>
</div>
<HorizontalGridLine />
{/* Dropzone */}
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
<span>File Upload</span>
<Link
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
href="/docs/nextjs/dropzone"
>
Go to block ➔
</Link>
</div>
<HorizontalGridLine />
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
<div className="-mt-4 -mb-12">
<ComponentPreview name="dropzone-demo" showCode={false} />
</div>
</div>
<HorizontalGridLine />
{/* Current User Avatar */}
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
<span>Current User Avatar</span>
<Link
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
href="/docs/nextjs/current-user-avatar"
>
Go to block ➔
</Link>
</div>
<HorizontalGridLine />
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
<div className="-mt-4 -mb-12">
<ComponentPreview name="current-user-avatar-preview" showCode={false} />
</div>
</div>
<HorizontalGridLine />
{/* Realtime Avatar Stack */}
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
<span>Realtime Avatar Stack</span>
<Link
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
href="/docs/nextjs/realtime-avatar-stack"
>
Go to block ➔
</Link>
</div>
<HorizontalGridLine />
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
<div className="-mt-4 -mb-12">
<ComponentPreview name="realtime-avatar-stack-preview" showCode={false} />
</div>
</div>
<HorizontalGridLine />
{/* Realtime Chat */}
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
<span>Realtime Chat</span>
<Link
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
href="/docs/nextjs/realtime-chat"
>
Go to block ➔
</Link>
</div>
<HorizontalGridLine />
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
<div className="-mt-4 grid md:flex rounded-lg overflow-hidden">
<BlockPreview name={`realtime-chat-demo?roomName=${roomName}`} isPair />
<BlockPreview name={`realtime-chat-demo?roomName=${roomName}`} isPair />
</div>
</div>
<HorizontalGridLine />
{/* Infinite Query Hook */}
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 pt-16 pb-6 text-xs uppercase font-mono text-foreground-light tracking-wider relative flex justify-between items-center">
<span>Infinite Query Hook</span>
<Link
className="text-foreground underline decoration-1 decoration-foreground-muted underline-offset-4 transition-colors hover:decoration-primary hover:decoration-2"
href="/docs/nextjs/social-auth"
>
Go to block ➔
</Link>
</div>
<HorizontalGridLine />
<div className="col-start-2 col-span-10 md:col-start-3 md:col-span-8 relative">
<div className="-mt-4">
<BlockPreview name="infinite-list-demo" />
</div>
</div>
<HorizontalGridLine />
</div>
</div>
</div>
</div>
</main>
<Suspense>
<LibraryOverview />
</Suspense>
)
}
@@ -4,6 +4,8 @@ import { useEffect } from 'react'
export function BaseInjector() {
useEffect(() => {
document.documentElement.classList.add('preview')
// Remove any existing base tag
const existingBase = document.querySelector('base')
if (existingBase) existingBase.remove()
@@ -13,14 +15,9 @@ export function BaseInjector() {
base.href = `${process.env.NEXT_PUBLIC_BASE_PATH ?? ''}/example/password-based-auth/`
document.head.prepend(base)
// Add preview class to body to use shadcn theme vars
const bodyElement = document.querySelector('body')
if (bodyElement) {
bodyElement.classList.add('preview')
}
return () => {
base.remove()
document.documentElement.classList.remove('preview')
}
}, [])
@@ -11,7 +11,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
return (
<>
<BaseInjector />
<div>
<div className="preview min-h-svh">
<div>{children}</div>
</div>
</>
@@ -10,7 +10,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
return (
<>
<BaseInjector />
<div className="relative flex h-screen w-full items-center justify-center bg-studio p-6 preview md:p-10">
<div className="relative flex h-screen w-full items-center justify-center p-6 preview md:p-10">
<div className="pointer-events-none absolute z-0 h-full w-full bg-[radial-gradient(oklch(from_var(--foreground-default)_l_c_h_/_0.02)_1px,transparent_1px)] bg-size-[16px_16px] mask-[radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]" />
<div className="z-10 w-full max-w-lg">{children}</div>
</div>
@@ -13,7 +13,9 @@ export default function Page() {
<div className="flex flex-col gap-6">
<Card>
<CardHeader>
<CardTitle className="text-2xl">Thank you for signing up!</CardTitle>
<CardTitle className="font-medium text-lg tracking-normal">
Thank you for signing up!
</CardTitle>
<CardDescription>Check your email to confirm</CardDescription>
</CardHeader>
<CardContent>
@@ -1,7 +1,6 @@
import { Metadata } from 'next'
import { BaseInjector } from './../base-injector'
import { Providers } from '@/app/Providers'
export const metadata: Metadata = {
title: 'Password Based Auth Example',
@@ -9,34 +8,12 @@ export const metadata: Metadata = {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html style={{ height: '100%', overflow: 'hidden' }}>
<head>
<style
dangerouslySetInnerHTML={{
__html: `
html, body, #root, main {
height: 100% !important;
min-height: 100% !important;
margin: 0 !important;
padding: 0 !important;
overflow: hidden !important;
}
`,
}}
/>
</head>
<body style={{ height: '100%', margin: 0, padding: 0, overflow: 'hidden' }}>
<BaseInjector />
<Providers>
<div
className="flex w-full h-full items-center justify-center p-6 md:p-10 preview bg-surface-100"
style={{ minHeight: '100%' }}
>
<div className="z-0 pointer-events-none absolute h-full w-full bg-[radial-gradient(oklch(from_var(--foreground-default)_l_c_h_/_0.05)_1px,transparent_1px)] bg-size-[16px_16px] mask-[radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]"></div>
<div className="w-full max-w-sm">{children}</div>
</div>
</Providers>
</body>
</html>
<>
<BaseInjector />
<div className="preview relative flex min-h-svh w-full items-center justify-center overflow-hidden p-6 md:p-10">
<div className="pointer-events-none absolute inset-0 z-0 bg-[radial-gradient(oklch(from_var(--foreground-default)_l_c_h_/_0.05)_1px,transparent_1px)] bg-size-[16px_16px] mask-[radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]" />
<div className="relative w-full max-w-sm">{children}</div>
</div>
</>
)
}
@@ -10,7 +10,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
return (
<>
<BaseInjector />
<div className="flex w-full items-center justify-center p-6 md:p-10 preview bg-surface-100 h-screen">
<div className="flex h-screen w-full items-center justify-center p-6 preview md:p-10">
<div className="w-full max-w-sm flex flex-col gap-4 justify-center items-center">
{children}
</div>
@@ -10,7 +10,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
return (
<>
<BaseInjector />
<div className="flex w-full items-center justify-center p-6 md:p-10 preview bg-surface-100 h-screen">
<div className="flex h-screen w-full items-center justify-center p-6 preview md:p-10">
<div className="w-full max-w-sm flex flex-col gap-4 justify-center items-center">
{children}
</div>
@@ -10,7 +10,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
return (
<>
<BaseInjector />
<div className="flex w-full items-center justify-center p-6 md:p-10 preview bg-surface-100 h-screen">
<div className="flex h-screen w-full items-center justify-center p-6 preview md:p-10">
<div className="w-full max-w-sm flex flex-col gap-4 justify-center items-center">
{children}
</div>
@@ -15,7 +15,7 @@ const SocialAuthDemo = () => {
<div className="flex flex-col gap-6">
<Card>
<CardHeader>
<CardTitle className="text-2xl">Welcome!</CardTitle>
<CardTitle className="font-medium text-lg tracking-normal">Welcome!</CardTitle>
<CardDescription>Sign in to your account to continue</CardDescription>
</CardHeader>
<CardContent>
@@ -1,7 +1,6 @@
import { Metadata } from 'next'
import { BaseInjector } from './../base-injector'
import { Providers } from '@/app/Providers'
export const metadata: Metadata = {
title: 'Social Auth Example',
@@ -9,34 +8,12 @@ export const metadata: Metadata = {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html style={{ height: '100%', overflow: 'hidden' }}>
<head>
<style
dangerouslySetInnerHTML={{
__html: `
html, body, #root, main {
height: 100% !important;
min-height: 100% !important;
margin: 0 !important;
padding: 0 !important;
overflow: hidden !important;
}
`,
}}
/>
</head>
<body style={{ height: '100%', margin: 0, padding: 0, overflow: 'hidden' }}>
<BaseInjector />
<Providers>
<div
className="flex w-full h-full items-center justify-center p-6 md:p-10 preview bg-surface-100"
style={{ minHeight: '100%' }}
>
<div className="z-0 pointer-events-none absolute h-full w-full bg-[radial-gradient(oklch(from_var(--foreground-default)_l_c_h_/_0.05)_1px,transparent_1px)] bg-size-[16px_16px] mask-[radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]"></div>
<div className="w-full max-w-sm">{children}</div>
</div>
</Providers>
</body>
</html>
<>
<BaseInjector />
<div className="preview relative flex min-h-svh w-full items-center justify-center overflow-hidden p-6 md:p-10">
<div className="pointer-events-none absolute inset-0 z-0 bg-[radial-gradient(oklch(from_var(--foreground-default)_l_c_h_/_0.05)_1px,transparent_1px)] bg-size-[16px_16px] mask-[radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)]" />
<div className="relative w-full max-w-sm">{children}</div>
</div>
</>
)
}
+13 -5
View File
@@ -4,20 +4,26 @@ import '@/styles/globals.css'
import { FeatureFlagProvider, TelemetryTagManager } from 'common'
import { genFaviconData } from 'common/MetaFavicons/app-router'
import { Inter } from 'next/font/google'
import { Inter, Manrope, Source_Code_Pro } from 'next/font/google'
import { Providers } from './Providers'
import { Toaster } from './toaster'
import { API_URL } from '@/lib/constants'
const inter = Inter({ subsets: ['latin'] })
const inter = Inter({ subsets: ['latin'], variable: '--font-inter', display: 'swap' })
const manrope = Manrope({ subsets: ['latin'], variable: '--font-manrope', display: 'swap' })
const sourceCodePro = Source_Code_Pro({
subsets: ['latin'],
variable: '--font-source-code-pro',
display: 'swap',
})
const BASE_PATH = process.env.NEXT_PUBLIC_BASE_PATH ?? ''
export const metadata: Metadata = {
applicationName: 'Supabase Library',
title: 'Supabase Library',
description: 'Provides a library of components for your project',
description: 'Supabase blocks and starter apps for authentication, storage, realtime, and more',
metadataBase: new URL('https://supabase.com'),
icons: genFaviconData(BASE_PATH),
openGraph: {
@@ -42,9 +48,11 @@ interface RootLayoutProps {
export default async function Layout({ children }: RootLayoutProps) {
return (
<html lang="en" suppressHydrationWarning>
<html lang="en" data-scroll-behavior="smooth" suppressHydrationWarning>
<head />
<body className={`${inter.className} antialiased`}>
<body
className={`${inter.variable} ${manrope.variable} ${sourceCodePro.variable} font-sans antialiased`}
>
<TelemetryTagManager />
<FeatureFlagProvider API_URL={API_URL}>
<Providers>
+29 -9
View File
@@ -1,14 +1,26 @@
'use client'
import { File } from 'lucide-react'
import dynamic from 'next/dynamic'
import { useState } from 'react'
import { flattenTree, TreeView, TreeViewItem } from 'ui'
import { CodeBlock, type CodeBlockLang } from 'ui-patterns/CodeBlock'
import { cn, flattenTree, TreeView, TreeViewItem } from 'ui'
import type { CodeBlockLang } from 'ui-patterns/CodeBlock'
import { RegistryNode } from '@/lib/process-registry'
const CodeBlock = dynamic(
() => import('ui-patterns/CodeBlock').then((module) => module.CodeBlock),
{
ssr: false,
loading: () => (
<div className="p-4 text-xs text-foreground-lighter">Loading code preview...</div>
),
}
)
interface BlockItemCodeProps {
files: RegistryNode[]
embedded?: boolean
}
interface TreeNode {
@@ -63,13 +75,13 @@ const findFirstFile = (nodes: RegistryNode[]): RegistryNode | null => {
return null
}
export function BlockItemCode({ files }: BlockItemCodeProps) {
export function BlockItemCode({ files, embedded = false }: BlockItemCodeProps) {
// Find the first file to select by default
const [selectedFile, setSelectedFile] = useState<RegistryNode | null>(findFirstFile(files))
const flattenedData = flattenTree({ name: '', children: flattenChildren(files) })
// Handle file selection from the TreeView
const handleNodeSelect = (element: any) => {
const handleNodeSelect = (filePath: string) => {
const findFileByPath = (nodes: RegistryNode[], path: string): RegistryNode | null => {
for (const node of nodes) {
if (node.path === path) {
@@ -85,7 +97,6 @@ export function BlockItemCode({ files }: BlockItemCodeProps) {
return null
}
const filePath = element.metadata.path
const foundFile = findFileByPath(files, filePath)
if (foundFile?.type === 'directory') return
@@ -94,9 +105,14 @@ export function BlockItemCode({ files }: BlockItemCodeProps) {
}
return (
<div className="flex mt-4 border rounded-lg overflow-hidden h-[652px] not-prose">
<div
className={cn(
'flex flex-col sm:flex-row overflow-hidden not-prose',
embedded ? 'h-full' : 'mt-4 border rounded-lg h-[652px]'
)}
>
{/* File browser sidebar */}
<div className="w-64 py-2 border-r bg-muted/30 overflow-y-auto">
<div className="h-44 w-full shrink-0 overflow-auto border-b bg-muted/30 py-2 sm:h-full sm:w-64 sm:border-b-0 sm:border-r">
<TreeView
data={flattenedData}
aria-label="file browser"
@@ -105,7 +121,11 @@ export function BlockItemCode({ files }: BlockItemCodeProps) {
defaultSelectedIds={flattenedData
.filter((n) => n.metadata?.path === selectedFile?.path)
.map((n) => n.id)}
onNodeSelect={({ element }) => handleNodeSelect(element)}
onNodeSelect={({ element }) => {
if (typeof element.metadata?.path === 'string') {
handleNodeSelect(element.metadata.path)
}
}}
nodeRenderer={({ element, isBranch, isExpanded, getNodeProps, level, isSelected }) => (
<TreeViewItem
{...getNodeProps()}
@@ -124,7 +144,7 @@ export function BlockItemCode({ files }: BlockItemCodeProps) {
{/* Code display area */}
{selectedFile?.content ? (
<CodeBlock
wrapperClassName="w-full"
wrapperClassName="w-full min-h-0 min-w-0 flex-1"
className="h-full max-w-none w-full! flex-1 font-mono text-xs rounded-none border-none"
language={languageFor(selectedFile.name)}
>
@@ -0,0 +1,54 @@
'use client'
import type { ReactNode } from 'react'
import { Tabs, TabsContent, TabsList, TabsTrigger } from 'ui'
export function BlockOverviewTabs({
children,
files,
}: {
children?: ReactNode
files?: ReactNode
}) {
return (
<div className="library-block-overview relative z-0 mt-4">
<Tabs
defaultValue={children ? 'preview' : 'files'}
className="overflow-hidden rounded-xl border bg-muted/50"
>
<TabsList aria-label="Block overview" className="gap-0 px-3">
{children && (
<TabsTrigger
value="preview"
className="px-3 py-3 text-xs data-[state=active]:border-brand"
>
Preview
</TabsTrigger>
)}
{files && (
<TabsTrigger
value="files"
className="px-3 py-3 text-xs data-[state=active]:border-brand"
>
Files
</TabsTrigger>
)}
</TabsList>
{children && (
<TabsContent
value="preview"
forceMount
className="library-overview-preview mt-0 data-[state=inactive]:hidden"
>
{children}
</TabsContent>
)}
{files && (
<TabsContent value="files" className="mt-0 h-[520px] md:h-[600px]">
{files}
</TabsContent>
)}
</Tabs>
</div>
)
}
@@ -0,0 +1,59 @@
import path from 'node:path'
import type { ReactNode } from 'react'
import { BlockItemCode } from './block-item-code'
import { BlockOverviewTabs } from './block-overview-tabs'
import { generateRegistryTree } from '@/lib/process-registry'
import { resolveRegistryItem } from '@/lib/registry-resolution'
import { registry } from '@/registry'
export function BlockOverview({
name,
children,
showFiles = false,
}: {
name: string
children?: ReactNode
showFiles?: boolean
}) {
const resolved = registry.items.some((item) => item.name === name)
? resolveRegistryItem((itemName) => registry.items.find((item) => item.name === itemName), name)
: undefined
return (
<BlockOverviewTabs
files={
showFiles ? (
<div className="flex h-full flex-col">
{resolved && (
// px-6 lines this up with the tab labels, which the list and trigger each inset by 12px.
<p className="border-b px-6 py-3 text-xs text-foreground-light">
Supabase files
{resolved.firstPartyDependencies.length > 0
? ', including registry dependencies'
: ''}
.
{resolved.externalRegistryDependencies.length > 0 && (
<>
{' '}
External UI dependencies: {resolved.externalRegistryDependencies.join(', ')}.
</>
)}
</p>
)}
<div className="min-h-0 flex-1">
<BlockItemCode
files={generateRegistryTree(
path.join(process.cwd(), 'public', 'r', `${name}.json`)
)}
embedded
/>
</div>
</div>
) : undefined
}
>
{children}
</BlockOverviewTabs>
)
}
+3 -2
View File
@@ -19,6 +19,7 @@ export function BlockPreview({ name, wide = false, isPair = false }: ComponentPr
}
>
<iframe
title={`${name.split('/')[0].replaceAll('-', ' ')} preview`}
src={`${process.env.NEXT_PUBLIC_BASE_PATH ?? ''}/example/${name}`}
style={{
border: 'none',
@@ -36,10 +37,10 @@ export function BlockPreview({ name, wide = false, isPair = false }: ComponentPr
const wideClasses = wide ? '2xl:-ml-12 2xl:-mr-12' : ''
return (
<div className={cn('mt-4 w-full', wideClasses)}>
<div className={cn('library-block-preview relative isolate mt-4 w-full', wideClasses)}>
<div
className={cn(
'relative border rounded-lg overflow-hidden bg-muted min-h-[150px] h-[600px]',
'relative border overflow-hidden min-h-[150px] h-[600px]',
isPair && 'rounded-none'
)}
>
@@ -0,0 +1,277 @@
import {
Bot,
Check,
File,
Github,
LockKeyhole,
MoreHorizontal,
MousePointer2,
Search,
Send,
Upload,
UserRound,
Workflow,
} from 'lucide-react'
import type { ReactNode } from 'react'
import type { CatalogPreviewKind } from '@/config/library'
const previewTitles: Partial<Record<CatalogPreviewKind, string>> = {
table: 'customers',
storage: 'product-assets',
chat: 'team chat',
editor: 'index.ts',
mcp: 'mcp-server',
agents: 'connected agents',
dashboard: 'acme workspace',
client: 'supabase.ts',
flow: 'shared canvas',
}
function PreviewWindow({ title, children }: { title: string; children: ReactNode }) {
return (
<div className="w-full max-w-xs overflow-hidden rounded-lg border bg-background shadow-lg">
<div className="flex h-8 items-center justify-between border-b px-3 text-foreground-light">
<div className="flex items-center gap-1.5">
<span className="size-1.5 rounded-full bg-brand" />
<span className="text-[10px]">{title}</span>
</div>
<MoreHorizontal className="size-3.5" />
</div>
<div className="flex min-h-28 flex-col justify-center gap-2.5 p-3">{children}</div>
</div>
)
}
export function CatalogPreview({ kind }: { kind: CatalogPreviewKind }) {
return (
<div
aria-hidden="true"
className="flex h-full w-full select-none flex-col p-5 text-[10px] text-foreground [mask-image:linear-gradient(to_bottom,black_calc(100%-20px),transparent)] md:p-6 md:[mask-image:linear-gradient(to_bottom,black_calc(100%-24px),transparent)]"
>
<div className="my-auto flex w-full shrink-0 items-center justify-center">
{kind === 'auth' || kind === 'social' || kind === 'consent' ? (
<div className="w-full max-w-[200px] rounded-lg border bg-background p-4 shadow-lg">
<div className="mb-2 flex size-6 items-center justify-center rounded-md border bg-surface-100">
<LockKeyhole className="size-3 text-primary" />
</div>
<p className="text-xs font-medium">
{kind === 'consent' ? 'Connect to Acme' : 'Welcome back'}
</p>
<p className="mt-1 text-[9px] text-foreground-light">
{kind === 'consent'
? 'Review the access requested below.'
: 'Sign in to continue to your account.'}
</p>
<div className="mt-3 flex flex-col gap-1.5">
{kind === 'social' ? (
<>
<div className="flex items-center justify-center gap-2 rounded border px-2 py-1.5">
<Github className="size-3" />
Continue with GitHub
</div>
<div className="rounded border px-2 py-1.5 text-center">Continue with Google</div>
</>
) : kind === 'consent' ? (
<>
<div className="flex items-center gap-2 py-1">
<Check className="size-3 text-primary" />
Read your profile
</div>
<div className="flex items-center gap-2 py-1">
<Check className="size-3 text-primary" />
Access your projects
</div>
</>
) : (
<>
<div className="rounded border px-2 py-1.5 text-foreground-lighter">
you@example.com
</div>
<div className="rounded border px-2 py-1.5 text-foreground-lighter">••••••••</div>
</>
)}
{kind !== 'social' && (
<div className="rounded border border-brand/30 bg-brand/20 py-1.5 text-center text-foreground">
{kind === 'consent' ? 'Allow access' : 'Sign in'}
</div>
)}
</div>
</div>
) : kind === 'avatar' || kind === 'avatars' ? (
<div className="flex w-full max-w-[210px] flex-col items-center rounded-lg border bg-background px-4 py-5 shadow-lg">
<div className="flex -space-x-2">
{(kind === 'avatar' ? ['JD'] : ['JD', 'AS', 'MK', '+2']).map((initials, index) => (
<div
key={initials}
className={`relative flex size-10 items-center justify-center rounded-full border-2 border-background text-xs ${index % 2 === 0 ? 'bg-brand/20 text-primary' : 'bg-surface-300 text-foreground-light'}`}
>
{initials}
{index === 0 && (
<span className="absolute bottom-0 right-0 size-2.5 rounded-full border-2 border-background bg-brand" />
)}
</div>
))}
</div>
<p className="mt-3 text-xs font-medium">
{kind === 'avatar' ? 'Jamie Davis' : 'Better together'}
</p>
<p className="mt-1 text-foreground-light">
{kind === 'avatar' ? 'jamie@example.com' : '5 people in this workspace'}
</p>
<div className="mt-4 w-full rounded border px-3 py-1.5 text-center text-foreground-light">
{kind === 'avatar' ? 'Account settings' : 'Everyone is up to date'}
</div>
</div>
) : kind === 'cursors' ? (
<div className="relative h-36 w-full max-w-xs rounded-lg border bg-background shadow-lg">
<div className="absolute inset-x-5 top-5 h-2 w-2/5 rounded bg-surface-200" />
<div className="absolute inset-x-5 top-10 h-2 rounded bg-surface-200" />
<div className="absolute inset-x-5 top-14 h-2 w-3/5 rounded bg-surface-200" />
<div className="absolute left-10 top-16 text-primary">
<MousePointer2 className="size-5 fill-current" />
<span className="ml-4 rounded bg-brand/20 px-2 py-1">Jamie</span>
</div>
<div className="absolute bottom-7 right-6 text-foreground-light">
<MousePointer2 className="size-5 fill-current" />
<span className="ml-4 rounded bg-surface-300 px-2 py-1">Alex</span>
</div>
</div>
) : (
<PreviewWindow title={previewTitles[kind] ?? 'workspace'}>
{kind === 'table' && (
<>
<div className="flex items-center gap-2 rounded border bg-surface-100 px-2 py-1 text-foreground-light">
<Search className="size-3" />
Filter rows
</div>
{['jamie', 'alex', 'morgan'].map((name, index) => (
<div key={name} className="flex items-center gap-3 border-b pb-1.5">
<span className="text-foreground-lighter">0{index + 1}</span>
<span className="truncate">{name}@example.com</span>
<span className="ml-auto size-1.5 shrink-0 rounded-full bg-brand" />
</div>
))}
</>
)}
{kind === 'storage' && (
<>
<div className="flex items-center justify-center gap-2 rounded-md border border-dashed py-3 text-foreground-light">
<Upload className="size-3.5" />
Drop files to upload
</div>
{['cover.png', 'design.fig'].map((name) => (
<div key={name} className="flex items-center gap-2 rounded border px-2 py-1.5">
<File className="size-3 text-foreground-light" />
<span>{name}</span>
<Check className="ml-auto size-3 text-primary" />
</div>
))}
</>
)}
{kind === 'chat' && (
<>
<div className="flex items-start gap-2">
<span className="flex size-5 shrink-0 items-center justify-center rounded-full bg-surface-200">
JD
</span>
<div className="rounded-md border bg-surface-100 px-2 py-1.5">
How is the new project going?
</div>
</div>
<div className="ml-8 rounded-md border border-brand/20 bg-brand/10 px-2 py-1.5">
Ready to share a first look.
</div>
<div className="mt-1 flex items-center justify-between rounded border px-2 py-1.5 text-foreground-lighter">
<span>Write a message...</span>
<Send className="size-3" />
</div>
</>
)}
{(kind === 'editor' || kind === 'client') && (
<div className="overflow-hidden whitespace-nowrap font-mono text-[9px] leading-5 text-foreground-light">
<p>
<span className="text-primary">import</span> {'{ createClient }'}
</p>
<p className="pl-3">from &apos;@supabase/supabase-js&apos;</p>
<p className="mt-2">
<span className="text-primary">const</span> supabase = createClient(
</p>
<p className="pl-3">SUPABASE_URL,</p>
<p className="pl-3">SUPABASE_PUBLISHABLE_KEY</p>
<p>)</p>
</div>
)}
{kind === 'dashboard' && (
<>
<div className="grid grid-cols-3 gap-2">
{['Users', 'Queries', 'Files'].map((label, index) => (
<div className="rounded border p-2" key={label}>
<p className="text-[9px] text-foreground-light">{label}</p>
<p className="mt-1 text-xs">{['824', '128', '42'][index]}</p>
</div>
))}
</div>
<div className="flex h-12 items-end gap-1.5 pt-2">
{['h-2/5', 'h-3/5', 'h-1/2', 'h-4/5', 'h-3/5', 'h-full', 'h-4/5', 'h-full'].map(
(height, index) => (
<div key={index} className={`flex-1 rounded-t bg-brand/30 ${height}`} />
)
)}
</div>
</>
)}
{kind === 'mcp' &&
['get_user', 'list_projects', 'search_documents'].map((tool, index) => (
<div key={tool} className="flex items-center gap-2 rounded border p-2">
<Bot
className={`size-3.5 ${index === 0 ? 'text-primary' : 'text-foreground-light'}`}
/>
<span>{tool}</span>
<span className="ml-auto text-foreground-lighter">tool</span>
</div>
))}
{kind === 'agents' && (
<>
{['Claude Code', 'Codex'].map((client, index) => (
<div key={client} className="flex items-center gap-2 rounded border p-2">
<Bot
className={`size-3.5 ${index === 0 ? 'text-primary' : 'text-foreground-light'}`}
/>
<span>{client}</span>
<span className="ml-auto text-foreground-lighter">Revoke</span>
</div>
))}
<div className="flex items-center gap-2 rounded-md border border-dashed px-2 py-1.5 text-foreground-light">
<LockKeyhole className="size-3" />
Connect an agent
</div>
</>
)}
{kind === 'flow' && (
<div className="flex flex-col items-center">
<div className="flex items-center gap-2 rounded border px-3 py-2">
<Workflow className="size-3 text-primary" />
New event
</div>
<div className="h-4 w-px bg-border" />
<div className="h-px w-2/3 bg-border" />
<div className="flex w-full justify-around">
<div className="h-3 w-px bg-border" />
<div className="h-3 w-px bg-border" />
</div>
<div className="flex w-full justify-around gap-2">
<div className="rounded border px-2 py-2">Update record</div>
<div className="flex items-center gap-1 rounded border px-2 py-2">
<UserRound className="size-3" />
Notify team
</div>
</div>
</div>
)}
</PreviewWindow>
)}
</div>
</div>
)
}
+40 -13
View File
@@ -1,6 +1,6 @@
'use client'
import { CircleIcon, LaptopIcon, MoonIcon, SunIcon } from 'lucide-react'
import { LaptopIcon, MoonIcon, SunIcon } from 'lucide-react'
import { useTheme } from 'next-themes'
import { useRouter } from 'next/navigation'
import * as React from 'react'
@@ -13,14 +13,19 @@ import {
CommandItem,
CommandList,
CommandSeparator,
DialogDescription,
DialogProps,
DialogTitle,
} from 'ui'
import { COMMAND_ITEMS } from '@/config/docs'
import { gettingStarted } from '@/config/docs'
import { getLibraryBlockHref, libraryBlocks, libraryCategories } from '@/config/library'
import { useFramework } from '@/context/framework-context'
import { cn } from '@/lib/utils'
export function CommandMenu({ ...props }: DialogProps) {
const router = useRouter()
const { framework } = useFramework()
const [open, setOpen] = React.useState(false)
const { setTheme } = useTheme()
@@ -55,37 +60,59 @@ export function CommandMenu({ ...props }: DialogProps) {
<Button
variant="outline"
className={cn(
`relative h-8 w-full justify-start rounded-[0.5rem] bg-background text-sm font-normal text-foreground-muted shadow-none sm:pr-12
`@container relative h-8 w-full justify-start rounded-[0.5rem] bg-background text-sm font-normal text-foreground-muted shadow-none sm:pr-12 md:pr-2.5 lg:pr-12
hover:border-foreground-muted hover:bg-surface-100 hover:text-foreground-lighter
`
)}
onClick={() => setOpen(true)}
{...props}
>
<span className="hidden lg:inline-flex">Search Library...</span>
<span className="inline-flex lg:hidden">Search...</span>
<kbd className="pointer-events-none absolute right-[0.3rem] top-[0.3rem] hidden h-5 select-none items-center gap-1 rounded-sm border bg-surface-200 px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex text-foreground-light">
<span className="hidden @min-[7.5rem]:inline-flex">Search library...</span>
<span className="inline-flex @min-[7.5rem]:hidden">Search...</span>
<kbd className="pointer-events-none absolute right-[0.3rem] top-[0.3rem] hidden h-5 select-none items-center gap-1 rounded-sm border bg-surface-200 px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex md:hidden lg:flex text-foreground-light">
<span className="text-sm">⌘</span>K
</kbd>
</Button>
<CommandDialog open={open} onOpenChange={setOpen}>
<DialogTitle className="sr-only">Search the library</DialogTitle>
<DialogDescription className="sr-only">
Find guides, blocks, starter apps, and theme settings.
</DialogDescription>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandList className="min-h-0 max-h-[min(400px,calc(100dvh-8rem))] overscroll-contain [&_[cmdk-group]]:p-2! [&_[cmdk-item]]:rounded-md [&_[cmdk-item]]:p-2!">
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup key="pages" heading="Pages">
{COMMAND_ITEMS.map((navItem) => (
{gettingStarted.items.map((navItem) => (
<CommandItem
key={navItem.href}
value={navItem.label}
value={navItem.title}
onSelect={() => runCommand(() => router.push(navItem.href as string))}
>
<div className="mr-2 flex h-4 w-4 items-center justify-center">
<CircleIcon className="h-3 w-3" strokeWidth={1} />
</div>
{navItem.label}
{navItem.title}
</CommandItem>
))}
</CommandGroup>
{libraryCategories.map((category) => (
<CommandGroup key={category.slug} heading={category.name}>
{libraryBlocks
.filter((block) => block.category === category.name)
.map((block) => (
<CommandItem
key={block.slug}
value={`${block.title} ${block.category} ${block.tags.join(' ')}`}
onSelect={() =>
runCommand(() => {
const href = getLibraryBlockHref(block, framework)
if (block.external) window.location.assign(href)
else router.push(href)
})
}
>
{block.title}
</CommandItem>
))}
</CommandGroup>
))}
<CommandSeparator />
<CommandGroup heading="Theme">
<CommandItem onSelect={() => runCommand(() => setTheme('light'))}>
+7 -39
View File
@@ -5,57 +5,25 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from 'ui'
import { CommandCopyButton } from './command-copy-button'
import { useLocalStorage } from './use-local-storage'
import {
getInstallCommands,
type PackageManager,
type ShadcnFramework,
} from '@/lib/install-command'
interface CommandCopyProps {
name: string
highlight?: boolean
// For Vue, we need to use the `shadcn-vue` package instead of `shadcn`
framework?: 'react' | 'vue'
framework?: ShadcnFramework
}
type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun'
const LOCAL_STORAGE_KEY = 'package-manager-copy-command'
const getBaseUrl = () => {
if (process.env.NEXT_PUBLIC_VERCEL_TARGET_ENV === 'production') {
// we have a special alias for the production environment, added in https://github.com/shadcn-ui/ui/pull/8161
return `@supabase`
} else if (process.env.NEXT_PUBLIC_VERCEL_TARGET_ENV === 'preview') {
return `https://${process.env.NEXT_PUBLIC_VERCEL_BRANCH_URL}`
} else {
return 'http://localhost:3004'
}
}
const getComponentPath = (name: string) => {
if (process.env.NEXT_PUBLIC_VERCEL_TARGET_ENV === 'production') {
return `/${name}`
} else {
return `${process.env.NEXT_PUBLIC_BASE_PATH ?? ''}/r/${name}.json`
}
}
export function Command({ name, highlight, framework = 'react' }: CommandCopyProps) {
const [value, setValue] = useLocalStorage(LOCAL_STORAGE_KEY, 'npm')
const baseUrl = getBaseUrl()
const componentPath = getComponentPath(name)
const commands: Record<PackageManager, string> =
framework === 'vue'
? {
npm: `npx shadcn-vue@latest add ${baseUrl}${componentPath}`,
pnpm: `pnpm dlx shadcn-vue@latest add ${baseUrl}${componentPath}`,
yarn: `yarn dlx shadcn-vue@latest add ${baseUrl}${componentPath}`,
bun: `bunx --bun shadcn-vue@latest add ${baseUrl}${componentPath}`,
}
: {
npm: `npx shadcn@latest add ${baseUrl}${componentPath}`,
pnpm: `pnpm dlx shadcn@latest add ${baseUrl}${componentPath}`,
yarn: `yarn dlx shadcn@latest add ${baseUrl}${componentPath}`,
bun: `bunx --bun shadcn@latest add ${baseUrl}${componentPath}`,
}
const commands = getInstallCommands(name, { framework })
return (
<Tabs value={value} onValueChange={setValue} className="w-full">
@@ -51,7 +51,7 @@ export function ComponentPreview({
}
return <Component />
}, [name, config.style])
}, [name])
const Codes = React.Children.toArray(children) as React.ReactElement[]
const Code = Codes[index]
@@ -81,13 +81,8 @@ export function ComponentPreview({
const wideClasses = wide ? '2xl:-ml-12 2xl:-mr-12' : ''
return (
<div className={cn('mt-4 mb-12', wideClasses)}>
<div
className={cn(
'relative bg-studio',
showCode ? 'rounded-tl-md rounded-tr-md border-t border-l border-r' : 'rounded-md border'
)}
>
<div className={cn('library-component-preview mt-4 mb-12', wideClasses)}>
<div className={cn('relative', showCode ? 'border-t border-l border-r' : 'border')}>
{showGrid && (
<div className="pointer-events-none absolute h-full w-full bg-[linear-gradient(to_right,oklch(from_var(--foreground-default)_l_c_h_/_0.02)_1px,transparent_1px),linear-gradient(to_bottom,#80808012_1px,transparent_1px)] bg-size-[24px_24px]"></div>
)}
@@ -0,0 +1,73 @@
'use client'
import { Check, Copy } from 'lucide-react'
import { useEffect, useState } from 'react'
import { Button } from 'ui'
interface CopyDocPromptProps {
title: string
markdownPath: string
intent: 'create-app' | 'add-to-project'
}
export function CopyDocPrompt({ title, markdownPath, intent }: CopyDocPromptProps) {
const [status, setStatus] = useState<'idle' | 'copied' | 'error'>('idle')
useEffect(() => {
if (status !== 'copied') return
const timeout = window.setTimeout(() => setStatus('idle'), 2500)
return () => window.clearTimeout(timeout)
}, [status])
async function copyPrompt() {
const url = new URL(markdownPath, window.location.origin).href
const task =
intent === 'create-app'
? `Help me create a new application using ${title} from Supabase Library. Inspect my workspace and create the app in a suitable directory.`
: `Help me add ${title} from Supabase Library to my existing project. Inspect my project structure and reuse any Supabase client setup that is already in place.`
const prompt = [
task,
`Read ${url} for the complete guide before making changes.`,
"To add the block's files, prefer the guide's shadcn install command over copying them from the registry JSON; the CLI also installs npm dependencies and related blocks.",
'Follow the rest of the guide, adapting it to my project, and verify that the setup works.',
].join(' ')
try {
await navigator.clipboard.writeText(prompt)
setStatus('copied')
} catch {
setStatus('error')
}
}
return (
<div className="flex max-w-full flex-col items-center">
<Button
variant="secondary"
size="medium"
// Button's medium icon container forces 20px, overriding the icons' own size.
className="rounded-full border-0 px-4 [&_svg]:size-4!"
icon={
status === 'copied' ? (
<Check size={16} strokeWidth={2} />
) : (
<Copy size={16} strokeWidth={2} />
)
}
onClick={copyPrompt}
>
{status === 'copied' ? 'Prompt copied' : 'Copy agent prompt'}
</Button>
<span
role="status"
className={status === 'error' ? 'max-w-sm text-xs text-foreground-light' : 'sr-only'}
>
{status === 'error'
? 'Unable to copy the prompt. Try again.'
: status === 'copied'
? 'Prompt copied to clipboard'
: ''}
</span>
</div>
)
}
@@ -0,0 +1,82 @@
'use client'
import { useEffect, useRef, type ReactNode } from 'react'
import { cn, Tabs, TabsContent, TabsList, TabsTrigger } from 'ui'
import { frameworkTitles } from '@/config/docs'
import { useFramework } from '@/context/framework-context'
export function FrameworkQuickstart({ children }: { children: ReactNode }) {
const { framework, setFramework } = useFramework()
const tabStripRef = useRef<HTMLDivElement>(null)
const activeTabRef = useRef<HTMLButtonElement>(null)
useEffect(() => {
const strip = tabStripRef.current
const tab = activeTabRef.current
if (!strip || !tab) return
const stripBounds = strip.getBoundingClientRect()
const tabBounds = tab.getBoundingClientRect()
if (tabBounds.left < stripBounds.left) {
strip.scrollLeft += tabBounds.left - stripBounds.left
} else if (tabBounds.right > stripBounds.right) {
strip.scrollLeft += tabBounds.right - stripBounds.right
}
}, [framework])
return (
<Tabs value={framework} onValueChange={setFramework} className="w-full">
<div ref={tabStripRef} className="overflow-x-auto">
<TabsList aria-label="Framework setup guides" className="w-max min-w-full gap-0">
{Object.entries(frameworkTitles).map(([value, label]) => (
<TabsTrigger
key={value}
ref={value === framework ? activeTabRef : undefined}
value={value}
className="shrink-0 px-3 py-3 text-xs font-medium data-[state=active]:border-brand"
>
{label}
</TabsTrigger>
))}
</TabsList>
</div>
{children}
</Tabs>
)
}
export function FrameworkQuickstartTab({
value,
children,
}: {
value: string
children: ReactNode
}) {
return (
<TabsContent value={value} className="mt-8 [&>h2]:sr-only">
{children}
</TabsContent>
)
}
export function QuickstartStep({ number, children }: { number: string; children: ReactNode }) {
return (
<section
className={cn(
'grid grid-cols-[1.75rem_minmax(0,1fr)] gap-4 border-t py-6',
number === '1' && 'border-t-0 pt-0'
)}
>
<span
aria-hidden="true"
className="flex size-7 items-center justify-center rounded-full border font-mono text-xs text-foreground-lighter"
>
{number}
</span>
<div className="min-w-0 [&_h3]:m-0 [&_h3]:font-sans [&_h3]:text-sm [&_h3]:font-semibold [&_p]:mt-2! [&_p]:text-sm [&_p]:leading-6 [&_pre]:mt-4 [&_pre]:mb-0 [&_pre]:rounded-md [&_pre_code]:text-xs">
{children}
</div>
</section>
)
}
@@ -63,8 +63,11 @@ export function FrameworkSelector() {
return (
<Select value={selectedFramework} onValueChange={onSelect}>
<SelectTrigger className="w-[180px] mt-4 lg:mt-0">
<SelectValue />
<SelectTrigger
aria-label="Framework"
className="h-7 w-auto min-w-28 gap-2 rounded-md bg-surface-75 px-2 text-xs"
>
<SelectValue>{frameworkTitles[selectedFramework]}</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectGroup>
@@ -0,0 +1,172 @@
'use client'
import { ArrowUpRight, Box } from 'lucide-react'
import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'
import { Button, cn } from 'ui'
import { CatalogPreview } from '@/components/catalog-preview'
import {
getLibraryBlockHref,
libraryBlocks,
libraryCategories,
type LibraryBlock,
} from '@/config/library'
import { useFramework } from '@/context/framework-context'
function LibraryCard({ block, framework }: { block: LibraryBlock; framework: string }) {
return (
<Link
href={getLibraryBlockHref(block, framework)}
className="group flex w-full min-w-0 flex-col gap-4 rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand sm:w-[calc((100%-1.5rem)/2)] lg:w-[calc((100%-3rem)/3)] xl:w-[calc((100%-4.5rem)/4)]"
>
<div className="aspect-[4/3] overflow-hidden rounded-md border bg-surface-75 bg-[radial-gradient(var(--color-border)_1px,transparent_1px)] [background-size:20px_20px] transition-colors group-hover:border-stronger group-focus-visible:border-stronger">
<div className="h-full w-full grayscale transition-[filter] duration-200 group-hover:grayscale-0 group-focus-visible:grayscale-0 motion-reduce:transition-none">
<CatalogPreview kind={block.preview} />
</div>
</div>
<div className="flex flex-col gap-2 px-1 pb-2">
<div className="flex items-start justify-between gap-3">
<h3 className="min-w-0 text-sm font-medium tracking-tight">{block.title}</h3>
<span className="flex shrink-0 items-center gap-1 rounded border px-1.5 py-0.5 text-[10px] leading-3 text-foreground-light">
<Box aria-hidden="true" className="size-2.5" />
{block.category === 'Starter apps' ? 'Starter' : 'Block'}
</span>
</div>
<p className="max-w-sm text-sm leading-5 text-foreground-lighter">{block.description}</p>
{block.external && (
<span className="flex items-center gap-1 text-xs text-foreground-lighter">
View starter on GitHub
<ArrowUpRight aria-hidden="true" className="size-3" />
</span>
)}
</div>
</Link>
)
}
export function LibraryOverview() {
const router = useRouter()
const searchParams = useSearchParams()
const selectedCategory = searchParams.get('category')
const activeCategory =
libraryCategories.find((category) => category.name === selectedCategory)?.name ?? 'All'
const { framework } = useFramework()
const filtered = libraryBlocks.filter(
(block) => activeCategory === 'All' || block.category === activeCategory
)
const sections = libraryCategories
.map((category) => ({
...category,
items: filtered.filter((block) => block.category === category.name),
}))
.filter((section) => section.items.length > 0)
function selectCategory(category: string) {
const params = new URLSearchParams(searchParams.toString())
if (category === 'All') params.delete('category')
else params.set('category', category)
router.replace(params.size ? `/?${params}` : '/', { scroll: false })
}
return (
<main>
<section className="mx-auto flex max-w-7xl flex-col gap-6 px-4 pb-10 pt-16 md:px-8 md:pb-12 md:pt-24">
<h1 className="max-w-2xl text-balance font-heading text-4xl font-medium tracking-normal sm:text-5xl sm:leading-none">
Building blocks for your next backend.
</h1>
</section>
<section
className="mx-auto flex max-w-7xl flex-col gap-12 px-4 pb-16 md:px-8 md:pb-24"
aria-labelledby="catalog-heading"
>
<h2 id="catalog-heading" className="sr-only">
Browse blocks
</h2>
<div className="flex flex-col gap-3 border-b pb-12">
<span id="category-label" className="text-xs font-medium text-foreground-lighter">
Category
</span>
<div
role="group"
aria-labelledby="category-label"
className="grid w-fit max-w-full grid-cols-2 gap-x-4 gap-y-2 sm:grid-cols-3"
>
{['All', ...libraryCategories.map((category) => category.name)].map((category) => (
<Button
key={category}
variant="text"
aria-pressed={activeCategory === category}
onClick={() => selectCategory(category)}
className={cn(
'h-auto min-w-0 justify-start rounded-sm border-0 p-0 text-left text-sm leading-6 hover:bg-transparent hover:text-foreground',
activeCategory === category ? 'text-foreground' : 'text-foreground-light'
)}
>
{category}
</Button>
))}
</div>
</div>
<p className="sr-only" role="status">
{filtered.length} blocks found
</p>
{sections.length > 0 ? (
<div className="flex flex-col gap-12">
{sections.map((section) => (
<section
key={section.slug}
id={section.slug}
aria-labelledby={`${section.slug}-heading`}
className="flex flex-col gap-8"
>
<div className="flex flex-col gap-0">
<h2 id={`${section.slug}-heading`} className="text-lg text-foreground">
{section.name}
</h2>
<p className="text-lg text-foreground-muted">{section.description}</p>
</div>
<div className="flex flex-wrap gap-6">
{section.items.map((block) => (
<LibraryCard key={block.slug} block={block} framework={framework} />
))}
</div>
</section>
))}
</div>
) : (
<div className="flex min-h-72 flex-col items-center justify-center gap-2 text-center">
<p className="text-sm font-medium">No blocks found</p>
<p className="text-lg text-foreground-muted">Try another category.</p>
<Button
variant="default"
className="mt-3"
onClick={() => {
selectCategory('All')
}}
>
Clear filters
</Button>
</div>
)}
<div className="flex flex-col justify-between gap-5 border-t pt-8 sm:flex-row sm:items-center">
<p className="max-w-xl text-lg text-foreground-muted">
Components, blocks, and starter apps for Supabase. Add blocks to your project with a
single command.
</p>
<div className="flex flex-wrap items-center gap-3">
<Button asChild variant="default">
<Link href="/docs/getting-started/quickstart">Get started</Link>
</Button>
<Button asChild variant="text" iconRight={<ArrowUpRight className="size-3.5" />}>
<a href="https://supabase.com/docs/guides/ai-tools/ai-skills">Install skills</a>
</Button>
</div>
</div>
</section>
</main>
)
}
@@ -2,14 +2,16 @@ import Link from 'next/link'
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, cn } from 'ui'
import { BlockItem } from './block-item'
import { BlockOverview } from './block-overview'
import { BlockPreview } from './block-preview'
import { Callout } from './callout'
import { CatalogPreview } from './catalog-preview'
import { ComponentPreview } from './component-preview'
import { CopyButton } from './copy-button'
import { DualRealtimeChat } from './dual-realtime-chat'
import { DualRealtimeFlow } from './dual-realtime-flow'
import { DualRealtimeMonaco } from './dual-realtime-monaco'
import { RegistryBlock } from './registry-block'
import { FrameworkQuickstart, FrameworkQuickstartTab, QuickstartStep } from './framework-quickstart'
import { StyleWrapper } from './style-wrapper'
import TanStackBeta from './tanstack-beta'
import { TanstackDBGenerator } from './tanstack-db-generator'
@@ -17,7 +19,11 @@ import { useMDXComponent } from '@/lib/use-mdx-component'
import type { Style } from '@/registry/styles'
const components = {
RegistryBlock,
BlockOverview,
CatalogPreview,
FrameworkQuickstart,
FrameworkQuickstartTab,
QuickstartStep,
Accordion,
AccordionContent,
AccordionItem,
@@ -1,13 +0,0 @@
'use server'
import path from 'path'
import { BlockItemCode } from './block-item-code'
import { generateRegistryTree } from '@/lib/process-registry'
export async function RegistryBlock({ itemName }: { itemName: string }) {
const registryPath = path.join(process.cwd(), 'public', 'r', `${itemName}.json`)
const tree = generateRegistryTree(registryPath)
return <BlockItemCode files={tree} />
}
@@ -19,7 +19,7 @@ interface NavigationItemProps
onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void
}
const NavigationItem: React.FC<NavigationItemProps> = ({ item, onClick, ...props }) => {
export const NavigationItem: React.FC<NavigationItemProps> = ({ item, onClick, ...props }) => {
const { setOpen } = useMobileMenu()
const { framework } = useFramework()
const pathname = usePathname()
@@ -35,7 +35,9 @@ const NavigationItem: React.FC<NavigationItemProps> = ({ item, onClick, ...props
// An item supports the current framework if either:
// 1. It has no framework restrictions (supports all frameworks)
// 2. The current framework is in its list of supported frameworks
return !hasFrameworkRestrictions || supportedFrameworks.includes(framework as any)
return (
!hasFrameworkRestrictions || supportedFrameworks.some((supported) => supported === framework)
)
}
// Build URL with priority:
@@ -85,13 +87,14 @@ const NavigationItem: React.FC<NavigationItemProps> = ({ item, onClick, ...props
href={href || '#'}
{...props}
onClick={handleClick}
aria-current={isActive ? 'page' : undefined}
className={cn(
'relative',
'flex',
'items-center justify-between',
'h-6',
'min-h-8 rounded-md py-1.5',
'text-sm',
'px-6',
'px-3',
'transition-all',
isActive
? 'bg-selection text-foreground'
@@ -99,14 +102,6 @@ const NavigationItem: React.FC<NavigationItemProps> = ({ item, onClick, ...props
props.className
)}
>
{/* Active indicator bar */}
<div
className={cn(
'transition',
'absolute left-0 w-1 h-full bg-foreground',
isActive ? 'opacity-100' : 'opacity-0'
)}
/>
{item.title}
{item.new && <Badge variant="success">New</Badge>}
</Link>
@@ -114,5 +109,3 @@ const NavigationItem: React.FC<NavigationItemProps> = ({ item, onClick, ...props
}
NavigationItem.displayName = 'NavigationItem'
export default NavigationItem
+41 -126
View File
@@ -1,134 +1,49 @@
'use client'
import Link from 'next/link'
import { CommandMenu } from './command-menu'
import { ThemeSwitcherDropdown } from './theme-switcher-dropdown'
import NavigationItem from '@/components/side-navigation-item'
import {
componentPages,
gettingStarted,
mcpBlocks,
oauthBlocks,
platformBlocks,
starterApps,
} from '@/config/docs'
import { NavigationItem } from '@/components/side-navigation-item'
import { gettingStarted } from '@/config/docs'
import { getLibraryBlockHref, libraryBlocks, libraryCategories } from '@/config/library'
import { useFramework } from '@/context/framework-context'
import { useMobileMenu } from '@/hooks/use-mobile-menu'
export function SideNavigation() {
const { framework } = useFramework()
const { setOpen } = useMobileMenu()
function SideNavigation() {
return (
<nav className="flex flex-col h-full min-w-[220px]">
<div className="p-6">
<div className="flex items-start justify-between mb-4">
<Link href="/">
<svg
xmlns="http://www.w3.org/2000/svg"
width="109"
height="113"
viewBox="0 0 109 113"
fill="none"
className="w-6 h-6"
>
<path
d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0625L99.1935 40.0625C107.384 40.0625 111.952 49.5226 106.859 55.9372L63.7076 110.284Z"
fill="url(#paint0_linear)"
<nav aria-label="Library navigation" className="flex min-w-0 flex-col p-5">
<Link href="/" onClick={() => setOpen(false)} className="mb-8 pr-6 text-sm font-medium">
Supabase Library
</Link>
<div className="mb-6 space-y-0.5">
<p className="mb-2 px-3 text-xs text-foreground-lighter">Getting started</p>
<NavigationItem item={{ title: 'Browse All Blocks', href: '/', items: [] }} />
{gettingStarted.items.map((item) => (
<NavigationItem item={item} key={item.href} />
))}
</div>
{libraryCategories.map((category) => {
const blocks = libraryBlocks.filter((block) => block.category === category.name)
if (blocks.length === 0) return null
return (
<div key={category.name} className="mb-6 space-y-0.5">
<p className="mb-2 px-3 text-xs text-foreground-lighter">{category.name}</p>
{blocks.map((block) => (
<NavigationItem
key={block.slug}
item={{
title: block.title,
href: getLibraryBlockHref(block, framework),
items: [],
}}
/>
<path
d="M63.7076 110.284C60.8481 113.885 55.0502 111.912 54.9813 107.314L53.9738 40.0625L99.1935 40.0625C107.384 40.0625 111.952 49.5226 106.859 55.9372L63.7076 110.284Z"
fill="url(#paint1_linear)"
fillOpacity="0.2"
/>
<path
d="M45.317 2.07103C48.1765 -1.53037 53.9745 0.442937 54.0434 5.041L54.4849 72.2922H9.83113C1.64038 72.2922 -2.92775 62.8321 2.1655 56.4175L45.317 2.07103Z"
fill="#3ECF8E"
/>
<defs>
<linearGradient
id="paint0_linear"
x1="53.9738"
y1="54.9738"
x2="94.1635"
y2="71.8293"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#249361" />
<stop offset="1" stopColor="#3ECF8E" />
</linearGradient>
<linearGradient
id="paint1_linear"
x1="36.1558"
y1="30.5779"
x2="54.4844"
y2="65.0804"
gradientUnits="userSpaceOnUse"
>
<stop />
<stop offset="1" stopOpacity="0" />
</linearGradient>
</defs>
</svg>
</Link>
<ThemeSwitcherDropdown />
</div>
<Link href="/" className="mb-4 block">
<h1>Supabase Library</h1>
</Link>
{/* <TopNavigationSearch /> */}
<CommandMenu />
</div>
<div className="pb-6 space-y-0.5">
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-widest">
{gettingStarted.title}
</div>
{gettingStarted.items.map((item, i) => (
<NavigationItem item={item} key={`${item.href}-${i}`} />
))}
</div>
<div className="pb-6 space-y-0.5">
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-widest">
{starterApps.title}
</div>
{starterApps.items.map((item, i) => (
<NavigationItem item={item} key={`${item.href}-${i}`} />
))}
</div>
<div className="pb-6">
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-widest">
UI Blocks
</div>
<div className="space-y-0.5">
{/* Render items based on component definitions */}
{componentPages.items.map((component, i) => (
<NavigationItem
item={component}
key={`${component.href?.toString() || component.title}-${i}`}
/>
))}
</div>
</div>
<div className="pb-6">
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-widest">
{oauthBlocks.title}
</div>
{oauthBlocks.items.map((item, i) => (
<NavigationItem item={item} key={`${item.href}-${i}`} />
))}
</div>
<div className="pb-6">
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-widest">
{mcpBlocks.title}
</div>
{mcpBlocks.items.map((item, i) => (
<NavigationItem item={item} key={`${item.href}-${i}`} />
))}
</div>
<div className="pb-6">
<div className="font-mono uppercase text-xs text-foreground-lighter/75 mb-2 px-6 tracking-widest">
{platformBlocks.title}
</div>
{platformBlocks.items.map((item, i) => (
<NavigationItem item={item} key={`${item.href}-${i}`} />
))}
</div>
))}
</div>
)
})}
</nav>
)
}
export default SideNavigation
+24 -24
View File
@@ -1,32 +1,32 @@
import { Menu } from 'lucide-react'
import { Button, ScrollArea, SheetContent, SheetTrigger } from 'ui'
import { Button, ScrollArea, SheetContent, SheetDescription, SheetTitle, SheetTrigger } from 'ui'
import { MobileMenuSheet } from './mobile-menu-sheet'
import { ThemeSwitcherDropdown } from './theme-switcher-dropdown'
import SideNavigation from '@/components/side-navigation'
import { SideNavigation } from '@/components/side-navigation'
export function Sidebar() {
return (
<>
<div className="md:hidden fixed top-0 left-0 right-0 z-50 bg-background justify-between flex items-center px-8 py-3 border-b">
<MobileMenuSheet>
<SheetTrigger asChild>
<Button variant="outline" icon={<Menu />} />
</SheetTrigger>
<SheetContent side="left" className="p-0 w-80" showClose={false}>
<ScrollArea className="h-full">
<SideNavigation />
</ScrollArea>
</SheetContent>
</MobileMenuSheet>
<ThemeSwitcherDropdown />
</div>
<aside className="fixed z-30 top-0 hidden h-screen w-full shrink-0 md:sticky md:block bg-200 border-r border-muted/50">
<ScrollArea className="h-full">
<SideNavigation />
</ScrollArea>
</aside>
</>
<div className="md:hidden">
<MobileMenuSheet>
<SheetTrigger asChild>
<Button
variant="text"
size="tiny"
className="px-2"
icon={<Menu size={18} />}
aria-label="Open library navigation"
/>
</SheetTrigger>
<SheetContent side="left" className="w-80 max-w-[90vw] p-0">
<SheetTitle className="sr-only">Library navigation</SheetTitle>
<SheetDescription className="sr-only">
Browse guides and blocks by category.
</SheetDescription>
<ScrollArea className="h-full">
<SideNavigation />
</ScrollArea>
</SheetContent>
</MobileMenuSheet>
</div>
)
}
+2 -23
View File
@@ -1,6 +1,6 @@
export function SiteFooter() {
return (
<footer className="py-6 px-4 md:px-8 md:py-0 mx-auto w-full max-w-site">
<footer className="py-6 px-4 md:px-8 md:py-0 mx-auto w-full max-w-7xl">
<div className="flex flex-col items-center justify-between gap-4 md:h-24 md:flex-row">
<p className="text-balance text-center text-sm leading-loose text-foreground-muted md:text-left">
Built by{' '}
@@ -14,7 +14,7 @@ export function SiteFooter() {
</a>
. The source code is available on{' '}
<a
href="https://github.com/supabase/supabase/tree/master/apps/library"
href="https://github.com/supabase/supabase/tree/master/apps/ui-library"
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4 hover:text-foreground-lighter"
@@ -23,27 +23,6 @@ export function SiteFooter() {
</a>
.
</p>
<p className="text-balance text-center text-sm leading-loose text-foreground-muted">
Site inspired by{' '}
<a
href="https://www.radix-ui.com/themes/docs/overview/getting-started"
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4 hover:text-foreground-lighter"
>
Radix
</a>
,{' '}
<a
href="https://ui.shadcn.com"
target="_blank"
rel="noreferrer"
className="font-medium underline underline-offset-4 hover:text-foreground-lighter"
>
shadcn/ui
</a>
.
</p>
</div>
</footer>
)
+205
View File
@@ -0,0 +1,205 @@
'use client'
import supabaseLogoIcon from 'common/assets/images/supabase-logo-icon.svg'
import { ArrowUpRight, ChevronRight, Code2, Grid2X2 } from 'lucide-react'
import Image from 'next/image'
import Link from 'next/link'
import { useState } from 'react'
import {
Button,
buttonVariants,
cn,
NavigationMenu,
NavigationMenuContent,
NavigationMenuItem,
NavigationMenuLink,
NavigationMenuList,
NavigationMenuTrigger,
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from 'ui'
import { CommandMenu } from '@/components/command-menu'
import { Sidebar } from '@/components/sidebar'
import { ThemeSwitcherDropdown } from '@/components/theme-switcher-dropdown'
import { getLibraryBlockHref, libraryBlocks, libraryCategories } from '@/config/library'
import { useFramework } from '@/context/framework-context'
import { useMenuAim } from '@/hooks/use-menu-aim'
export function SiteHeader() {
const { framework } = useFramework()
const [activeCategory, setActiveCategory] = useState<string>(libraryCategories[0].name)
const menuAim = useMenuAim(activeCategory, setActiveCategory)
return (
<header className="sticky top-0 z-40 before:pointer-events-none before:absolute before:inset-x-0 before:top-0 before:h-24 before:bg-gradient-to-b before:from-background before:via-background/90 before:via-60% before:to-transparent">
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:bg-background focus:p-3"
>
Skip to content
</a>
<div className="relative mx-auto flex h-14 max-w-site items-center justify-between gap-4 px-4 md:px-8">
<div className="flex shrink-0 items-center gap-4 lg:gap-8">
<Link
href="/"
className="flex items-center gap-2 text-sm font-medium tracking-tight"
aria-label="Supabase Library home"
>
<Image src={supabaseLogoIcon} alt="" width={24} height={24} />
<span aria-hidden="true" className="text-border">
/
</span>
<span className="text-foreground-light">Library</span>
</Link>
</div>
<nav aria-label="Main navigation" className="absolute left-1/2 -translate-x-1/2">
<NavigationMenu
delayDuration={0}
className="hidden h-14 md:flex md:flex-none [&>div:last-child]:left-1/2 [&>div:last-child]:-translate-x-1/2"
viewportClassName="rounded-xl bg-background data-[state=open]:slide-in-from-right-0!"
>
<NavigationMenuList className="flex-none gap-1">
<NavigationMenuItem>
<NavigationMenuTrigger
className={cn(
buttonVariants({ variant: 'text', size: 'small' }),
'h-8 rounded-full px-3 text-foreground-light hover:text-foreground data-[state=open]:text-foreground'
)}
>
<Grid2X2 aria-hidden="true" className="size-4 text-muted-foreground" />
<span className="font-medium text-foreground">Explore</span>
</NavigationMenuTrigger>
<NavigationMenuContent>
<Tabs
value={activeCategory}
onValueChange={menuAim.selectCategory}
onPointerMove={menuAim.onPointerMove}
onPointerLeave={menuAim.onPointerLeave}
orientation="vertical"
className="flex w-[640px] max-w-[calc(100vw-4rem)] max-h-[calc(100dvh-5rem)] overflow-y-auto"
>
<div className="flex w-60 shrink-0 flex-col border-r bg-surface-75 py-2">
<TabsList
aria-label="Block categories"
className="h-auto flex-col items-stretch justify-start border-b-0"
>
{libraryCategories.map((category) => (
<TabsTrigger
key={category.name}
value={category.name}
data-menu-category={category.name}
className="justify-between gap-3 whitespace-normal border-0 px-4 py-2.5 text-left data-[state=active]:bg-surface-200 data-[state=active]:shadow-none"
>
{category.name}
<ChevronRight
size={14}
className="shrink-0 text-foreground-lighter group-data-[state=active]:text-primary"
/>
</TabsTrigger>
))}
</TabsList>
<ul className="mx-4 mt-5 flex flex-col border-t pt-2.5">
{[
{ href: '/', label: 'Browse all blocks' },
{ href: '/docs/getting-started/quickstart', label: 'Quickstart' },
{ href: '/docs/getting-started/faq', label: 'FAQ' },
].map(({ href, label }) => (
<li key={href}>
<NavigationMenuLink asChild>
<Link
href={href}
className="block py-1.5 text-xs text-foreground-light hover:text-foreground"
>
{label}
</Link>
</NavigationMenuLink>
</li>
))}
</ul>
</div>
{libraryCategories.map((category) => {
const blocks = libraryBlocks.filter(
(block) => block.category === category.name
)
return (
<TabsContent
key={category.name}
value={category.name}
className="m-0 min-w-0 flex-1 p-6"
>
{blocks.length > 0 ? (
<ul className="flex flex-col gap-4">
{blocks.map((block) => (
<li key={block.slug}>
<NavigationMenuLink asChild>
<Link
href={getLibraryBlockHref(block, framework)}
className="group/menu-item flex h-fit flex-col gap-1 rounded-md text-sm text-foreground-light hover:text-foreground select-none leading-none no-underline focus-ring focus-visible:text-foreground"
>
<span className="flex items-center gap-1">
<span className="text-sm leading-snug text-foreground">
{block.title}
</span>
<ChevronRight
strokeWidth={2}
className="h-3 w-3 text-foreground transition-all -translate-x-1 opacity-0 group-hover/menu-item:translate-x-0 group-hover/menu-item:opacity-100 motion-reduce:transition-none motion-reduce:translate-x-0"
/>
</span>
<span className="text-xs leading-snug text-foreground-lighter group-hover/menu-item:text-foreground-light group-focus-visible/menu-item:text-foreground-light">
{block.description}
</span>
</Link>
</NavigationMenuLink>
</li>
))}
</ul>
) : (
<p className="text-base text-foreground-light">
No blocks in this category yet.
</p>
)}
</TabsContent>
)
})}
</Tabs>
</NavigationMenuContent>
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</nav>
{/* Below md the centered nav is hidden, so search can take the free space. */}
<div className="flex min-w-0 flex-1 items-center justify-end gap-2 md:flex-none">
<div className="min-w-0 max-w-48 flex-1 md:w-28 md:flex-none lg:w-48">
<CommandMenu />
</div>
<Button
asChild
variant="text"
size="tiny"
className="hidden h-8 w-8 p-0 text-muted-foreground hover:text-foreground sm:inline-flex"
aria-label="View source on GitHub"
>
<a href="https://github.com/supabase/supabase/tree/master/apps/ui-library">
<Code2 size={16} />
</a>
</Button>
<Button
asChild
variant="text"
size="small"
className="hidden h-8 px-3 text-sm font-medium text-muted-foreground hover:text-foreground sm:inline-flex"
iconRight={<ArrowUpRight size={14} />}
>
<a href="https://supabase.com/docs">Docs</a>
</Button>
<ThemeSwitcherDropdown />
<Sidebar />
</div>
</div>
</header>
)
}
@@ -48,7 +48,8 @@ const ThemeSwitcherDropdown = () => {
)
}
const iconClasses = 'text-foreground-light group-data-open:text-foreground'
const iconClasses =
'text-muted-foreground group-hover:text-foreground group-data-open:text-foreground'
return (
<>
@@ -57,7 +58,8 @@ const ThemeSwitcherDropdown = () => {
<Button
variant="text"
size="tiny"
className="px-1 group"
aria-label="Choose a theme"
className="group h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
icon={
resolvedTheme?.includes('light') ? (
<Sun className={iconClasses} />
-107
View File
@@ -1,107 +0,0 @@
// @ts-nocheck
'use client'
import * as React from 'react'
import { useMounted } from '@/hooks/use-mounted'
import { TableOfContents } from '@/lib/toc'
import { cn } from '@/lib/utils'
interface TocProps {
toc: TableOfContents
}
export function DashboardTableOfContents({ toc }: TocProps) {
const itemIds = React.useMemo(
() =>
toc.items
? toc.items
.flatMap((item) => [item.url, item?.items?.map((item) => item.url)])
.flat()
.filter(Boolean)
.map((id) => id?.split('#')[1])
: [],
[toc]
)
const activeHeading = useActiveItem(itemIds)
const mounted = useMounted()
if (!toc?.items || !mounted) {
return null
}
return (
<div className="space-y-2">
<p className="font-medium text-foreground-light">On This Page</p>
<Tree tree={toc} activeItem={activeHeading} />
</div>
)
}
function useActiveItem(itemIds: string[]) {
const [activeId, setActiveId] = React.useState(null)
React.useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setActiveId(entry.target.id)
}
})
},
{ rootMargin: `0% 0% -80% 0%` }
)
itemIds?.forEach((id) => {
const element = document.getElementById(id)
if (element) {
observer.observe(element)
}
})
return () => {
itemIds?.forEach((id) => {
const element = document.getElementById(id)
if (element) {
observer.unobserve(element)
}
})
}
}, [itemIds])
return activeId
}
interface TreeProps {
tree: TableOfContents
level?: number
activeItem?: string
}
function Tree({ tree, level = 1, activeItem }: TreeProps) {
return tree?.items?.length && level < 3 ? (
<ul className={cn('m-0 list-none', { 'pl-4': level !== 1 })}>
{tree.items.map((item, index) => {
return (
<li key={index} className={cn('mt-0 pt-2')}>
<a
href={item.url}
className={cn(
'inline-block no-underline transition-colors hover:text-foreground',
item.url === `#${activeItem}`
? 'font-medium text-foreground'
: 'text-foreground-muted'
)}
>
{item.title}
</a>
{item.items?.length ? (
<Tree tree={item} level={level + 1} activeItem={activeItem} />
) : null}
</li>
)
})}
</ul>
) : null
}
+2 -59
View File
@@ -10,10 +10,10 @@ export const gettingStarted: SidebarNavGroup = {
commandItemLabel: 'Introduction',
},
{
title: 'Quick Start',
title: 'Quickstart',
href: '/docs/getting-started/quickstart',
items: [],
commandItemLabel: 'Quick Start',
commandItemLabel: 'Quickstart',
},
{
title: 'FAQ',
@@ -24,40 +24,6 @@ export const gettingStarted: SidebarNavGroup = {
],
}
export const starterApps: SidebarNavGroup = {
title: 'Starter Apps',
items: [
{
title: 'Next.js Starter',
href: '/docs/starters/nextjs-starter',
items: [],
new: true,
commandItemLabel: 'Next.js Starter',
},
{
title: 'SaaS Starter',
href: '/docs/starters/saas-starter',
items: [],
new: true,
commandItemLabel: 'SaaS Starter',
},
{
title: 'AI Chat App',
href: '/docs/starters/ai-chat-app',
items: [],
new: true,
commandItemLabel: 'AI Chat App',
},
{
title: 'Flutter Starter',
href: '/docs/starters/flutter-starter',
items: [],
new: true,
commandItemLabel: 'Flutter Starter',
},
],
}
export const platformBlocks: SidebarNavGroup = {
title: 'Platform',
items: [
@@ -193,29 +159,6 @@ export const componentPages: SidebarNavGroup = {
],
}
export const COMMAND_ITEMS = [
...gettingStarted.items.map((item) => ({
label: item.commandItemLabel,
href: item.href,
})),
...starterApps.items.map((item) => ({
label: item.commandItemLabel,
href: item.href,
})),
...componentPages.items.map((item) => ({
label: item.commandItemLabel,
href: item.href,
})),
...oauthBlocks.items.map((item) => ({
label: item.commandItemLabel,
href: item.href,
})),
...mcpBlocks.items.map((item) => ({
label: item.commandItemLabel,
href: item.href,
})),
]
// Framework titles for display
export const frameworkTitles: Record<string, string> = {
nextjs: 'Next.js',
@@ -1,94 +1,134 @@
---
title: FAQ
description: Frequently asked questions
title: Frequently Asked Questions
description: Exploring, installing, and adapting Supabase Library blocks
---
<Accordion openBehaviour="single" collapsible>
<AccordionItem value="how">
<AccordionTrigger>How does it work? </AccordionTrigger>
<AccordionContent>
You run a command in a new or existing project that looks something like this:
```bash
npx shadcn@latest add https://supabase.com/library/r/password-based-auth-nextjs.json
```
This will add all of the files to your project and install the dependencies.
After you've run the command, you can start using the components. In the case of Auth, you should be up and running with user sign in/sign up with just this one command.
</AccordionContent>
</AccordionItem>
<AccordionItem value="overwrite">
<AccordionTrigger>Will it overwrite existing files?</AccordionTrigger>
<AccordionContent>
No. If you run a command in an existing project, it will <u>not</u> overwrite your existing
files. If there conflicting files, you will be asked what you want to do (leave them or
overwrite them). If you run it in a new project, it will just add the new files to your project.
</AccordionContent>
</AccordionItem>
<AccordionItem value="existing-project">
<AccordionTrigger>I've already started a project, what should I do?</AccordionTrigger>
<AccordionContent>
You can still use these components, even if you've already started your project! The commands
won't overwrite your existing files, but you should still be careful with the changes to makes
sure you understand what's been added. You should commit your changes into Git before running
these commands so you'll be able to see a clean diff of the changes.
</AccordionContent>
</AccordionItem>
<AccordionItem value="shadcn">
<AccordionTrigger>I've already installed shadcn/ui, what should I do?</AccordionTrigger>
<AccordionContent>
You're off to a great start! You can now run an add command to add the desired component to your
project.
</AccordionContent>
</AccordionItem>
<AccordionItem value="quickstart">
<AccordionTrigger>
I already started with `npx create-next-app -e with-supabase` what should I do now?
</AccordionTrigger>
<AccordionContent>
Our Next.js quickstart is a great starting point for building a full app with Supabase and
Next.js. If you've already started with this template, you can just add the components in this library to your project.
</AccordionContent>
</AccordionItem>
<AccordionItem value="quickstart-needed">
<AccordionTrigger>
Do I need to install one of the framework quickstarts before using this library?
</AccordionTrigger>
<AccordionContent>
No. You don't need to start with one of our framework quickstarts.
<div className="library-faq border-y">
If you already have an existing project, you can just add the components in this library to your project. If you're starting from scratch, you should use one of the shadcn/ui setup guides for your framework of choice via the [Quickstart](/library/docs/getting-started/quickstart) page.
</AccordionContent>
</AccordionItem>
<AccordionItem value="ai-builder">
<AccordionTrigger>I used an AI builder to build an app, what should I do?</AccordionTrigger>
<AccordionContent>
If your app is already built, you can use any of the commands to drop components into your
project. Your app likely already has a Supabase client set up, so you can just continue using
that if it's working well for you.
</AccordionContent>
</AccordionItem>
<AccordionItem value="react-only">
<AccordionTrigger>Why are many UI blocks React-only?</AccordionTrigger>
<AccordionContent>
Several reasons.
<details className="group border-b py-6 last:border-b-0">
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
<span>What is Supabase Library?</span>
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
</summary>
First, we use React ourselves and know the ecosystem really well. That makes these UI blocks easy for us to build and maintain.
Supabase Library is a collection of blocks and starter apps for Supabase projects. Browse by category, inspect a block, and follow its installation instructions. The files live in your project, so you can adapt them to your application.
Second, the UI portion of the library stitches together existing shadcn/ui and internal components into Supabase-specific workflows. It builds on an established tool rather than creating a new UI framework from the ground up.
</details>
Last, it's a way for us to better distribute components that we already had out there in different places (our docs and guides, different framework quickstarts, etc)
<details className="group border-b py-6 last:border-b-0">
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
<span>How do I install a block?</span>
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
</summary>
TL;DR: shadcn/ui + our internal React experience make this easy to do well. Doing it for other ecosystems would be much more challenging.
</AccordionContent>
</AccordionItem>
<AccordionItem value="other-frameworks">
<AccordionTrigger>Any plans for other, non-React frameworks?</AccordionTrigger>
<AccordionContent>
Not at the moment, but we're not ruling it out.
Open a block page, choose your framework, and run its installation command from your project directory. For example, this adds the Next.js password authentication block:
Our expertise is predominantly in React—that's the ecosystem in which we think we can provide the highest quality tools for our users. But we've heard from lots of people in the Vue and Svelte communities in particular, and agree something like this would be really beneficial for them.
```bash
npx shadcn@latest add https://supabase.com/library/r/password-based-auth-nextjs.json
```
This isn't a situation like "we will never do it", but instead more of a "we don't want to reinvent the wheel". If the right opportunity comes up for supporting Vue, Svelte, etc like this, we're very open to supporting it.
</AccordionContent>
</AccordionItem>
The command adds the block's files and dependencies. Follow the block's setup instructions to configure your Supabase project, environment variables, and any required redirect URLs or email templates. Vue and Nuxt blocks use `shadcn-vue` instead of `shadcn`.
</Accordion>
</details>
<details className="group border-b py-6 last:border-b-0">
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
<span>What does a starter app include?</span>
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
</summary>
Starter apps are blocks that provide a starting point for a complete application. Each starter page includes its framework, setup commands, configuration steps, and source repository. Follow the commands on that page: some starters compose library blocks, while others use an existing application template.
</details>
<details className="group border-b py-6 last:border-b-0">
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
<span>Will installing a block overwrite existing files?</span>
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
</summary>
The block installer asks how to handle conflicting files. Review those prompts and the resulting changes before accepting replacements. Commit your current work first so you can inspect the diff and recover any changes you want to keep.
</details>
<details className="group border-b py-6 last:border-b-0">
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
<span>Can I use blocks in an existing project?</span>
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
</summary>
Yes. Use the block's framework-specific instructions and adapt its files to your project structure. If you already have a working Supabase client, reuse it instead of replacing it. You can also copy the agent prompt from the block page to help adapt the installation to your conventions.
</details>
<details className="group border-b py-6 last:border-b-0">
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
<span>What if I already use shadcn/ui?</span>
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
</summary>
You can run the block's add command without initializing shadcn/ui again. Keep your existing `components.json`, aliases, and theme. Review the block's prerequisites if you encounter a setup issue.
</details>
<details className="group border-b py-6 last:border-b-0">
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
<span>What if I started with the Next.js Supabase template?</span>
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
</summary>
The `with-supabase` template already includes a Supabase client and authentication setup. Add the blocks you need and review overlapping files before replacing them. You do not need to create another app or repeat the client setup.
</details>
<details className="group border-b py-6 last:border-b-0">
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
<span>Do I need a framework quickstart first?</span>
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
</summary>
Use the [Quickstart](/library/docs/getting-started/quickstart) if you are starting a new project. An existing project can install blocks directly once it meets the block's framework and component-library requirements.
</details>
<details className="group border-b py-6 last:border-b-0">
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
<span>Can I add blocks to an app built with AI?</span>
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
</summary>
Yes, if the app uses a supported framework. Keep any Supabase client that is already working, install the block, and adapt its components to your app. Review the changes just as you would for any other project.
</details>
<details className="group border-b py-6 last:border-b-0">
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
<span>Which frameworks are supported?</span>
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
</summary>
The library includes blocks for Next.js, React Router, TanStack Start, React SPA, Vue, and Nuxt. Support varies by block; the framework selector shows the available versions. Starter apps can use other frameworks, such as Flutter, with their own setup instructions.
</details>
<details className="group border-b py-6 last:border-b-0">
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
<span>Why are some blocks only available for React?</span>
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
</summary>
Some blocks build on React-specific libraries and components used in Supabase's own tools. Other blocks also have Vue and Nuxt versions. Check the framework selector on the block page for its current support; a React block cannot be installed directly into a different UI framework.
</details>
<details className="group border-b py-6 last:border-b-0">
<summary className="flex cursor-pointer list-none items-center justify-between gap-6 text-sm font-semibold [&::-webkit-details-marker]:hidden">
<span>Where can I get help?</span>
<span aria-hidden="true" className="font-mono text-lg font-normal text-foreground-lighter transition-transform group-open:rotate-45 motion-reduce:transition-none">+</span>
</summary>
Start with the [Quickstart](/library/docs/getting-started/quickstart) and the setup notes on your block's page. For Supabase configuration, see the [documentation](https://supabase.com/docs). You can report library issues in the [Supabase repository](https://github.com/supabase/supabase/issues).
</details>
</div>
@@ -1,77 +1,452 @@
---
title: Quick Start
description: Install shadcn/ui and use the components in your project
title: Quickstart
description: Create an app, connect Supabase, and install your first library block
---
## Set up shadcn/ui
<FrameworkQuickstart>
Start by installing [shadcn/ui](https://ui.shadcn.com/) in your project. Use the appropriate setup guide linked below for your framework of choice.
<FrameworkQuickstartTab value="nextjs">
If you're already using a recent version of shadcn/ui in your existing project, you can skip this step—this library should work with your current setup. However, if you encounter issues, try deleting your existing `components.json` file and follow one of the guides to reset your setup.
## Next.js
<div className="grid gap-4 mt-8 sm:gap-6">
<Card>
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
className="w-10 h-10"
fill="currentColor"
>
<title>Next.js</title>
<path d="M11.5725 0c-.1763 0-.3098.0013-.3584.0067-.0516.0053-.2159.021-.3636.0328-3.4088.3073-6.6017 2.1463-8.624 4.9728C1.1004 6.584.3802 8.3666.1082 10.255c-.0962.659-.108.8537-.108 1.7474s.012 1.0884.108 1.7476c.652 4.506 3.8591 8.2919 8.2087 9.6945.7789.2511 1.6.4223 2.5337.5255.3636.04 1.9354.04 2.299 0 1.6117-.1783 2.9772-.577 4.3237-1.2643.2065-.1056.2464-.1337.2183-.1573-.0188-.0139-.8987-1.1938-1.9543-2.62l-1.919-2.592-2.4047-3.5583c-1.3231-1.9564-2.4117-3.556-2.4211-3.556-.0094-.0026-.0187 1.5787-.0235 3.509-.0067 3.3802-.0093 3.5162-.0516 3.596-.061.115-.108.1618-.2064.2134-.075.0374-.1408.0445-.495.0445h-.406l-.1078-.068a.4383.4383 0 01-.1572-.1712l-.0493-.1056.0053-4.703.0067-4.7054.0726-.0915c.0376-.0493.1174-.1125.1736-.143.0962-.047.1338-.0517.5396-.0517.4787 0 .5584.0187.6827.1547.0353.0377 1.3373 1.9987 2.895 4.3608a10760.433 10760.433 0 004.7344 7.1706l1.9002 2.8782.096-.0633c.8518-.5536 1.7525-1.3418 2.4657-2.1627 1.5179-1.7429 2.4963-3.868 2.8247-6.134.0961-.6591.1078-.854.1078-1.7475 0-.8937-.012-1.0884-.1078-1.7476-.6522-4.506-3.8592-8.2919-8.2087-9.6945-.7672-.2487-1.5836-.42-2.4985-.5232-.169-.0176-1.0835-.0366-1.6123-.037zm4.0685 7.217c.3473 0 .4082.0053.4857.047.1127.0562.204.1642.237.2767.0186.061.0234 1.3653.0186 4.3044l-.0067 4.2175-.7436-1.14-.7461-1.14v-3.066c0-1.982.0093-3.0963.0234-3.1502.0375-.1313.1196-.2346.2323-.2955.0961-.0494.1313-.054.4997-.054z" />
</svg>
<p className="font-medium mt-2 mb-6">Next.js</p>
<div className="border rounded-md py-1 px-2 bg-muted flex items-center gap-2">
<p>
<span className="select-none">`$`</span>`npx create-next-app -e with-supabase`
</p>
<CopyButton value="npx create-next-app -e with-supabase" />
</div>
</Card>
<LinkedCard target="_blank" href="https://ui.shadcn.com/docs/installation/react-router">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
className="w-10 h-10"
fill="currentColor"
>
<path d="M12.118 5.466a2.306 2.306 0 0 0-.623.08c-.278.067-.702.332-.953.583-.41.423-.49.609-.662 1.469-.08.423.41 1.43.847 1.734.45.317 1.085.502 2.065.608 1.429.16 1.84.636 1.84 2.197 0 1.377-.385 1.747-1.96 1.906-1.707.172-2.58.834-2.765 2.117-.106.781.41 1.76 1.125 2.091 1.627.768 3.15-.198 3.467-2.196.211-1.284.622-1.642 1.998-1.747 1.588-.133 2.409-.675 2.713-1.787.278-1.02-.304-2.157-1.297-2.554-.264-.106-.873-.238-1.35-.291-1.495-.16-1.879-.424-2.038-1.39-.225-1.337-.317-1.562-.794-2.09a2.174 2.174 0 0 0-1.613-.73zm-4.785 4.36a2.145 2.145 0 0 0-.497.048c-1.469.318-2.17 2.051-1.35 3.295 1.178 1.774 3.944.953 3.97-1.177.012-1.193-.98-2.143-2.123-2.166zM2.089 14.19a2.22 2.22 0 0 0-.427.052c-2.158.476-2.237 3.626-.106 4.182.53.145.582.145 1.111.013 1.191-.318 1.866-1.456 1.549-2.607-.278-1.02-1.144-1.664-2.127-1.64zm19.824.008c-.233.002-.477.058-.784.162-1.39.477-1.866 2.092-.98 3.336.557.794 1.96 1.058 2.82.516 1.416-.874 1.363-3.057-.093-3.746-.38-.186-.663-.271-.963-.268z" />
</svg>
<p className="font-medium mt-2">React Router</p>
</LinkedCard>
<LinkedCard target="_blank" href="https://ui.shadcn.com/docs/installation/tanstack">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
className="w-10 h-10"
fill="currentColor"
>
<path d="M6.93 13.688a.343.343 0 0 1 .468.132l.063.106c.48.851.98 1.66 1.5 2.426a35.65 35.65 0 0 0 2.074 2.742.345.345 0 0 1-.039.484l-.074.066c-2.543 2.223-4.191 2.665-4.953 1.333-.746-1.305-.477-3.672.808-7.11a.344.344 0 0 1 .153-.18ZM17.75 16.3a.34.34 0 0 1 .395.27l.02.1c.628 3.286.187 4.93-1.325 4.93-1.48 0-3.36-1.402-5.649-4.203a.327.327 0 0 1-.074-.222c0-.188.156-.34.344-.34h.121a32.984 32.984 0 0 0 2.809-.098c1.07-.086 2.191-.23 3.359-.437zm.871-6.977a.353.353 0 0 1 .445-.21l.102.034c3.262 1.11 4.504 2.332 3.719 3.664-.766 1.305-2.993 2.254-6.684 2.848a.362.362 0 0 1-.238-.047.343.343 0 0 1-.125-.476l.062-.106a34.07 34.07 0 0 0 1.367-2.523c.477-.989.93-2.051 1.352-3.184zM7.797 8.34a.362.362 0 0 1 .238.047.343.343 0 0 1 .125.476l-.062.106a34.088 34.088 0 0 0-1.367 2.523c-.477.988-.93 2.051-1.352 3.184a.353.353 0 0 1-.445.21l-.102-.034C1.57 13.742.328 12.52 1.113 11.188 1.88 9.883 4.106 8.934 7.797 8.34Zm5.281-3.984c2.543-2.223 4.192-2.664 4.953-1.332.746 1.304.477 3.671-.808 7.109a.344.344 0 0 1-.153.18.343.343 0 0 1-.468-.133l-.063-.106a34.64 34.64 0 0 0-1.5-2.426 35.65 35.65 0 0 0-2.074-2.742.345.345 0 0 1 .039-.484ZM7.285 2.274c1.48 0 3.364 1.402 5.649 4.203a.349.349 0 0 1 .078.218.348.348 0 0 1-.348.344l-.117-.004a34.584 34.584 0 0 0-2.809.102 35.54 35.54 0 0 0-3.363.437.343.343 0 0 1-.394-.273l-.02-.098c-.629-3.285-.188-4.93 1.324-4.93Zm2.871 5.812h3.688a.638.638 0 0 1 .55.316l1.848 3.22a.644.644 0 0 1 0 .628l-1.847 3.223a.638.638 0 0 1-.551.316h-3.688a.627.627 0 0 1-.547-.316L7.758 12.25a.644.644 0 0 1 0-.629L9.61 8.402a.627.627 0 0 1 .546-.316Zm3.23.793a.638.638 0 0 1 .552.316l1.39 2.426a.644.644 0 0 1 0 .629l-1.39 2.43a.638.638 0 0 1-.551.316h-2.774a.627.627 0 0 1-.546-.316l-1.395-2.43a.644.644 0 0 1 0-.629l1.395-2.426a.627.627 0 0 1 .546-.316Zm-.491.867h-1.79a.624.624 0 0 0-.546.316l-.899 1.56a.644.644 0 0 0 0 .628l.899 1.563a.632.632 0 0 0 .547.316h1.789a.632.632 0 0 0 .547-.316l.898-1.563a.644.644 0 0 0 0-.629l-.898-1.558a.624.624 0 0 0-.547-.317Zm-.477.828c.227 0 .438.121.547.317l.422.73a.625.625 0 0 1 0 .629l-.422.734a.627.627 0 0 1-.547.317h-.836a.632.632 0 0 1-.547-.317l-.422-.734a.625.625 0 0 1 0-.629l.422-.73a.632.632 0 0 1 .547-.317zm-.418.817a.548.548 0 0 0-.473.273.547.547 0 0 0 0 .547.544.544 0 0 0 .473.27.544.544 0 0 0 .473-.27.547.547 0 0 0 0-.547.548.548 0 0 0-.473-.273Zm-4.422.546h.98M18.98 7.75c.391-1.895.477-3.344.223-4.398-.148-.63-.422-1.137-.84-1.508-.441-.39-1-.582-1.625-.582-1.035 0-2.12.472-3.281 1.367a14.9 14.9 0 0 0-1.473 1.316 1.206 1.206 0 0 0-.136-.144c-1.446-1.285-2.66-2.082-3.7-2.39-.617-.184-1.195-.2-1.722-.024-.559.187-1.004.574-1.317 1.117-.515.894-.652 2.074-.46 3.527.078.59.214 1.235.402 1.934a1.119 1.119 0 0 0-.215.047C3.008 8.62 1.71 9.269.926 10.015c-.465.442-.77.938-.883 1.481-.113.578 0 1.156.312 1.7.516.894 1.465 1.597 2.817 2.155.543.223 1.156.426 1.844.61a1.023 1.023 0 0 0-.07.226c-.391 1.891-.477 3.344-.223 4.395.148.629.425 1.14.84 1.508.44.39 1 .582 1.625.582 1.035 0 2.12-.473 3.28-1.364.477-.37.973-.816 1.489-1.336a1.2 1.2 0 0 0 .195.227c1.446 1.285 2.66 2.082 3.7 2.39.617.184 1.195.2 1.722.024.559-.187 1.004-.574 1.317-1.117.515-.894.652-2.074.46-3.527a14.941 14.941 0 0 0-.425-2.012 1.225 1.225 0 0 0 .238-.047c1.828-.61 3.125-1.258 3.91-2.004.465-.441.77-.937.883-1.48.113-.578 0-1.157-.313-1.7-.515-.894-1.464-1.597-2.816-2.156a14.576 14.576 0 0 0-1.906-.625.865.865 0 0 0 .059-.195z" />
</svg>
<p className="font-medium mt-2">TanStack Start</p>
</LinkedCard>
<LinkedCard target="_blank" href="https://ui.shadcn.com/docs/installation/manual">
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
className="w-10 h-10"
fill="currentColor"
>
<title>React SPA</title>
<path d="M14.23 12.004a2.236 2.236 0 0 1-2.235 2.236 2.236 2.236 0 0 1-2.236-2.236 2.236 2.236 0 0 1 2.235-2.236 2.236 2.236 0 0 1 2.236 2.236zm2.648-10.69c-1.346 0-3.107.96-4.888 2.622-1.78-1.653-3.542-2.602-4.887-2.602-.41 0-.783.093-1.106.278-1.375.793-1.683 3.264-.973 6.365C1.98 8.917 0 10.42 0 12.004c0 1.59 1.99 3.097 5.043 4.03-.704 3.113-.39 5.588.988 6.38.32.187.69.275 1.102.275 1.345 0 3.107-.96 4.888-2.624 1.78 1.654 3.542 2.603 4.887 2.603.41 0 .783-.09 1.106-.275 1.374-.792 1.683-3.263.973-6.365C22.02 15.096 24 13.59 24 12.004c0-1.59-1.99-3.097-5.043-4.032.704-3.11.39-5.587-.988-6.38-.318-.184-.688-.277-1.092-.278zm-.005 1.09v.006c.225 0 .406.044.558.127.666.382.955 1.835.73 3.704-.054.46-.142.945-.25 1.44-.96-.236-2.006-.417-3.107-.534-.66-.905-1.345-1.727-2.035-2.447 1.592-1.48 3.087-2.292 4.105-2.295zm-9.77.02c1.012 0 2.514.808 4.11 2.28-.686.72-1.37 1.537-2.02 2.442-1.107.117-2.154.298-3.113.538-.112-.49-.195-.964-.254-1.42-.23-1.868.054-3.32.714-3.707.19-.09.4-.127.563-.132zm4.882 3.05c.455.468.91.992 1.36 1.564-.44-.02-.89-.034-1.345-.034-.46 0-.915.01-1.36.034.44-.572.895-1.096 1.345-1.565zM12 8.1c.74 0 1.477.034 2.202.093.406.582.802 1.203 1.183 1.86.372.64.71 1.29 1.018 1.946-.308.655-.646 1.31-1.013 1.95-.38.66-.773 1.288-1.18 1.87-.728.063-1.466.098-2.21.098-.74 0-1.477-.035-2.202-.093-.406-.582-.802-1.204-1.183-1.86-.372-.64-.71-1.29-1.018-1.946.303-.657.646-1.313 1.013-1.954.38-.66.773-1.286 1.18-1.868.728-.064 1.466-.098 2.21-.098zm-3.635.254c-.24.377-.48.763-.704 1.16-.225.39-.435.782-.635 1.174-.265-.656-.49-1.31-.676-1.947.64-.15 1.315-.283 2.015-.386zm7.26 0c.695.103 1.365.23 2.006.387-.18.632-.405 1.282-.66 1.933-.2-.39-.41-.783-.64-1.174-.225-.392-.465-.774-.705-1.146zm3.063.675c.484.15.944.317 1.375.498 1.732.74 2.852 1.708 2.852 2.476-.005.768-1.125 1.74-2.857 2.475-.42.18-.88.342-1.355.493-.28-.958-.646-1.956-1.1-2.98.45-1.017.81-2.01 1.085-2.964zm-13.395.004c.278.96.645 1.957 1.1 2.98-.45 1.017-.812 2.01-1.086 2.964-.484-.15-.944-.318-1.37-.5-1.732-.737-2.852-1.706-2.852-2.474 0-.768 1.12-1.742 2.852-2.476.42-.18.88-.342 1.356-.494zm11.678 4.28c.265.657.49 1.312.676 1.948-.64.157-1.316.29-2.016.39.24-.375.48-.762.705-1.158.225-.39.435-.788.636-1.18zm-9.945.02c.2.392.41.783.64 1.175.23.39.465.772.705 1.143-.695-.102-1.365-.23-2.006-.386.18-.63.406-1.282.66-1.933zM17.92 16.32c.112.493.2.968.254 1.423.23 1.868-.054 3.32-.714 3.708-.147.09-.338.128-.563.128-1.012 0-2.514-.807-4.11-2.28.686-.72 1.37-1.536 2.02-2.44 1.107-.118 2.154-.3 3.113-.54zm-11.83.01c.96.234 2.006.415 3.107.532.66.905 1.345 1.727 2.035 2.446-1.595 1.483-3.092 2.295-4.11 2.295-.22-.005-.406-.05-.553-.132-.666-.38-.955-1.834-.73-3.703.054-.46.142-.944.25-1.438zm4.56.64c.44.02.89.034 1.345.034.46 0 .915-.01 1.36-.034-.44.572-.895 1.095-1.345 1.565-.455-.47-.91-.993-1.36-1.565z" />
</svg>
<p className="font-medium mt-2">React SPA</p>
</LinkedCard>
</div>
<QuickstartStep number="1">
<TanStackBeta />
### Create the app
When you've finished setting up shadcn/ui, you can start exploring the components in the sidebar
Create a [Next.js project](https://ui.shadcn.com/docs/installation/next) with Tailwind CSS, import aliases, and shadcn/ui configured. Choose npm when prompted for a package manager.
```bash
npx shadcn@latest init --template next --name my-app --base radix
cd my-app
```
Already have a project? Run `npx shadcn@latest init` from its root if it does not have a `components.json` file.
</QuickstartStep>
<QuickstartStep number="2">
### Install the Supabase client
Add the [Next.js client](/library/docs/nextjs/client). The installer adds the client files and Supabase dependencies. If you already have a client, keep your existing setup and check that the block imports match its location.
```bash
npx shadcn@latest add https://supabase.com/library/r/supabase-client-nextjs.json
```
</QuickstartStep>
<QuickstartStep number="3">
### Configure Supabase
Create `.env.local` in the project root. Add your project URL and publishable key from the [Connect dialog](https://supabase.com/dashboard/project/_?showConnect=true).
```env
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-publishable-key
```
</QuickstartStep>
<QuickstartStep number="4">
### Add your first block
Install the password authentication block from the project root. This adds the authentication files and their UI dependencies.
```bash
npx shadcn@latest add https://supabase.com/library/r/password-based-auth-nextjs.json
```
Follow the [Next.js authentication setup](/library/docs/nextjs/password-based-auth#usage) to connect the routes, configure redirect URLs, and finish the email flow before testing sign-up.
</QuickstartStep>
<QuickstartStep number="5">
### Start developing
Run the development server and open the local URL printed in your terminal. Restart the server after changing environment variables.
```bash
npm run dev
```
</QuickstartStep>
</FrameworkQuickstartTab>
<FrameworkQuickstartTab value="react-router">
## React Router
<QuickstartStep number="1">
### Create the app
Create a [React Router project](https://ui.shadcn.com/docs/installation/react-router) with Tailwind CSS, import aliases, and shadcn/ui configured. Choose npm when prompted for a package manager.
```bash
npx shadcn@latest init --template react-router --name my-app --base radix
cd my-app
```
Already have a project? Run `npx shadcn@latest init` from its root if it does not have a `components.json` file.
</QuickstartStep>
<QuickstartStep number="2">
### Install the Supabase client
Add the [React Router client](/library/docs/react-router/client). The installer adds the client files and Supabase dependencies. If you already have a client, keep your existing setup and check that the block imports match its location.
```bash
npx shadcn@latest add https://supabase.com/library/r/supabase-client-react-router.json
```
</QuickstartStep>
<QuickstartStep number="3">
### Configure Supabase
Create `.env` in the project root. Add your project URL and publishable key from the [Connect dialog](https://supabase.com/dashboard/project/_?showConnect=true).
```env
VITE_SUPABASE_URL=your-project-url
VITE_SUPABASE_PUBLISHABLE_KEY=your-publishable-key
```
</QuickstartStep>
<QuickstartStep number="4">
### Add your first block
Install the password authentication block from the project root. This adds the authentication files and their UI dependencies.
```bash
npx shadcn@latest add https://supabase.com/library/r/password-based-auth-react-router.json
```
Follow the [React Router authentication setup](/library/docs/react-router/password-based-auth#usage) to connect the routes, configure redirect URLs, and finish the email flow before testing sign-up.
</QuickstartStep>
<QuickstartStep number="5">
### Start developing
Run the development server and open the local URL printed in your terminal. Restart the server after changing environment variables.
```bash
npm run dev
```
</QuickstartStep>
</FrameworkQuickstartTab>
<FrameworkQuickstartTab value="tanstack">
## TanStack Start
<QuickstartStep number="1">
### Create the app
Create a [TanStack Start project](https://ui.shadcn.com/docs/installation/tanstack) with Tailwind CSS, import aliases, and shadcn/ui configured. Choose npm when prompted for a package manager.
```bash
npx shadcn@latest init --template start --name my-app --base radix
cd my-app
```
Already have a project? Run `npx shadcn@latest init` from its root if it does not have a `components.json` file.
</QuickstartStep>
<QuickstartStep number="2">
### Install the Supabase client
Add the [TanStack Start client](/library/docs/tanstack/client). The installer adds the client files and Supabase dependencies. If you already have a client, keep your existing setup and check that the block imports match its location.
```bash
npx shadcn@latest add https://supabase.com/library/r/supabase-client-tanstack.json
```
</QuickstartStep>
<QuickstartStep number="3">
### Configure Supabase
Create `.env` in the project root. Add your project URL and publishable key from the [Connect dialog](https://supabase.com/dashboard/project/_?showConnect=true).
```env
VITE_SUPABASE_URL=your-project-url
VITE_SUPABASE_PUBLISHABLE_KEY=your-publishable-key
```
</QuickstartStep>
<QuickstartStep number="4">
### Add your first block
Install the password authentication block from the project root. This adds the authentication files and their UI dependencies.
```bash
npx shadcn@latest add https://supabase.com/library/r/password-based-auth-tanstack.json
```
Follow the [TanStack Start authentication setup](/library/docs/tanstack/password-based-auth#usage) to connect the routes, configure redirect URLs, and finish the email flow before testing sign-up.
</QuickstartStep>
<QuickstartStep number="5">
### Start developing
Run the development server and open the local URL printed in your terminal. Restart the server after changing environment variables.
```bash
npm run dev
```
</QuickstartStep>
Check the [TanStack Start guide](https://tanstack.com/start/latest/docs/framework/react/getting-started) for framework setup changes if the generated routes differ from the block examples.
</FrameworkQuickstartTab>
<FrameworkQuickstartTab value="react">
## React SPA
<QuickstartStep number="1">
### Create the app
Create a [React SPA project](https://ui.shadcn.com/docs/installation/vite) with Tailwind CSS, import aliases, and shadcn/ui configured. Choose npm when prompted for a package manager.
```bash
npx shadcn@latest init --template vite --name my-app --base radix
cd my-app
```
Already have a project? Run `npx shadcn@latest init` from its root if it does not have a `components.json` file.
</QuickstartStep>
<QuickstartStep number="2">
### Install the Supabase client
Add the [React SPA client](/library/docs/react/client). The installer adds the client files and Supabase dependencies. If you already have a client, keep your existing setup and check that the block imports match its location.
```bash
npx shadcn@latest add https://supabase.com/library/r/supabase-client-react.json
```
</QuickstartStep>
<QuickstartStep number="3">
### Configure Supabase
Create `.env.local` in the project root. Add your project URL and publishable key from the [Connect dialog](https://supabase.com/dashboard/project/_?showConnect=true).
```env
VITE_SUPABASE_URL=your-project-url
VITE_SUPABASE_PUBLISHABLE_KEY=your-publishable-key
```
</QuickstartStep>
<QuickstartStep number="4">
### Add your first block
Install the password authentication block from the project root. This adds the authentication files and their UI dependencies.
```bash
npx shadcn@latest add https://supabase.com/library/r/password-based-auth-react.json
```
Follow the [React SPA authentication setup](/library/docs/react/password-based-auth#usage) to connect the routes, configure redirect URLs, and finish the email flow before testing sign-up.
</QuickstartStep>
<QuickstartStep number="5">
### Start developing
Run the development server and open the local URL printed in your terminal. Restart the server after changing environment variables.
```bash
npm run dev
```
</QuickstartStep>
</FrameworkQuickstartTab>
<FrameworkQuickstartTab value="vue">
## Vue
<QuickstartStep number="1">
### Create the app
Create a [Vue project](https://www.shadcn-vue.com/docs/installation/vite) with Tailwind CSS, import aliases, and shadcn-vue configured. Choose npm when prompted for a package manager.
```bash
npx shadcn-vue@latest init --template vite --name my-app
cd my-app
```
Already have a project? Run `npx shadcn-vue@latest init` from its root if it does not have a `components.json` file.
</QuickstartStep>
<QuickstartStep number="2">
### Install the Supabase client
Add the [Vue client](/library/docs/vue/client). The installer adds the client files and Supabase dependencies. If you already have a client, keep your existing setup and check that the block imports match its location.
```bash
npx shadcn-vue@latest add https://supabase.com/library/r/supabase-client-vue.json
```
</QuickstartStep>
<QuickstartStep number="3">
### Configure Supabase
Create `.env.local` in the project root. Add your project URL and publishable key from the [Connect dialog](https://supabase.com/dashboard/project/_?showConnect=true).
```env
VITE_SUPABASE_URL=your-project-url
VITE_SUPABASE_PUBLISHABLE_KEY=your-publishable-key
```
</QuickstartStep>
<QuickstartStep number="4">
### Add your first block
Install the password authentication block from the project root. This adds the authentication files and their UI dependencies.
```bash
npx shadcn-vue@latest add https://supabase.com/library/r/password-based-auth-vue.json
```
Follow the [Vue authentication setup](/library/docs/vue/password-based-auth#usage) to connect the routes, configure redirect URLs, and finish the email flow before testing sign-up.
</QuickstartStep>
<QuickstartStep number="5">
### Start developing
Run the development server and open the local URL printed in your terminal. Restart the server after changing environment variables.
```bash
npm run dev
```
</QuickstartStep>
</FrameworkQuickstartTab>
<FrameworkQuickstartTab value="nuxtjs">
## Nuxt.js
<QuickstartStep number="1">
### Create the app
Create a [Nuxt.js project](https://www.shadcn-vue.com/docs/installation/nuxt) with Tailwind CSS, import aliases, and shadcn-vue configured. Choose npm when prompted for a package manager.
```bash
npx shadcn-vue@latest init --template nuxt --name my-app
cd my-app
```
Already have a project? Run `npx shadcn-vue@latest init` from its root if it does not have a `components.json` file.
</QuickstartStep>
<QuickstartStep number="2">
### Install the Supabase client
Add the [Nuxt.js client](/library/docs/nuxtjs/client). The installer adds the client files and Supabase dependencies. If you already have a client, keep your existing setup and check that the block imports match its location.
```bash
npx shadcn-vue@latest add https://supabase.com/library/r/supabase-client-nuxtjs.json
```
</QuickstartStep>
<QuickstartStep number="3">
### Configure Supabase
Create `.env` in the project root. Add your project URL and publishable key from the [Connect dialog](https://supabase.com/dashboard/project/_?showConnect=true).
```env
NUXT_PUBLIC_SUPABASE_URL=your-project-url
NUXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-publishable-key
```
</QuickstartStep>
<QuickstartStep number="4">
### Add your first block
Install the password authentication block from the project root. This adds the authentication files and their UI dependencies.
```bash
npx shadcn-vue@latest add https://supabase.com/library/r/password-based-auth-nuxtjs.json
```
Follow the [Nuxt.js authentication setup](/library/docs/nuxtjs/password-based-auth#usage) to connect the routes, configure redirect URLs, and finish the email flow before testing sign-up.
</QuickstartStep>
<QuickstartStep number="5">
### Start developing
Run the development server and open the local URL printed in your terminal. Restart the server after changing environment variables.
```bash
npm run dev
```
</QuickstartStep>
</FrameworkQuickstartTab>
</FrameworkQuickstart>
## Explore the library
Browse [blocks](/library) by category to find your next component. Each block page includes installation commands, source files, and framework-specific usage instructions.
## Troubleshooting
If you encounter issues, try deleting your existing `components.json` file and follow one of the guides to reset your setup.
Run install commands from the directory containing `components.json`. If an import cannot be resolved, check its aliases and the paths in your TypeScript configuration. For an existing project, use the setup guide linked in your framework tab to check Tailwind CSS and alias configuration before reinitializing shadcn/ui.
For issues and questions about specific frameworks, see the [FAQs](/library/docs/getting-started/faq)
For framework-specific questions, see the [FAQs](/library/docs/getting-started/faq).
@@ -1,6 +1,7 @@
---
title: MCP Server
description: Add a user-scoped MCP server to your product
preview: <BlockOverview name="mcp-server" showFiles />
---
Give embedded product agents and external clients such as Codex, Claude Code,
@@ -19,10 +20,6 @@ root even when the frontend uses `src/`.
In a frontend app, add `"supabase/functions/**"` to the app's `tsconfig.json` `exclude`
list, preserving existing entries. Check the function separately with Deno.
## Folder structure
<RegistryBlock itemName="mcp-server" />
## Configure the project
The function verifies access tokens itself, so disable the gateway JWT check:
@@ -1,15 +1,12 @@
---
title: Supabase Client Libraries
description: Supabase client for Next.js
preview: <BlockOverview name="supabase-client-nextjs" showFiles />
---
## Installation
<BlockItem name="supabase-client-nextjs" description="Supabase Client for Next.js" />
## Folder structure
<RegistryBlock itemName="supabase-client-nextjs" />
<BlockItem name="supabase-client-nextjs" />
## Usage
@@ -1,20 +1,15 @@
---
title: Current User Avatar
description: Supabase Auth-aware avatar
preview: |-
<BlockOverview name="current-user-avatar-nextjs" showFiles>
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
</BlockOverview>
---
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
## Installation
<BlockItem
name="current-user-avatar-nextjs"
description="Renders the avatar of the current user."
/>
## Folder structure
<RegistryBlock itemName="current-user-avatar-nextjs" />
<BlockItem name="current-user-avatar-nextjs" />
## Introduction
@@ -1,24 +1,19 @@
---
title: Dropzone (File Upload)
description: Displays a control for easier uploading of files directly to Supabase Storage
preview: |-
<BlockOverview name="dropzone-nextjs" showFiles>
<ComponentPreview
name="dropzone-demo"
description="A file upload dropzone that accepts dragged or selected files, previews images, shows validation errors, and uploads to Supabase Storage."
showCode={false}
/>
</BlockOverview>
---
<ComponentPreview
name="dropzone-demo"
description="An alert with an icon, title and description. The title says 'Heads up!' and the description is 'You can add components to your app using the cli.'."
showCode={false}
/>
## Installation
<BlockItem
name="dropzone-nextjs"
description="Displays a control for easier uploading of files directly to Supabase Storage"
/>
## Folder structure
<RegistryBlock itemName="dropzone-nextjs" />
<BlockItem name="dropzone-nextjs" />
## Introduction
@@ -1,23 +1,18 @@
---
title: OAuth Consent
description: OAuth 2.1 consent block for Next.js
preview: |-
<BlockOverview name="oauth-consent-nextjs" showFiles>
<BlockPreview name="oauth-consent" />
</BlockOverview>
---
<BlockPreview name="oauth-consent" />
## Installation
<BlockItem
name="oauth-consent-nextjs"
description="OAuth 2.1 consent screen and the /oauth/consent route"
/>
## Folder structure
<BlockItem name="oauth-consent-nextjs" />
This block includes the [Supabase client](/library/docs/nextjs/client). If you already have one installed, you can skip overwriting it.
<RegistryBlock itemName="oauth-consent-nextjs" />
## Usage
This block installs an OAuth 2.1 consent route at `/oauth/consent`. It is designed for an app that already has authentication: it does not install sign-in, sign-up, or callback routes.
@@ -1,23 +1,18 @@
---
title: Password-based Authentication
description: Password-based authentication block for Next.js
preview: |-
<BlockOverview name="password-based-auth-nextjs" showFiles>
<BlockPreview name="password-based-auth/auth/sign-up" />
</BlockOverview>
---
<BlockPreview name="password-based-auth/auth/sign-up" />
## Installation
<BlockItem
name="password-based-auth-nextjs"
description="All needed components for the password based auth flow"
/>
## Folder structure
<BlockItem name="password-based-auth-nextjs" />
This block includes the [Supabase client](/library/docs/nextjs/client). If you already have one installed, you can skip overwriting it.
<RegistryBlock itemName="password-based-auth-nextjs" />
## Usage
Once you install the block in your Next.js project, you'll get all the necessary pages and components to set up a password-based authentication flow.
@@ -1,20 +1,15 @@
---
title: Realtime Avatar Stack
description: Avatar stack in realtime
preview: |-
<BlockOverview name="realtime-avatar-stack-nextjs" showFiles>
<BlockPreview name="realtime-avatar-stack-demo" />
</BlockOverview>
---
<BlockPreview name="realtime-avatar-stack-demo" />
## Installation
<BlockItem
name="realtime-avatar-stack-nextjs"
description="Renders a stack of avatars which are connected via Supabase Realtime"
/>
## Folder structure
<RegistryBlock itemName="realtime-avatar-stack-nextjs" />
<BlockItem name="realtime-avatar-stack-nextjs" />
## Usage
@@ -1,20 +1,15 @@
---
title: Realtime Chat
description: Real-time chat component for collaborative applications
preview: |-
<BlockOverview name="realtime-chat-nextjs" showFiles>
<DualRealtimeChat />
</BlockOverview>
---
<DualRealtimeChat />
## Installation
<BlockItem
name="realtime-chat-nextjs"
description="Renders a real-time chat interface for users in a shared room."
/>
## Folder structure
<RegistryBlock itemName="realtime-chat-nextjs" />
<BlockItem name="realtime-chat-nextjs" />
## Introduction
@@ -1,23 +1,18 @@
---
title: Realtime Cursor
description: Real-time cursor sharing for collaborative applications
preview: |-
<BlockOverview name="realtime-cursor-nextjs" showFiles>
<div className="flex flex-row -space-x-px w-full">
<BlockPreview name="realtime-cursor-demo" isPair />
<BlockPreview name="realtime-cursor-demo" isPair />
</div>
</BlockOverview>
---
<div className="flex flex-row -space-x-px w-full">
<BlockPreview name="realtime-cursor-demo" isPair />
<BlockPreview name="realtime-cursor-demo" isPair />
</div>
## Installation
<BlockItem
name="realtime-cursor-nextjs"
description="Renders realtime cursors from other users in a room."
/>
## Folder structure
<RegistryBlock itemName="realtime-cursor-nextjs" />
<BlockItem name="realtime-cursor-nextjs" />
## Introduction
@@ -1,20 +1,15 @@
---
title: Realtime Flow
description: Real-time flow diagram editor for collaborative applications
preview: |-
<BlockOverview name="realtime-flow-nextjs" showFiles>
<DualRealtimeFlow />
</BlockOverview>
---
<DualRealtimeFlow />
## Installation
<BlockItem
name="realtime-flow-nextjs"
description="Real-time flow diagram editor for collaborative applications."
/>
## Folder structure
<RegistryBlock itemName="realtime-flow-nextjs" />
<BlockItem name="realtime-flow-nextjs" />
## Introduction
@@ -115,13 +110,14 @@ If you need programmatic access to nodes and edges (e.g. adding nodes, custom no
```tsx
import {
ReactFlow,
ReactFlowProvider,
Background,
Controls,
type Node,
ReactFlow,
ReactFlowProvider,
type Edge,
type Node,
} from '@xyflow/react'
import { useRealtimeFlow } from '@/hooks/use-realtime-flow'
const initialNodes: Node[] = [
@@ -1,20 +1,15 @@
---
title: Realtime Monaco
description: Real-time Monaco editor for collaborative applications
preview: |-
<BlockOverview name="realtime-monaco-nextjs" showFiles>
<DualRealtimeMonaco />
</BlockOverview>
---
<DualRealtimeMonaco />
## Installation
<BlockItem
name="realtime-monaco-nextjs"
description="Real-time Monaco editor for collaborative applications."
/>
## Folder structure
<RegistryBlock itemName="realtime-monaco-nextjs" />
<BlockItem name="realtime-monaco-nextjs" />
## Introduction
@@ -1,10 +1,12 @@
---
title: Social Authentication
description: Social authentication block for Next.js
preview: |-
<BlockOverview name="social-auth-nextjs" showFiles>
<BlockPreview name="social-auth/auth/login" />
</BlockOverview>
---
<BlockPreview name="social-auth/auth/login" />
<Callout className="mt-4">
The block is using Github provider by default, but can be easily switched by changing a single
parameter.
@@ -12,14 +14,10 @@ description: Social authentication block for Next.js
## Installation
<BlockItem name="social-auth-nextjs" description="All needed components for the social auth flow" />
## Folder structure
<BlockItem name="social-auth-nextjs" />
This block includes the [Supabase client](/library/docs/nextjs/client). If you already have one installed, you can skip overwriting it.
<RegistryBlock itemName="social-auth-nextjs" />
## Usage
Once you install the block in your Next.js project, you'll get all the necessary pages and components to set up a social authentication flow.
@@ -1,6 +1,7 @@
---
title: TanStack DB
description: Local-first, reactive collections backed by Supabase
description: 'Local-first, reactive collections backed by Supabase'
preview: <BlockOverview name="tanstack-db-nextjs" />
---
A [TanStack/db](https://tanstack.com/db/latest) collection backed by [Supabase](https://supabase.com/). It wires up Supabase PostgREST queries, mutations, and real-time subscriptions so you get a reactive state that stays in sync with your Postgres database.
@@ -1,19 +1,14 @@
---
title: Supabase Client Libraries
description: Supabase client for Nuxt.js
preview: <BlockOverview name="supabase-client-nuxtjs" showFiles />
---
## Installation
<BlockItem
name="supabase-client-nuxtjs"
description="Supabase Client for Nuxt.js"
framework="vue"
/>
If your project already has a Supabase client, reuse it instead of running this command. Otherwise, install the client and configure the environment variables below.
## Folder structure
<RegistryBlock itemName="supabase-client-nuxtjs" />
<BlockItem name="supabase-client-nuxtjs" framework="vue" />
## Usage
@@ -1,23 +1,25 @@
---
title: Current User Avatar
description: Supabase Auth-aware avatar
preview: |-
<BlockOverview name="current-user-avatar-nuxtjs" showFiles>
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
</BlockOverview>
---
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
## Installation
<BlockItem
name="current-user-avatar-nuxtjs"
description="Renders the avatar of the current user."
framework="vue"
/>
### Set up the Supabase client
## Folder structure
This block requires a Supabase client for Nuxt.js. If your project already has one, reuse it and update the block's imports to match your client. Otherwise, install the client:
This block assumes that you have already installed a Supabase client for Vue from the previous step.
<BlockItem name="supabase-client-nuxtjs" framework="vue" showOpenInV0={false} />
<RegistryBlock itemName="current-user-avatar-nuxtjs" />
Configure the environment variables described in the [Nuxt.js client guide](/library/docs/nuxtjs/client#getting-started) before using the block.
### Install the block
<BlockItem name="current-user-avatar-nuxtjs" framework="vue" />
## Introduction
@@ -1,27 +1,29 @@
---
title: Dropzone (File Upload)
description: Displays a control for easier uploading of files directly to Supabase Storage
preview: |-
<BlockOverview name="dropzone-nuxtjs" showFiles>
<ComponentPreview
name="dropzone-demo"
description="A file upload dropzone component that allows users to drag-and-drop files or select them manually. It supports multiple files, displays upload progress, previews images, and shows validation errors."
showCode={false}
/>
</BlockOverview>
---
<ComponentPreview
name="dropzone-demo"
description="A file upload dropzone component that allows users to drag-and-drop files or select them manually. It supports multiple files, displays upload progress, previews images, and shows validation errors."
showCode={false}
/>
## Installation
<BlockItem
name="dropzone-nuxtjs"
description="Displays a control for easier uploading of files directly to Supabase Storage"
framework="vue"
/>
### Set up the Supabase client
## Folder structure
This block requires a Supabase client for Nuxt.js. If your project already has one, reuse it and update the block's imports to match your client. Otherwise, install the client:
This block assumes that you have already installed a Supabase client for Nuxt from the previous step.
<BlockItem name="supabase-client-nuxtjs" framework="vue" showOpenInV0={false} />
<RegistryBlock itemName="dropzone-nuxtjs" />
Configure the environment variables described in the [Nuxt.js client guide](/library/docs/nuxtjs/client#getting-started) before using the block.
### Install the block
<BlockItem name="dropzone-nuxtjs" framework="vue" />
## Introduction
@@ -1,27 +1,29 @@
---
title: Password-based Authentication
description: Password-based authentication block for Nuxt.js
preview: |-
<BlockOverview name="password-based-auth-nuxtjs" showFiles>
<BlockPreview name="password-based-auth/auth/sign-up" />
</BlockOverview>
---
<BlockPreview name="password-based-auth/auth/sign-up" />
## Installation
<BlockItem
name="password-based-auth-nuxtjs"
description="All needed components for the password based auth flow"
framework="vue"
/>
### Set up the Supabase client
## Folder structure
This block requires a Supabase client for Nuxt.js. If your project already has one, reuse it and update the block's imports to match your client. Otherwise, install the client:
This block assumes that you have already installed a Supabase client for Nuxt from the previous step.
<BlockItem name="supabase-client-nuxtjs" framework="vue" showOpenInV0={false} />
<RegistryBlock itemName="password-based-auth-nuxtjs" />
Configure the environment variables described in the [Nuxt.js client guide](/library/docs/nuxtjs/client#getting-started) before using the block.
### Install the block
<BlockItem name="password-based-auth-nuxtjs" framework="vue" />
## Usage
Once you install the block in your Next.js project, you'll get all the necessary pages and components to set up a password-based authentication flow.
Once you install the block in your Nuxt.js project, you'll get all the necessary pages and components to set up a password-based authentication flow.
### Getting started
@@ -1,23 +1,25 @@
---
title: Realtime Avatar Stack
description: Avatar stack in realtime
preview: |-
<BlockOverview name="realtime-avatar-stack-nuxtjs" showFiles>
<BlockPreview name="realtime-avatar-stack-demo" />
</BlockOverview>
---
<BlockPreview name="realtime-avatar-stack-demo" />
## Installation
<BlockItem
name="realtime-avatar-stack-nuxtjs"
description="Renders a stack of avatars which are connected via Supabase Realtime"
framework="vue"
/>
### Set up the Supabase client
## Folder structure
This block requires a Supabase client for Nuxt.js. If your project already has one, reuse it and update the block's imports to match your client. Otherwise, install the client:
This block assumes that you have already installed a Supabase client for Nuxt from the previous step.
<BlockItem name="supabase-client-nuxtjs" framework="vue" showOpenInV0={false} />
<RegistryBlock itemName="realtime-avatar-stack-nuxtjs" />
Configure the environment variables described in the [Nuxt.js client guide](/library/docs/nuxtjs/client#getting-started) before using the block.
### Install the block
<BlockItem name="realtime-avatar-stack-nuxtjs" framework="vue" />
## Usage
@@ -1,23 +1,25 @@
---
title: Realtime Chat
description: Real-time chat component for collaborative applications
preview: |-
<BlockOverview name="realtime-chat-nuxtjs" showFiles>
<DualRealtimeChat />
</BlockOverview>
---
<DualRealtimeChat />
## Installation
<BlockItem
name="realtime-chat-nuxtjs"
description="Renders a real-time chat interface for users in a shared room."
framework="vue"
/>
### Set up the Supabase client
## Folder structure
This block requires a Supabase client for Nuxt.js. If your project already has one, reuse it and update the block's imports to match your client. Otherwise, install the client:
This block assumes that you have already installed a Supabase client for Vue from the previous step.
<BlockItem name="supabase-client-nuxtjs" framework="vue" showOpenInV0={false} />
<RegistryBlock itemName="realtime-chat-nuxtjs" />
Configure the environment variables described in the [Nuxt.js client guide](/library/docs/nuxtjs/client#getting-started) before using the block.
### Install the block
<BlockItem name="realtime-chat-nuxtjs" framework="vue" />
## Introduction
@@ -1,26 +1,28 @@
---
title: Realtime Cursor
description: Real-time cursor sharing for collaborative applications
preview: |-
<BlockOverview name="realtime-cursor-nuxtjs" showFiles>
<div className="flex flex-row -space-x-px w-full">
<BlockPreview name="realtime-cursor-demo" isPair />
<BlockPreview name="realtime-cursor-demo" isPair />
</div>
</BlockOverview>
---
<div className="flex flex-row -space-x-px w-full">
<BlockPreview name="realtime-cursor-demo" isPair />
<BlockPreview name="realtime-cursor-demo" isPair />
</div>
## Installation
<BlockItem
name="realtime-cursor-nuxtjs"
description="Renders realtime cursors from other users in a room."
framework="vue"
/>
### Set up the Supabase client
## Folder structure
This block requires a Supabase client for Nuxt.js. If your project already has one, reuse it and update the block's imports to match your client. Otherwise, install the client:
This block assumes that you have already installed a Supabase client for Nuxt from the previous step.
<BlockItem name="supabase-client-nuxtjs" framework="vue" showOpenInV0={false} />
<RegistryBlock itemName="realtime-cursor-nuxtjs" />
Configure the environment variables described in the [Nuxt.js client guide](/library/docs/nuxtjs/client#getting-started) before using the block.
### Install the block
<BlockItem name="realtime-cursor-nuxtjs" framework="vue" />
## Introduction
@@ -1,10 +1,12 @@
---
title: Social Authentication
description: Social authentication block for Nuxt.js
preview: |-
<BlockOverview name="social-auth-nuxtjs" showFiles>
<BlockPreview name="social-auth/auth/login" />
</BlockOverview>
---
<BlockPreview name="social-auth/auth/login" />
<Callout className="mt-4">
The block is using Github provider by default, but can be easily switched by changing a single
parameter.
@@ -12,17 +14,17 @@ description: Social authentication block for Nuxt.js
## Installation
<BlockItem
name="social-auth-nuxtjs"
description="All needed components for the social auth flow"
framework="vue"
/>
### Set up the Supabase client
## Folder structure
This block requires a Supabase client for Nuxt.js. If your project already has one, reuse it and update the block's imports to match your client. Otherwise, install the client:
This block assumes that you have already installed a Supabase client for Nuxt from the previous step.
<BlockItem name="supabase-client-nuxtjs" framework="vue" showOpenInV0={false} />
<RegistryBlock itemName="social-auth-nuxtjs" />
Configure the environment variables described in the [Nuxt.js client guide](/library/docs/nuxtjs/client#getting-started) before using the block.
### Install the block
<BlockItem name="social-auth-nuxtjs" framework="vue" />
## Usage
@@ -1,26 +1,21 @@
---
title: Platform Kit
description: The easiest way to build platforms on top of Supabase
preview: |-
<BlockOverview name="platform-kit-nextjs" showFiles>
<video width="100%" muted autoPlay controls>
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/marketing/blog/platform-kit/platform-kit.mp4"
type="video/mp4"
/>
Your browser does not support the video tag.
</video>
</BlockOverview>
---
<video width="100%" muted autoPlay controls>
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/marketing/blog/platform-kit/platform-kit.mp4"
type="video/mp4"
/>
Your browser does not support the video tag.
</video>
## Installation
<BlockItem
name="platform-kit-nextjs"
description="Embeddable dialog for managing Supabase projects via the Management API."
/>
## Folder structure
<RegistryBlock itemName="platform-kit-nextjs" />
<BlockItem name="platform-kit-nextjs" />
## Introduction
@@ -68,7 +63,7 @@ export default function Example() {
}
```
## Quick Start
## Quickstart
1. **Set up environment variables:** in your `.env.local` file:
@@ -1,15 +1,12 @@
---
title: Supabase Client Libraries
description: Supabase client for React Router
preview: <BlockOverview name="supabase-client-react-router" showFiles />
---
## Installation
<BlockItem name="supabase-client-react-router" description="Supabase Client for React Router" />
## Folder structure
<RegistryBlock itemName="supabase-client-react-router" />
<BlockItem name="supabase-client-react-router" />
## Usage
@@ -1,20 +1,15 @@
---
title: Current User Avatar
description: Supabase Auth-aware avatar
preview: |-
<BlockOverview name="current-user-avatar-react-router" showFiles>
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
</BlockOverview>
---
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
## Installation
<BlockItem
name="current-user-avatar-react-router"
description="Renders the avatar of the current user."
/>
## Folder structure
<RegistryBlock itemName="current-user-avatar-react-router" />
<BlockItem name="current-user-avatar-react-router" />
## Introduction
@@ -1,24 +1,19 @@
---
title: Dropzone (File Upload)
description: Displays a control for easier uploading of files directly to Supabase Storage
preview: |-
<BlockOverview name="dropzone-react-router" showFiles>
<ComponentPreview
name="dropzone-demo"
description="A file upload dropzone that accepts dragged or selected files, previews images, shows validation errors, and uploads to Supabase Storage."
showCode={false}
/>
</BlockOverview>
---
<ComponentPreview
name="dropzone-demo"
description="An alert with an icon, title and description. The title says 'Heads up!' and the description is 'You can add components to your app using the cli.'."
showCode={false}
/>
## Installation
<BlockItem
name="dropzone-react-router"
description="Displays a control for easier uploading of files directly to Supabase Storage"
/>
## Folder structure
<RegistryBlock itemName="dropzone-react-router" />
<BlockItem name="dropzone-react-router" />
## Introduction
@@ -1,23 +1,18 @@
---
title: OAuth Consent
description: OAuth 2.1 consent block for React Router
preview: |-
<BlockOverview name="oauth-consent-react-router" showFiles>
<BlockPreview name="oauth-consent" />
</BlockOverview>
---
<BlockPreview name="oauth-consent" />
## Installation
<BlockItem
name="oauth-consent-react-router"
description="OAuth 2.1 consent screen and the /oauth/consent route"
/>
## Folder structure
<BlockItem name="oauth-consent-react-router" />
This block includes the [Supabase client](/library/docs/react-router/client). If you already have one installed, you can skip overwriting it.
<RegistryBlock itemName="oauth-consent-react-router" />
## Usage
This block installs an OAuth 2.1 consent route at `/oauth/consent`. It is designed for an app that already has authentication: it does not install sign-in, sign-up, or callback routes.
@@ -1,23 +1,18 @@
---
title: Password-based Authentication
description: Password-based authentication block for React Router
preview: |-
<BlockOverview name="password-based-auth-react-router" showFiles>
<BlockPreview name="password-based-auth/auth/sign-up" />
</BlockOverview>
---
<BlockPreview name="password-based-auth/auth/sign-up" />
## Installation
<BlockItem
name="password-based-auth-react-router"
description="All needed components for the password based auth flow"
/>
## Folder structure
<BlockItem name="password-based-auth-react-router" />
This block includes the [Supabase client](/library/docs/react-router/client). If you already have one installed, you can skip overwriting it.
<RegistryBlock itemName="password-based-auth-react-router" />
## Usage
Once you install the block in your React Router project, you'll get all the necessary pages and components to set up a password-based authentication flow.
@@ -1,20 +1,15 @@
---
title: Realtime Avatar Stack
description: Avatar stack in realtime
preview: |-
<BlockOverview name="realtime-avatar-stack-react-router" showFiles>
<BlockPreview name="realtime-avatar-stack-demo" />
</BlockOverview>
---
<BlockPreview name="realtime-avatar-stack-demo" />
## Installation
<BlockItem
name="realtime-avatar-stack-react-router"
description="Renders a stack of avatars which are connected via Supabase Realtime"
/>
## Folder structure
<RegistryBlock itemName="realtime-avatar-stack-react-router" />
<BlockItem name="realtime-avatar-stack-react-router" />
## Usage
@@ -1,20 +1,15 @@
---
title: Realtime Chat
description: Real-time chat component for collaborative applications
preview: |-
<BlockOverview name="realtime-chat-react-router" showFiles>
<DualRealtimeChat />
</BlockOverview>
---
<DualRealtimeChat />
## Installation
<BlockItem
name="realtime-chat-react-router"
description="Renders a real-time chat interface for users in a shared room."
/>
## Folder structure
<RegistryBlock itemName="realtime-chat-react-router" />
<BlockItem name="realtime-chat-react-router" />
## Introduction
@@ -1,23 +1,18 @@
---
title: Realtime Cursor
description: Real-time cursor sharing for collaborative applications
preview: |-
<BlockOverview name="realtime-cursor-react-router" showFiles>
<div className="flex flex-row -space-x-px w-full">
<BlockPreview name="realtime-cursor-demo" isPair />
<BlockPreview name="realtime-cursor-demo" isPair />
</div>
</BlockOverview>
---
<div className="flex flex-row -space-x-px w-full">
<BlockPreview name="realtime-cursor-demo" isPair />
<BlockPreview name="realtime-cursor-demo" isPair />
</div>
## Installation
<BlockItem
name="realtime-cursor-react-router"
description="Renders realtime cursors from other users in a room."
/>
## Folder structure
<RegistryBlock itemName="realtime-cursor-react-router" />
<BlockItem name="realtime-cursor-react-router" />
## Introduction
@@ -1,20 +1,15 @@
---
title: Realtime Flow
description: Real-time flow diagram editor for collaborative applications
preview: |-
<BlockOverview name="realtime-flow-react-router" showFiles>
<DualRealtimeFlow />
</BlockOverview>
---
<DualRealtimeFlow />
## Installation
<BlockItem
name="realtime-flow-react-router"
description="Real-time flow diagram editor for collaborative applications."
/>
## Folder structure
<RegistryBlock itemName="realtime-flow-react-router" />
<BlockItem name="realtime-flow-react-router" />
## Introduction
@@ -115,13 +110,14 @@ If you need programmatic access to nodes and edges (e.g. adding nodes, custom no
```tsx
import {
ReactFlow,
ReactFlowProvider,
Background,
Controls,
type Node,
ReactFlow,
ReactFlowProvider,
type Edge,
type Node,
} from '@xyflow/react'
import { useRealtimeFlow } from '@/hooks/use-realtime-flow'
const initialNodes: Node[] = [
@@ -1,20 +1,15 @@
---
title: Realtime Monaco
description: Real-time Monaco editor for collaborative applications
preview: |-
<BlockOverview name="realtime-monaco-react-router" showFiles>
<DualRealtimeMonaco />
</BlockOverview>
---
<DualRealtimeMonaco />
## Installation
<BlockItem
name="realtime-monaco-react-router"
description="Real-time Monaco editor for collaborative applications."
/>
## Folder structure
<RegistryBlock itemName="realtime-monaco-react-router" />
<BlockItem name="realtime-monaco-react-router" />
## Introduction
@@ -1,10 +1,12 @@
---
title: Social Authentication
description: Social authentication block for React Router
preview: |-
<BlockOverview name="social-auth-react-router" showFiles>
<BlockPreview name="social-auth/auth/login" />
</BlockOverview>
---
<BlockPreview name="social-auth/auth/login" />
<Callout className="mt-4">
The block is using GitHub provider by default, but can be easily switched by changing a single
parameter.
@@ -12,17 +14,10 @@ description: Social authentication block for React Router
## Installation
<BlockItem
name="social-auth-react-router"
description="All needed components for the social auth flow"
/>
## Folder structure
<BlockItem name="social-auth-react-router" />
This block includes the [Supabase client](/library/docs/react-router/client). If you already have one installed, you can skip overwriting it.
<RegistryBlock itemName="social-auth-react-router" />
## Usage
Once you install the block in your React Router project, you'll get all the necessary pages and components to set up a social authentication flow.
@@ -1,15 +1,12 @@
---
title: Supabase Client Libraries
description: Supabase client for React Single Page Applications
preview: <BlockOverview name="supabase-client-react" showFiles />
---
## Installation
<BlockItem name="supabase-client-react" description="Supabase Client for React SPA" />
## Folder structure
<RegistryBlock itemName="supabase-client-react" />
<BlockItem name="supabase-client-react" />
## Usage
@@ -1,17 +1,15 @@
---
title: Current User Avatar
description: Supabase Auth-aware avatar
preview: |-
<BlockOverview name="current-user-avatar-react" showFiles>
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
</BlockOverview>
---
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
## Installation
<BlockItem name="current-user-avatar-react" description="Renders the avatar of the current user." />
## Folder structure
<RegistryBlock itemName="current-user-avatar-react" />
<BlockItem name="current-user-avatar-react" />
## Introduction
@@ -1,24 +1,19 @@
---
title: Dropzone (File Upload)
description: Displays a control for easier uploading of files directly to Supabase Storage
preview: |-
<BlockOverview name="dropzone-react" showFiles>
<ComponentPreview
name="dropzone-demo"
description="A file upload dropzone that accepts dragged or selected files, previews images, shows validation errors, and uploads to Supabase Storage."
showCode={false}
/>
</BlockOverview>
---
<ComponentPreview
name="dropzone-demo"
description="An alert with an icon, title and description. The title says 'Heads up!' and the description is 'You can add components to your app using the cli.'."
showCode={false}
/>
## Installation
<BlockItem
name="dropzone-react"
description="Displays a control for easier uploading of files directly to Supabase Storage"
/>
## Folder structure
<RegistryBlock itemName="dropzone-react" />
<BlockItem name="dropzone-react" />
## Introduction
@@ -1,20 +1,15 @@
---
title: Infinite Query Hook
description: React hook for infinite lists, fetching data from Supabase.
description: 'React hook for infinite lists, fetching data from Supabase.'
preview: |-
<BlockOverview name="infinite-query-hook" showFiles>
<BlockPreview name="infinite-list-demo" />
</BlockOverview>
---
<BlockPreview name="infinite-list-demo" />
## Installation
<BlockItem
name="infinite-query-hook"
description="Installs the Infinite List component and necessary Supabase client setup."
/>
## Folder structure
<RegistryBlock itemName="infinite-query-hook" />
<BlockItem name="infinite-query-hook" />
## Introduction
@@ -23,7 +18,7 @@ The hook is fully typed, provided you have generated and setup your database typ
## Adding types
Before using this hook, we **highly** recommend you setup database types in your project. This will make the hook fully-typesafe. More info about generating Typescript types from database schema [here](https://supabase.com/docs/guides/api/rest/generating-types)
Before using this hook, we **highly** recommend you setup database types in your project. This will make the hook fully-typesafe. More info about generating TypeScript types from database schema [here](https://supabase.com/docs/guides/api/rest/generating-types).
## Props
@@ -285,7 +280,7 @@ export const InfiniteListDemo = () => {
## Further reading
- [Generating Typescript types from the database](https://supabase.com/docs/reference/javascript/typescript-support)
- [Generating TypeScript types from the database](https://supabase.com/docs/reference/javascript/typescript-support)
- [Supabase Database API](https://supabase.com/docs/reference/javascript/select)
- [Supabase Pagination](https://supabase.com/docs/reference/javascript/select#pagination)
- [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API)
@@ -1,23 +1,18 @@
---
title: OAuth Consent
description: OAuth 2.1 consent block for React single-page applications
preview: |-
<BlockOverview name="oauth-consent-react" showFiles>
<BlockPreview name="oauth-consent" />
</BlockOverview>
---
<BlockPreview name="oauth-consent" />
## Installation
<BlockItem
name="oauth-consent-react"
description="OAuth 2.1 consent component for an existing authenticated application"
/>
## Folder structure
<BlockItem name="oauth-consent-react" />
This block includes the [Supabase client](/library/docs/react/client). If you already have one installed, you can skip overwriting it.
<RegistryBlock itemName="oauth-consent-react" />
## Usage
This block provides an OAuth 2.1 consent component for an app that already has authentication. It does not install sign-in, sign-up, callback, or router-specific routes.
@@ -1,23 +1,18 @@
---
title: Password-based Authentication
description: Password-based authentication block for React Single Page Applications
preview: |-
<BlockOverview name="password-based-auth-react" showFiles>
<BlockPreview name="password-based-auth/auth/sign-up" />
</BlockOverview>
---
<BlockPreview name="password-based-auth/auth/sign-up" />
## Installation
<BlockItem
name="password-based-auth-react"
description="All needed components for the password based auth flow"
/>
## Folder structure
<BlockItem name="password-based-auth-react" />
This block includes the [Supabase client](/library/docs/react/client). If you already have one installed, you can skip overwriting it.
<RegistryBlock itemName="password-based-auth-react" />
## Usage
Once you install the block in your React project, you'll get all the necessary pages and components to set up a password-based authentication flow.
@@ -1,20 +1,15 @@
---
title: Realtime Avatar Stack
description: Avatar stack in realtime
preview: |-
<BlockOverview name="realtime-avatar-stack-react" showFiles>
<BlockPreview name="realtime-avatar-stack-demo" />
</BlockOverview>
---
<BlockPreview name="realtime-avatar-stack-demo" />
## Installation
<BlockItem
name="realtime-avatar-stack-react"
description="Renders a stack of avatars which are connected via Supabase Realtime"
/>
## Folder structure
<RegistryBlock itemName="realtime-avatar-stack-react" />
<BlockItem name="realtime-avatar-stack-react" />
## Usage
@@ -1,20 +1,15 @@
---
title: Realtime Chat
description: Real-time chat component for collaborative applications
preview: |-
<BlockOverview name="realtime-chat-react" showFiles>
<DualRealtimeChat />
</BlockOverview>
---
<DualRealtimeChat />
## Installation
<BlockItem
name="realtime-chat-react"
description="Renders a real-time chat interface for users in a shared room."
/>
## Folder structure
<RegistryBlock itemName="realtime-chat-react" />
<BlockItem name="realtime-chat-react" />
## Introduction
@@ -1,23 +1,18 @@
---
title: Realtime Cursor
description: Real-time cursor sharing for collaborative applications
preview: |-
<BlockOverview name="realtime-cursor-react" showFiles>
<div className="flex flex-row -space-x-px w-full">
<BlockPreview name="realtime-cursor-demo" isPair />
<BlockPreview name="realtime-cursor-demo" isPair />
</div>
</BlockOverview>
---
<div className="flex flex-row -space-x-px w-full">
<BlockPreview name="realtime-cursor-demo" isPair />
<BlockPreview name="realtime-cursor-demo" isPair />
</div>
## Installation
<BlockItem
name="realtime-cursor-react"
description="Renders realtime cursors from other users in a room."
/>
## Folder structure
<RegistryBlock itemName="realtime-cursor-react" />
<BlockItem name="realtime-cursor-react" />
## Introduction
@@ -1,20 +1,15 @@
---
title: Realtime Flow
description: Real-time flow diagram editor for collaborative applications
preview: |-
<BlockOverview name="realtime-flow-react" showFiles>
<DualRealtimeFlow />
</BlockOverview>
---
<DualRealtimeFlow />
## Installation
<BlockItem
name="realtime-flow-react"
description="Real-time flow diagram editor for collaborative applications."
/>
## Folder structure
<RegistryBlock itemName="realtime-flow-react" />
<BlockItem name="realtime-flow-react" />
## Introduction
@@ -115,13 +110,14 @@ If you need programmatic access to nodes and edges (e.g. adding nodes, custom no
```tsx
import {
ReactFlow,
ReactFlowProvider,
Background,
Controls,
type Node,
ReactFlow,
ReactFlowProvider,
type Edge,
type Node,
} from '@xyflow/react'
import { useRealtimeFlow } from '@/hooks/use-realtime-flow'
const initialNodes: Node[] = [
@@ -1,20 +1,15 @@
---
title: Realtime Monaco
description: Real-time Monaco editor for collaborative applications
preview: |-
<BlockOverview name="realtime-monaco-react" showFiles>
<DualRealtimeMonaco />
</BlockOverview>
---
<DualRealtimeMonaco />
## Installation
<BlockItem
name="realtime-monaco-react"
description="Real-time Monaco editor for collaborative applications."
/>
## Folder structure
<RegistryBlock itemName="realtime-monaco-react" />
<BlockItem name="realtime-monaco-react" />
## Introduction
@@ -1,10 +1,12 @@
---
title: Social Authentication
description: Social authentication block for React Single Page Applications
preview: |-
<BlockOverview name="social-auth-react" showFiles>
<BlockPreview name="social-auth/auth/login" />
</BlockOverview>
---
<BlockPreview name="social-auth/auth/login" />
<Callout className="mt-4">
The block is using GitHub provider by default, but can be easily switched by changing a single
parameter.
@@ -12,14 +14,10 @@ description: Social authentication block for React Single Page Applications
## Installation
<BlockItem name="social-auth-react" description="All needed components for the social auth flow" />
## Folder structure
<BlockItem name="social-auth-react" />
This block includes the [Supabase client](/library/docs/react/client). If you already have one installed, you can skip overwriting it.
<RegistryBlock itemName="social-auth-react" />
## Usage
Once you install the block in your React project, you'll get all the necessary pages and components to set up a social authentication flow.
@@ -3,6 +3,12 @@ title: AI Chat App
description: >-
A Next.js chat app with streaming responses, authentication, and saved
conversations
preview: |-
<BlockOverview name="ai-chat-app">
<div className="library-component-preview h-80 overflow-hidden border md:h-96">
<CatalogPreview kind="chat" />
</div>
</BlockOverview>
---
The [Supabase Community AI chatbot template](https://github.com/supabase-community/vercel-ai-chatbot) includes a chat interface, streaming responses, Supabase Auth, and conversation history in Postgres. Creating the app installs those features together.
@@ -1,6 +1,12 @@
---
title: Flutter Starter
description: 'A Flutter user management app with magic links, profiles, and avatar uploads'
preview: |-
<BlockOverview name="flutter-starter">
<div className="library-component-preview h-80 overflow-hidden border md:h-96">
<CatalogPreview kind="avatar" />
</div>
</BlockOverview>
---
Start from the [Supabase Flutter user management example](https://github.com/supabase/supabase/tree/master/examples/user-management/flutter-user-management). It includes a Flutter app, the Supabase Flutter SDK, account screens, and local database migrations.
@@ -1,6 +1,10 @@
---
title: Next.js Starter
description: 'A Next.js app with Supabase authentication, TypeScript, and Tailwind CSS'
preview: |-
<BlockOverview name="password-based-auth-nextjs" showFiles>
<BlockPreview name="password-based-auth/auth/sign-up" />
</BlockOverview>
---
Start with a Next.js App Router project, then install the Library's password-based authentication block. The block adds sign-in, sign-up, password recovery, and a protected page.
@@ -1,6 +1,12 @@
---
title: SaaS Starter
description: A Next.js subscription app with Supabase Auth and Stripe billing
preview: |-
<BlockOverview name="saas-starter">
<div className="library-component-preview h-80 overflow-hidden border md:h-96">
<CatalogPreview kind="dashboard" />
</div>
</BlockOverview>
---
The [Supabase Community subscription template](https://github.com/supabase-community/nextjs-subscription-payments) includes authentication, subscription checkout, a customer portal, and database migrations. Its components and billing routes are installed together when you create the app.
@@ -1,18 +1,15 @@
---
title: Supabase Client Libraries
description: Supabase client for TanStack Start
preview: <BlockOverview name="supabase-client-tanstack" showFiles />
---
## Installation
<BlockItem name="supabase-client-tanstack" description="Supabase Client for TanStack Start" />
<BlockItem name="supabase-client-tanstack" />
<TanStackBeta />
## Folder structure
<RegistryBlock itemName="supabase-client-tanstack" />
## Usage
This block installs Supabase clients for connecting your TanStack Start project to Supabase. They're designed for use in both server-side loaders and actions, as well as client-side components.
@@ -1,20 +1,15 @@
---
title: Current User Avatar
description: Supabase Auth-aware avatar
preview: |-
<BlockOverview name="current-user-avatar-tanstack" showFiles>
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
</BlockOverview>
---
<ComponentPreview name="current-user-avatar-demo" showCode={false} />
## Installation
<BlockItem
name="current-user-avatar-tanstack"
description="Renders the avatar of the current user."
/>
## Folder structure
<RegistryBlock itemName="current-user-avatar-tanstack" />
<BlockItem name="current-user-avatar-tanstack" />
## Introduction
@@ -1,24 +1,19 @@
---
title: Dropzone (File Upload)
description: Displays a control for easier uploading of files directly to Supabase Storage
preview: |-
<BlockOverview name="dropzone-tanstack" showFiles>
<ComponentPreview
name="dropzone-demo"
description="A file upload dropzone that accepts dragged or selected files, previews images, shows validation errors, and uploads to Supabase Storage."
showCode={false}
/>
</BlockOverview>
---
<ComponentPreview
name="dropzone-demo"
description="An alert with an icon, title and description. The title says 'Heads up!' and the description is 'You can add components to your app using the cli.'."
showCode={false}
/>
## Installation
<BlockItem
name="dropzone-tanstack"
description="Displays a control for easier uploading of files directly to Supabase Storage"
/>
## Folder structure
<RegistryBlock itemName="dropzone-tanstack" />
<BlockItem name="dropzone-tanstack" />
## Introduction
@@ -1,6 +1,10 @@
---
title: Headless App
description: A backend with an agent as the primary interface
preview: |-
<BlockOverview name="headless-app-tanstack" showFiles>
<BlockPreview name="headless-app" />
</BlockOverview>
---
A headless app uses an AI agent as its primary interface. Users sign in and
@@ -8,8 +12,6 @@ approve access in the browser, then work through their agent using MCP tools.
This starter includes task tools and a database with Row Level Security (RLS)
that limits each user to their own data.
<BlockPreview name="headless-app" />
## Installation
Create a TanStack Start app:
@@ -1,23 +1,18 @@
---
title: OAuth Consent
description: OAuth 2.1 consent block for TanStack Start
preview: |-
<BlockOverview name="oauth-consent-tanstack" showFiles>
<BlockPreview name="oauth-consent" />
</BlockOverview>
---
<BlockPreview name="oauth-consent" />
## Installation
<BlockItem
name="oauth-consent-tanstack"
description="OAuth 2.1 consent screen and the /oauth/consent route"
/>
## Folder structure
<BlockItem name="oauth-consent-tanstack" />
This block includes the [Supabase client](/library/docs/tanstack/client). If you already have one installed, you can skip overwriting it.
<RegistryBlock itemName="oauth-consent-tanstack" />
## Usage
This block installs an OAuth 2.1 consent route at `/oauth/consent`. It is designed for an app that already has authentication: it does not install sign-in, sign-up, or callback routes.
@@ -1,20 +1,18 @@
---
title: Password-based Authentication
description: Password-based authentication block for TanStack Start
preview: |-
<BlockOverview name="password-based-auth-tanstack" showFiles>
<BlockPreview name="password-based-auth/auth/sign-up" />
</BlockOverview>
---
<BlockPreview name="password-based-auth/auth/sign-up" />
## Installation
<BlockItem name="password-based-auth-tanstack" description="Supabase Client for TanStack Start" />
## Folder structure
<BlockItem name="password-based-auth-tanstack" />
This block includes the [Supabase client](/library/docs/tanstack/client). If you already have one installed, you can skip overwriting it.
<RegistryBlock itemName="password-based-auth-tanstack" />
## Usage
Once you install the block in your TanStack Start project, you'll get all the necessary pages and components to set up a password-based authentication flow.
@@ -1,20 +1,15 @@
---
title: Realtime Avatar Stack
description: Avatar stack in realtime
preview: |-
<BlockOverview name="realtime-avatar-stack-tanstack" showFiles>
<BlockPreview name="realtime-avatar-stack-demo" />
</BlockOverview>
---
<BlockPreview name="realtime-avatar-stack-demo" />
## Installation
<BlockItem
name="realtime-avatar-stack-tanstack"
description="Renders a stack of avatars which are connected via Supabase Realtime"
/>
## Folder structure
<RegistryBlock itemName="realtime-avatar-stack-tanstack" />
<BlockItem name="realtime-avatar-stack-tanstack" />
## Usage
@@ -1,20 +1,15 @@
---
title: Realtime Chat
description: Real-time chat component for collaborative applications
preview: |-
<BlockOverview name="realtime-chat-tanstack" showFiles>
<DualRealtimeChat />
</BlockOverview>
---
<DualRealtimeChat />
## Installation
<BlockItem
name="realtime-chat-tanstack"
description="Renders a real-time chat interface for users in a shared room."
/>
## Folder structure
<RegistryBlock itemName="realtime-chat-tanstack" />
<BlockItem name="realtime-chat-tanstack" />
## Introduction
@@ -1,23 +1,18 @@
---
title: Realtime Cursor
description: Real-time cursor sharing for collaborative applications
preview: |-
<BlockOverview name="realtime-cursor-tanstack" showFiles>
<div className="flex flex-row -space-x-px w-full">
<BlockPreview name="realtime-cursor-demo" isPair />
<BlockPreview name="realtime-cursor-demo" isPair />
</div>
</BlockOverview>
---
<div className="flex flex-row -space-x-px w-full">
<BlockPreview name="realtime-cursor-demo" isPair />
<BlockPreview name="realtime-cursor-demo" isPair />
</div>
## Installation
<BlockItem
name="realtime-cursor-tanstack"
description="Renders realtime cursors from other users in a room."
/>
## Folder structure
<RegistryBlock itemName="realtime-cursor-tanstack" />
<BlockItem name="realtime-cursor-tanstack" />
## Introduction
@@ -1,20 +1,15 @@
---
title: Realtime Flow
description: Real-time flow diagram editor for collaborative applications
preview: |-
<BlockOverview name="realtime-flow-tanstack" showFiles>
<DualRealtimeFlow />
</BlockOverview>
---
<DualRealtimeFlow />
## Installation
<BlockItem
name="realtime-flow-tanstack"
description="Real-time flow diagram editor for collaborative applications."
/>
## Folder structure
<RegistryBlock itemName="realtime-flow-tanstack" />
<BlockItem name="realtime-flow-tanstack" />
## Introduction
@@ -115,13 +110,14 @@ If you need programmatic access to nodes and edges (e.g. adding nodes, custom no
```tsx
import {
ReactFlow,
ReactFlowProvider,
Background,
Controls,
type Node,
ReactFlow,
ReactFlowProvider,
type Edge,
type Node,
} from '@xyflow/react'
import { useRealtimeFlow } from '@/hooks/use-realtime-flow'
const initialNodes: Node[] = [
@@ -1,20 +1,15 @@
---
title: Realtime Monaco
description: Real-time Monaco editor for collaborative applications
preview: |-
<BlockOverview name="realtime-monaco-tanstack" showFiles>
<DualRealtimeMonaco />
</BlockOverview>
---
<DualRealtimeMonaco />
## Installation
<BlockItem
name="realtime-monaco-tanstack"
description="Real-time Monaco editor for collaborative applications."
/>
## Folder structure
<RegistryBlock itemName="realtime-monaco-tanstack" />
<BlockItem name="realtime-monaco-tanstack" />
## Introduction
Loaded 100 of 161 files, more files were not shown because too many files have changed in this diff. Show more