Once UI Handbook
Vibe coding
Introduction to vibe coding
Set up your local dev environment
Essential tools for vibe coding
Build your first Once UI app
Getting started
What is Once UI?
Install & config
Page skeleton
Your first page
Upgrade from 1.8 to 2.0
Once UI without Next.js
Build and launch
How to launch a portfolio site that actually gets you hired
How to build a documentation site with MDX and Next.js
How to ship a landing page and dashboard with authentication in Next.js
How to launch a social app with Next.js and Supabase
Common patterns
Hero section
Static panel
Form layout
Highlighted card
Responsive stacking
Decoration layers
Dialog & modal
Toast & feedback
Tables & lists
Loading states
App shell navigation
Dashboard & charts
Chat & messaging
Auth & verification
Settings & split panels
Data filters & toolbar
Command palette
Pricing & plans
Accordion & FAQ
Media & uploads
Empty & error states
SEO, Open Graph, and structured data
Documentation code blocks with live preview
Carousels, galleries, and before/after comparisons
Date pickers and scheduling
Context menus and dropdown actions
Tags, chips, and multi-value inputs
Roadmap & kanban
Form inputs & controls
Progress, status & badges
Scrolling & feeds
Profile, avatars & identity
Social proof & logo clouds
Masonry & media grids
Table pagination, search & bulk selection
Banners & announcements
Footer layouts
Onboarding & first run
Waitlist & coming soon
Forms that wire themselves
Design tips
Color & surfaces
Harness overview
Row, Column & Grid
Spacing & rhythm
Icons & buttons
Reveal & motion
Theme tokens, FOUC-free init, and runtime style controls
Tooltips and hover cards
Border radius & nested corners
Bring your own icons
Agent resources
Column, not Flex
Semantics over CSS
Typography scale
Common gotchas
Block anchors
Validate generated code
Pro block registry
Match task bundles
Block updates
Block updates changelog
TrademarkTrademark
Ctrl k
Search…
Sign up
Once UI Handbook
Vibe coding
Introduction to vibe coding
Set up your local dev environment
Essential tools for vibe coding
Build your first Once UI app
Getting started
What is Once UI?
Install & config
Page skeleton
Your first page
Upgrade from 1.8 to 2.0
Once UI without Next.js
Build and launch
How to launch a portfolio site that actually gets you hired
How to build a documentation site with MDX and Next.js
How to ship a landing page and dashboard with authentication in Next.js
How to launch a social app with Next.js and Supabase
Common patterns
Hero section
Static panel
Form layout
Highlighted card
Responsive stacking
Decoration layers
Dialog & modal
Toast & feedback
Tables & lists
Loading states
App shell navigation
Dashboard & charts
Chat & messaging
Auth & verification
Settings & split panels
Data filters & toolbar
Command palette
Pricing & plans
Accordion & FAQ
Media & uploads
Empty & error states
SEO, Open Graph, and structured data
Documentation code blocks with live preview
Carousels, galleries, and before/after comparisons
Date pickers and scheduling
Context menus and dropdown actions
Tags, chips, and multi-value inputs
Roadmap & kanban
Form inputs & controls
Progress, status & badges
Scrolling & feeds
Profile, avatars & identity
Social proof & logo clouds
Masonry & media grids
Table pagination, search & bulk selection
Banners & announcements
Footer layouts
Onboarding & first run
Waitlist & coming soon
Forms that wire themselves
Design tips
Color & surfaces
Harness overview
Row, Column & Grid
Spacing & rhythm
Icons & buttons
Reveal & motion
Theme tokens, FOUC-free init, and runtime style controls
Tooltips and hover cards
Border radius & nested corners
Bring your own icons
Agent resources
Column, not Flex
Semantics over CSS
Typography scale
Common gotchas
Block anchors
Validate generated code
Pro block registry
Match task bundles
Block updates
Block updates changelog
TrademarkTrademark
Once UIDocumentationBlog
© Once UI. All rights reserved.
Built with Aveiro
Beginner Guides

Once UI without Next.js

In 2.0, links, images and navigation go through four adapters. Plain React needs nothing, React Router needs three lines, and Next needs one import you shouldn't skip.
Updated 14 days ago
Upgrade from 1.8 to 2.0
5 min · Setup · Once UI 2.0
Next.js is an optional peer now. Here is what that changes for your app, whichever framework it runs on.

What changed in 2.0

Core no longer imports next/* at runtime. The four things a framework owns are read through adapters instead:
Adapter
Does
Default without a framework
`Link`Renders internal linksA plain `<a>`
`Image`Renders images
A plain `<img>`, lazy unless `priority` is set
`usePathname`Reads the current path
`window.location.pathname`, updated on back and forward, empty on the server
`useNavigate`Navigates from code`window.location.assign`
Everything that routes or loads an image goes through them: SmartLink, Media, Logo, MegaMenu, Kbar, and any Button, Card or ToggleButton you give an href. That is why the same package now installs and runs in a Vite app, a Remix app, or plain React.

Plain React: do nothing

If your app has no router, the defaults are already right. Links are links, images are images, and a navigation is a page load. There is no provider to add.

React Router: swap three pieces

With a client-side router you want links to stay in the app instead of reloading the page. Wrap your tree in AdapterProvider and hand it your router's pieces:
import { AdapterProvider } from "@once-ui-system/core";
import { Link, useLocation, useNavigate } from "react-router";

export function Adapters({ children }: { children: React.ReactNode }) {
  return (
    <AdapterProvider
      adapters={{
        Link: ({ href, ...props }) => <Link to={href} {...props} />,
        usePathname: () => useLocation().pathname,
        useNavigate,
      }}
    >
      {children}
    </AdapterProvider>
  );
}
The override is partial. Anything you leave out keeps the value from the nearest provider above it, or the default, so this example keeps the plain <img>. It also means you can nest a second AdapterProvider to change one adapter for one part of the tree.

Next.js: one import, and don't skip it

On Next, import LayoutProvider from the next subpath instead of the root:
Same component, same props, with next/link, next/image and next/navigation wired in. If you compose your own provider tree, from the same subpath does the same job.
NextAdapterProvider
Skip it and nothing crashes, which is the trap. The plain defaults take over, so every internal link becomes a full page load and images lose next/image optimisation. The page still looks right. In development Once UI prints a console warning when it spots a Next app running without the adapters, but in production it says nothing. If routing feels heavy after the upgrade, check this import first.

Why it doesn't just detect Next

It would be nicer if core noticed Next and wired itself up. It can't, safely. Two of the adapters are hooks, and the two versions call a different number of hooks: the default useNavigate calls none, the Next one calls useRouter and useCallback. Swapping them after the first render breaks React's rules of hooks. The choice has to be settled before that render, and a browser bundle has no synchronous way to load an optional module on a condition. An explicit import is the version of that which works with every bundler.

Your own components

If you build a component that should route the same way Once UI's do, read the adapters instead of importing your router directly:
Then the component works unchanged in a Next app, a React Router app and a plain one.
The full reference, including the image adapter's props, lives in the AdapterProvider docs. If you are still on 1.8, start with the upgrade guide in this section.

More beginner guides

import { useAdapters } from "@once-ui-system/core";

function PricingLink() {
  const { Link, usePathname } = useAdapters();
  const pathname = usePathname();

  return (
    <Link href="/pricing" aria-current={pathname === "/pricing" ? "page" : undefined}>
      Pricing
    </Link>
  );
}