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
Design Tips

Bring your own icons

Register react-icons, lucide or hand-written SVGs with IconProvider, type-check the new names with IconLibraryOverrides, and replace the icons Once UI components draw.
Updated 2 days ago
Border radius & nested corners
5 min · Design · Icons
Once UI 2.0 ships its own icons and lets you register more. Add them through IconProvider and teach TypeScript the new names, so a typo fails the build instead of rendering nothing.

What ships in the box

Once UI 2.0 no longer depends on an icon package. The SVG data for the built-in set is inlined in @once-ui-system/core, and it covers everything the components themselves draw: chevrons, check, close, info, warning, danger, search and so on. name on Icon is typed as IconName, a real union of those names. Before 2.0 the registry was typed Record<string, IconType>, so any string compiled and a typo only showed up as a blank space and a console warning. In 2.0 an unknown name is a type error.

Add your own icons

An icon is anything that renders an SVG and takes no props. Once UI never passes any. That means components from react-icons, lucide, heroicons, or an SVG you wrote yourself all work. Keep them in one file and hand that object to IconProvider: Your icons are merged over the built-in set, so you only list what you add or change.

Teach TypeScript the new names

Registering icons at runtime does not change the IconName type. Without one more step, <Icon name="rocket" /> renders fine but fails type-checking. Extend the union by merging into IconLibraryOverrides, right under the object: Because the interface is derived from the object, the two can't drift. Add an icon and its name type-checks straight away. Remove one and every place that still uses it becomes a build error, which is the point. Two things not to do:
  • Don't annotate the object as Record<string, IconType>. That widens the keys to string, IconName collapses back to string, and typos get through again.
<Icon name="check" onBackground="success-medium" />
<Icon name="chekc" /> // type error in 2.0
// resources/icons.ts
import { HiOutlineRocketLaunch } from "react-icons/hi2";
import { FaGithub } from "react-icons/fa6";

export const iconLibrary = {
  rocket: HiOutlineRocketLaunch,
  github: FaGithub,
};
// Providers.tsx
import { IconProvider } from "@once-ui-system/core";
import { iconLibrary } from "../resources/icons";

<IconProvider icons={iconLibrary}>
  {children}
</IconProvider>
declare module "@once-ui-system/core" {
  interface IconLibraryOverrides extends Record<keyof typeof iconLibrary, true> {}
}
  • Don't write the names out by hand in the interface. The list will fall behind the object.
  • Replace a default icon

    The components use the same registry. To swap a chevron, or the light, dark and computer icons the theme switcher draws, register your component under the same key:
    import { LuChevronDown, LuSun, LuMoon } from "react-icons/lu";
    
    export const iconLibrary = {
      chevronDown: LuChevronDown,
      light: LuSun,
      dark: LuMoon,
    };
    Every component that asks for chevronDown now gets yours. This is the cleanest way to make Once UI match an icon set you already use elsewhere.

    Resolve a name yourself

    For rendering, use Icon. When you need the component behind a name, for example to fall back when a name comes from data, useIcons() returns the merged library:
    const { icons } = useIcons();
    const known = Object.keys(icons);
    
    <Icon name={known.includes(name) ? name : "info"} />
    useIcons() does not throw outside a provider. It returns the built-in set, so a component that only needs default icons works with no provider in the tree.

    Write this / not this

    Do
    Avoid
    `IconProvider icons={iconLibrary}`
    Importing raw SVG components into every file
    `IconLibraryOverrides` derived from the object
    `Record<string, IconType>` on the registry
    Same key to replace a defaultWrapping components to hide their icon
    `<Icon name="rocket" />`
    `<HiOutlineRocketLaunch />` inline next to Once UI icons

    More design tips

    The full reference, including the provider props, is in the IconProvider docs.