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-components/react

Add a portal container to your root layout:

layout.tsx
<body>
<div className="root">
  {children}
</div>
</body>

Copy and paste the global CSS variables into your globals.css file:

globals.css
/*
 * Unified globals for both CSS Modules and Tailwind demos
 * - @layer all: truly global styles (both demo types)
 * - @layer base: Tailwind's reset (reverted in CSS modules demos)
 * - @layer theme: theme tokens
 * - @layer components: component-specific styles
 * - @layer utilities: Tailwind utilities
 */

@import "tailwindcss/preflight" layer(base);
@import "tailwindcss/theme" layer(theme);
@import "tailwindcss/utilities" layer(utilities);

.root {
  isolation: isolate;
  background-color: var(--background);
}

@layer all;
@layer base;
@layer theme;
@layer components;
@layer utilities;

@theme inline {
  /* Border radius */
  --radius: 0.6rem;
  --radius-lg: 1.2rem;

  /* Reference theme-switching variables */
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --color-card: var(--card);
  --color-card-light: var(--card-light);
  --color-card-foreground: var(--foreground);
  --color-popover: var(--card);
  --color-popover-foreground: var(--popover-foreground);

  --color-primary: var(--primary);
  --color-primary-foreground: var(--primary-foreground);

  --color-secondary: var(--secondary);
  --color-secondary-foreground: var(--secondary-foreground);

  --color-muted: var(--muted);
  --color-muted-foreground: var(--muted-foreground);

  --color-accent: var(--accent);
  --color-accent-foreground: var(--accent-foreground);

  --color-destructive: var(--destructive);
  --color-destructive-foreground: var(--destructive-foreground);

  --color-success: var(--success);
  --color-warning: var(--warning);
  --color-warning-foreground: var(--warning-foreground);
  --color-info: var(--info);

  --color-border: var(--border);
  --color-border-60: oklch(from var(--color-border) l c h / 0.6);
  --color-border-10: oklch(from var(--color-border) l c h / 0.1);
  --color-input: var(--input);
  --color-ring: var(--ring);

  /* Chart colors */
  --color-chart-1: var(--chart-1);
  --color-chart-2: var(--chart-2);
  --color-chart-3: var(--chart-3);
  --color-chart-4: var(--chart-4);
  --color-chart-5: var(--chart-5);

  /* Easing functions */
  --ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
  --ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
  --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
  --ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955);
}

.light,
[data-theme="light"],
:root {
  --radius: 0.6rem;
  --radius-lg: 1.2rem;

  --background: oklch(1 0 0);
  --foreground: oklch(0.2365 0.0078 17.71);
  --card: oklch(0.98 0 0);
  --card-light: oklch(0.9759 0.0013 106.42);
  --popover-foreground: oklch(0.18 0 0);
  --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.958 0.0025 0);
  --secondary-foreground: oklch(0.4 0 0);

  --muted: oklch(0.9759 0.0013 106.42);
  --muted-foreground: oklch(0.6476 0.0012 17.19);

  --accent: oklch(0.9579 0.0013 106.42);
  --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);

  --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);

  --ease-out-quad: cubic-bezier(0.25, 0.46, 0.45, 0.94);
  --ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1);
  --ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1);
  --ease-in-out-quad: cubic-bezier(0.455, 0.03, 0.515, 0.955);

  --mix-card-15-bg: oklch(0.98 0 0);
  --mix-card-20-bg: oklch(0.975 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-70-bg: oklch(0.93 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);
  --mix-muted-20-trans: oklch(0.92 0 0 / 0.2);
  --mix-muted-50-trans: oklch(0.9 0 0 / 0.5);
  --mix-border-50-trans: oklch(0.94 0 0 / 0.5);
  --mix-border-70-trans: oklch(0.93 0 0 / 0.7);
  --mix-accent-20-trans: oklch(0.8 0 0 / 0.2);
  --mix-accent-33-trans: oklch(0.8 0 0 / 0.33);
  --mix-accent-66-bg: oklch(0.84 0 0);
  --mix-accent-75-bg: oklch(0.83 0 0);
  --mix-destructive-66-bg: oklch(0.75 0.09 15);
  --mix-foreground-6-trans: oklch(0.35 0 0 / 0.06);
  --mix-foreground-15-trans: oklch(0.35 0 0 / 0.15);
  --mix-foreground-20-muted: oklch(0.82 0 0);
  --mix-foreground-25-trans: oklch(0.35 0 0 / 0.25);
  --mix-destructive-20-muted: oklch(0.86 0.03 15);
  --mix-secondary-20-muted: oklch(0.916 0 0);
  --mix-success-20-muted: oklch(0.84 0.04 146);
  --mix-transparent-85-fg: oklch(0.35 0 0 / 0.85);
  --mix-transparent-88-fg: oklch(0.35 0 0 / 0.88);
}

@supports (background: color-mix(in oklab, white, black)) {
  :root {
    --mix-card-5-bg: color-mix(in oklab, var(--card) 5%, var(--background));
    --mix-card-10-bg: color-mix(in oklab, var(--card) 10%, var(--background));
    --mix-card-15-bg: color-mix(in oklab, var(--card) 15%, var(--background));
    --mix-card-20-bg: color-mix(in oklab, var(--card) 20%, 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-70-bg: color-mix(in oklab, var(--card) 70%, 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));
    --mix-muted-20-trans: color-mix(in oklab, var(--muted) 20%, transparent);
    --mix-muted-50-trans: color-mix(in oklab, var(--muted) 50%, transparent);
    --mix-border-50-trans: color-mix(in oklab, var(--border) 50%, transparent);
    --mix-border-70-trans: color-mix(in oklab, var(--border) 70%, transparent);
    --mix-accent-20-trans: color-mix(in oklab, var(--accent) 20%, transparent);
    --mix-accent-33-trans: color-mix(in oklab, var(--accent) 33%, transparent);
    --mix-accent-66-bg: color-mix(
      in oklab,
      var(--accent) 66%,
      var(--background)
    );
    --mix-accent-75-bg: color-mix(
      in oklab,
      var(--accent) 75%,
      var(--background)
    );
    --mix-destructive-66-bg: color-mix(
      in oklab,
      var(--destructive) 66%,
      var(--background)
    );
    --mix-foreground-6-trans: color-mix(
      in oklab,
      var(--foreground) 6%,
      transparent
    );
    --mix-foreground-15-trans: color-mix(
      in oklab,
      var(--foreground) 15%,
      transparent
    );
    --mix-foreground-20-muted: color-mix(
      in oklab,
      var(--foreground) 20%,
      var(--muted)
    );
    --mix-foreground-25-trans: color-mix(
      in oklab,
      var(--foreground) 25%,
      transparent
    );
    --mix-destructive-20-muted: color-mix(
      in oklab,
      var(--destructive) 20%,
      var(--muted)
    );
    --mix-secondary-20-muted: color-mix(
      in oklab,
      var(--secondary) 20%,
      var(--muted)
    );
    --mix-success-20-muted: color-mix(
      in oklab,
      var(--success) 20%,
      var(--muted)
    );
    --mix-transparent-85-fg: color-mix(
      in oklab,
      transparent 85%,
      var(--foreground)
    );
    --mix-transparent-88-fg: color-mix(
      in oklab,
      transparent 88%,
      var(--foreground)
    );
  }
}

.dark,
[data-theme="dark"] {
  --background: oklch(0.15 0 0);
  --foreground: oklch(0.94 0 0);
  --card: oklch(0.19 0 0);
  --card-light: oklch(0.2 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.25 0 283);
  --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.45 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);

  --destructive: oklch(0.5219 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);
  --mix-card-5-bg: oklch(0.17 0 0);
  --mix-card-10-bg: oklch(0.175 0 0);
  --mix-card-15-bg: oklch(0.18 0 0);
  --mix-card-20-bg: oklch(0.185 0 0);
  --mix-card-33-bg: oklch(0.195 0 0);
  --mix-card-50-bg: oklch(0.21 0 0);
  --mix-card-66-bg: oklch(0.225 0 0);
  --mix-card-70-bg: oklch(0.23 0 0);
  --mix-card-75-bg: oklch(0.235 0 0);
  --mix-card-80-muted: oklch(0.265 0 0);
  --mix-card-85-bg: oklch(0.24 0 0);
  --mix-muted-20-trans: oklch(0.19 0 0 / 0.2);
  --mix-muted-50-trans: oklch(0.24 0 0 / 0.5);
  --mix-border-50-trans: oklch(0.2 0 0 / 0.5);
  --mix-border-70-trans: oklch(0.22 0 0 / 0.7);
  --mix-accent-20-trans: oklch(0.32 0 0 / 0.2);
  --mix-accent-33-trans: oklch(0.32 0 0 / 0.33);
  --mix-accent-66-bg: oklch(0.25 0 0);
  --mix-accent-75-bg: oklch(0.23 0 0);
  --mix-destructive-66-bg: oklch(0.35 0.11 18);
  --mix-foreground-6-trans: oklch(0.94 0 0 / 0.06);
  --mix-foreground-15-trans: oklch(0.94 0 0 / 0.15);
  --mix-foreground-20-muted: oklch(0.68 0 0);
  --mix-foreground-25-trans: oklch(0.94 0 0 / 0.25);
  --mix-destructive-20-muted: oklch(0.36 0.05 18);
  --mix-secondary-20-muted: oklch(0.265 0 0);
  --mix-success-20-muted: oklch(0.48 0.04 151);
  --mix-transparent-85-fg: oklch(0.94 0 0 / 0.85);
  --mix-transparent-88-fg: oklch(0.94 0 0 / 0.88);
}

:focus-visible {
  outline: 1px solid var(--ring);
}

@layer all {
  [data-demo="css-modules"] *,
  [data-demo="css-modules"] *::before,
  [data-demo="css-modules"] *::after {
    box-sizing: border-box;
  }

  html {
    /* Stable scrollbar */
    overflow-y: scroll;
    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);
  }

  [data-demo="css-modules"] input,
  [data-demo="css-modules"] button,
  [data-demo="css-modules"] textarea,
  [data-demo="css-modules"] select {
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
  }

  [data-demo="css-modules"] ::placeholder {
    opacity: 1;
  }

  ::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%;
    min-height: 44px;
    min-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>;
}