Quick Start
Get started with roi-ui components in minutes
Roi UI components are built on top of Base UI. You'll need to install Base UI first to use these components.
npm install @base-ui/reactThe tailwind utils file will not have the -tailwind suffix when it's in your registry.
npx shadcn@latest add @roiui/utilsnpx shadcn@latest add @roiui/utils-tailwindAdd a portal container to your root layout:
layout.tsx
<body>
<div className="root">
{children}
</div>
</body>Copy and paste the css modules or tailwind globals.
globals.css
.root {
isolation: isolate;
}
@layer fallbacks, dynamic;
.light,
[data-theme="light"],
:root {
--radius: 0.563rem;
--radius-lg: 1.2rem;
/* Subtle border colors for 0.5px borders */
--border-subtle: oklch(from var(--border) l c h / 0.7);
--background: oklch(1 0 0);
--foreground: oklch(0.2365 0.0078 17.71);
--card: oklch(0.978 0 0);
--popover-foreground: oklch(0.18 0 0);
--popover: oklch(0.9959 0 106);
--dialog-overlay: oklch(0 0 0 / 0.4);
--dialog-z: 100;
--primary: oklch(0.1855 0 0);
--primary-foreground: oklch(0.99 0 0);
--secondary: oklch(0.95 0.0025 0);
--secondary-foreground: oklch(0.4 0 0);
--muted: oklch(0.95 0 106.42);
--muted-foreground: oklch(0.6476 0.0012 17.19);
--accent: oklch(0.965 0 0);
--accent-foreground: oklch(0.32 0.015 280);
--border: oklch(0.86 0 0);
--input: oklch(0.98 0 0);
--ring: oklch(0.6734 0.153764 264.1315);
--shadow-lg:
0 10px 15px -3px oklch(0 0 0 / 0.05), 0 4px 6px -2px oklch(0 0 0 / 0.02);
--shadow-color: oklch(from var(--border) l c h / 0.2);
--shadow-border-stack:
oklch(0 0 0 / 0.02) 0px 3px 8px 1px,
oklch(0 0 0 / 0.02) 0px 2px 4px -1px,
oklch(0 0 0 / 0.02) 0px 1px 1px 0;
--destructive: oklch(0.6553 0.1374 15.66);
--destructive-foreground: oklch(0.9808 0 24);
--success: oklch(0.722 0.1946 146.7);
--warning: oklch(0.8782 0.0981 74.65);
--warning-foreground: oklch(0.6866 0.095 81);
--info: oklch(0.7091 0.15 264.71);
--selection-bg: oklch(0.1855 0.0326 233.92);
--selection-text: oklch(0.8985 0.0098 252.82);
--chart-1: oklch(0.5759 0.1503 264.31);
--chart-2: oklch(0.77 0.1788 263.5);
--chart-3: oklch(0.7091 0.15 264.71);
--chart-4: oklch(0.8782 0.0981 74.65);
--chart-5: oklch(0.722 0.1946 146.7);
--col-background: oklch(0.99 0 0);
}
@layer fallbacks {
.light,
[data-theme="light"],
:root {
--mix-card-5-bg: oklch(0.985 0 0);
--mix-card-15-bg: oklch(0.98 0 0);
--mix-card-33-bg: oklch(0.965 0 0);
--mix-card-50-bg: oklch(0.95 0 0);
--mix-card-66-bg: oklch(0.935 0 0);
--mix-card-75-bg: oklch(0.925 0 0);
--mix-card-80-muted: oklch(0.918 0 0);
--mix-card-85-bg: oklch(0.92 0 0);
}
.dark,
[data-theme="dark"] {
--mix-card-5-bg: oklch(0.16 0 0);
--mix-card-15-bg: oklch(0.17 0 0);
--mix-card-33-bg: oklch(0.185 0 0);
--mix-card-50-bg: oklch(0.2 0 0);
--mix-card-66-bg: oklch(0.215 0 0);
--mix-card-75-bg: oklch(0.225 0 0);
--mix-card-80-muted: oklch(0.255 0 0);
--mix-card-85-bg: oklch(0.23 0 0);
}
}
@layer dynamic {
@supports (background: color-mix(in oklab, white, black)) {
:root {
--mix-card-5-bg: color-mix(in oklab, var(--card) 5%, var(--background));
--mix-card-15-bg: color-mix(in oklab, var(--card) 15%, var(--background));
--mix-card-33-bg: color-mix(in oklab, var(--card) 33%, var(--background));
--mix-card-50-bg: color-mix(in oklab, var(--card) 50%, var(--background));
--mix-card-66-bg: color-mix(in oklab, var(--card) 66%, var(--background));
--mix-card-75-bg: color-mix(in oklab, var(--card) 75%, var(--background));
--mix-card-85-bg: color-mix(in oklab, var(--card) 85%, var(--background));
--mix-card-80-muted: color-mix(in oklab, var(--card) 80%, var(--muted));
}
}
}
.dark,
[data-theme="dark"] {
/* Subtle border colors for 0.5px borders */
--border-subtle: oklch(from var(--border) l c h / 0.7);
--background: oklch(0.15 0 0);
--foreground: oklch(0.94 0 0);
--card: oklch(0.26 0 0);
--popover: oklch(0.24 0 0);
--popover-foreground: oklch(0.94 0 0);
--primary: oklch(0.8985 0.0098 0);
--primary-foreground: oklch(0.08 0 0);
--secondary: oklch(0.3 0 0);
--secondary-foreground: oklch(0.88 0 0);
--muted: oklch(0.28 0 0);
--muted-foreground: oklch(0.67 0 0);
--accent: oklch(0.3 0 0);
--accent-foreground: oklch(0.85 0 0);
--border: oklch(0.38 0 0);
--input: oklch(0.2029 0.003 0);
--ring: oklch(0.48 0.025 270);
--shadow-lg:
0 10px 15px -3px oklch(0 0 0 / 0.25), 0 4px 6px -2px oklch(0 0 0 / 0.15);
--shadow-border-stack:
oklch(0 0 0 / 0.2) 0px 3px 8px, oklch(0 0 0 / 0.2) 0px 2px 4px,
oklch(0 0 0 / 0.2) 0px 1px 1px;
--destructive: oklch(0.54 0.1656 18.34);
--destructive-foreground: oklch(0.8988 0.0447 17.32);
--success: oklch(0.8325 0.2177 151.25);
--warning: oklch(0.829 0.105 81.04);
--warning-foreground: oklch(0.5893 0.1083 78.11);
--info: oklch(0.7091 0.15 264.71);
--selection-bg: oklch(0.8985 0.0098 252.82);
--selection-text: oklch(0.1855 0.0326 233.92);
--chart-1: oklch(0.6487 0.1856 264.84);
--chart-2: oklch(0.4187 0.2556 267.84);
--chart-3: oklch(0.7091 0.15 264.71);
--chart-4: oklch(0.829 0.105 81.04);
--chart-5: oklch(0.8325 0.2177 151.25);
--col-background: oklch(0.14 0 0);
}
@layer base {
:focus-visible {
outline: 1px solid var(--ring);
}
}
@layer all {
html {
/* Stable scrollbar - prevents layout shift when portals lock body scroll */
overflow-y: scroll;
scrollbar-gutter: stable;
max-width: 100vw;
overflow-x: hidden;
color: var(--foreground);
color-scheme: light dark;
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
}
body {
font-synthesis: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: var(--background);
}
html::-webkit-scrollbar {
width: 8px;
height: 8px;
}
html::-webkit-scrollbar-track {
background: transparent;
}
html::-webkit-scrollbar-thumb {
background: var(--border);
border-radius: 4px;
}
html::-webkit-scrollbar-thumb:hover {
background: var(--muted-foreground);
}
::selection {
background-color: var(--selection-bg);
color: var(--selection-text);
}
::-moz-selection {
background-color: var(--selection-bg);
color: var(--selection-text);
}
/* Hit area extension utility */
.hit-area-extend {
position: relative;
}
.hit-area-extend::before {
content: "";
position: absolute;
display: block;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 100%;
height: 100%;
max-height: 44px;
max-width: 44px;
}
}
Once you have Base UI set up, you can start using roi-ui components. Here's a simple button example:
import { Button } from "@/components/ui/button/button";
export default function ButtonDemo() {
return <Button>Button</Button>;
}