Installation

Prepare a project once, then paste any block, component or effect. No CLI, no registry, no lock-in.

  1. Start from a React project with Tailwind CSS v4

    Any Next.js, Vite or Remix app works. Make sure TypeScript resolves the @/ alias to your source folder, as Next.js does by default.

  2. Install the base packages

    Terminal
    npm install @base-ui/react class-variance-authority clsx tailwind-merge lucide-react tw-animate-css
  3. Add the shared files

    The cn helper merges class names. The stylesheets hold the design tokens, the state variants Base UI relies on, and the keyframes used by effects.

    lib/utils.ts
    import { type ClassValue, clsx } from "clsx";
    import { twMerge } from "tailwind-merge";
    
    export function cn(...inputs: ClassValue[]) {
      return twMerge(clsx(inputs));
    }
    
    styles/theme.css
    /*
     * Design tokens shared by the site and every catalog item.
     * Customers copy this file as-is, so keep it framework-agnostic.
     */
    /* Sections marked .light stay light inside a dark page. */
    @custom-variant dark (&:is(.dark *):not(:where(.light, .light *)));
    
    @theme inline {
      --radius-sm: calc(var(--radius) - 4px);
      --radius-md: calc(var(--radius) - 2px);
      --radius-lg: var(--radius);
      --radius-xl: calc(var(--radius) + 4px);
      --radius-2xl: calc(var(--radius) * 1.8);
      --radius-3xl: calc(var(--radius) * 2.2);
      --radius-4xl: calc(var(--radius) * 2.6);
      --color-background: var(--background);
      --color-foreground: var(--foreground);
      --color-card: var(--card);
      --color-card-foreground: var(--card-foreground);
      --color-popover: var(--popover);
      --color-popover-foreground: var(--popover-foreground);
      --color-primary: var(--primary);
      --color-primary-foreground: var(--primary-foreground);
      --color-primary-from: var(--primary-from);
      --color-primary-to: var(--primary-to);
      --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-border: var(--border);
      --color-input: var(--input);
      --color-ring: var(--ring);
      --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);
      --color-sidebar: var(--sidebar);
      --color-sidebar-foreground: var(--sidebar-foreground);
      --color-sidebar-primary: var(--sidebar-primary);
      --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
      --color-sidebar-accent: var(--sidebar-accent);
      --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
      --color-sidebar-border: var(--sidebar-border);
      --color-sidebar-ring: var(--sidebar-ring);
    }
    
    :root,
    .light {
      --background: oklch(1 0 0);
      --foreground: oklch(0.145 0 0);
      --card: oklch(1 0 0);
      --card-foreground: oklch(0.145 0 0);
      --popover: oklch(1 0 0);
      --popover-foreground: oklch(0.145 0 0);
      --primary: oklch(0.488 0.243 264.376);
      --primary-foreground: oklch(0.97 0.014 254.604);
      /* The primary button's top-to-bottom gradient, the same in both themes. */
      --primary-from: oklch(0.605 0.213 259.02);
      --primary-to: oklch(0.51 0.256 263.87);
      --secondary: oklch(0.967 0.001 286.375);
      --secondary-foreground: oklch(0.21 0.006 285.885);
      --muted: oklch(0.97 0 0);
      --muted-foreground: oklch(0.556 0 0);
      --accent: oklch(0.97 0 0);
      --accent-foreground: oklch(0.205 0 0);
      --destructive: oklch(0.577 0.245 27.325);
      --border: oklch(0.922 0 0);
      --input: oklch(0.922 0 0);
      --ring: oklch(0.708 0 0);
      --chart-1: oklch(0.809 0.105 251.813);
      --chart-2: oklch(0.623 0.214 259.815);
      --chart-3: oklch(0.546 0.245 262.881);
      --chart-4: oklch(0.488 0.243 264.376);
      --chart-5: oklch(0.424 0.199 265.638);
      --radius: 0.625rem;
      --sidebar: oklch(0.985 0 0);
      --sidebar-foreground: oklch(0.145 0 0);
      --sidebar-primary: oklch(0.546 0.245 262.881);
      --sidebar-primary-foreground: oklch(0.97 0.014 254.604);
      --sidebar-accent: oklch(0.97 0 0);
      --sidebar-accent-foreground: oklch(0.205 0 0);
      --sidebar-border: oklch(0.922 0 0);
      --sidebar-ring: oklch(0.708 0 0);
    }
    
    .dark {
      --background: oklch(0.145 0 0);
      --foreground: oklch(0.985 0 0);
      --card: oklch(0.205 0 0);
      --card-foreground: oklch(0.985 0 0);
      --popover: oklch(0.205 0 0);
      --popover-foreground: oklch(0.985 0 0);
      --primary: oklch(0.424 0.199 265.638);
      --primary-foreground: oklch(0.97 0.014 254.604);
      --secondary: oklch(0.274 0.006 286.033);
      --secondary-foreground: oklch(0.985 0 0);
      --muted: oklch(0.269 0 0);
      --muted-foreground: oklch(0.708 0 0);
      --accent: oklch(0.269 0 0);
      --accent-foreground: oklch(0.985 0 0);
      --destructive: oklch(0.704 0.191 22.216);
      --border: oklch(1 0 0 / 10%);
      --input: oklch(1 0 0 / 15%);
      --ring: oklch(0.556 0 0);
      --chart-1: oklch(0.809 0.105 251.813);
      --chart-2: oklch(0.623 0.214 259.815);
      --chart-3: oklch(0.546 0.245 262.881);
      --chart-4: oklch(0.488 0.243 264.376);
      --chart-5: oklch(0.424 0.199 265.638);
      --sidebar: oklch(0.205 0 0);
      --sidebar-foreground: oklch(0.985 0 0);
      --sidebar-primary: oklch(0.623 0.214 259.815);
      --sidebar-primary-foreground: oklch(0.97 0.014 254.604);
      --sidebar-accent: oklch(0.269 0 0);
      --sidebar-accent-foreground: oklch(0.985 0 0);
      --sidebar-border: oklch(1 0 0 / 10%);
      --sidebar-ring: oklch(0.556 0 0);
    }
    
    styles/variants.css
    /*
     * State variants and keyframes the Base UI primitives rely on.
     * Customers copy this file next to theme.css.
     */
    @theme inline {
      @keyframes accordion-down {
        from {
          height: 0;
        }
        to {
          height: var(--accordion-panel-height, auto);
        }
      }
    
      @keyframes accordion-up {
        from {
          height: var(--accordion-panel-height, auto);
        }
        to {
          height: 0;
        }
      }
    }
    
    @custom-variant data-open {
      &:where([data-open]:not([data-open="false"])) {
        @slot;
      }
    }
    
    @custom-variant data-closed {
      &:where([data-closed]:not([data-closed="false"])) {
        @slot;
      }
    }
    
    @custom-variant data-checked {
      &:where([data-checked]:not([data-checked="false"])) {
        @slot;
      }
    }
    
    @custom-variant data-unchecked {
      &:where([data-unchecked]:not([data-unchecked="false"])) {
        @slot;
      }
    }
    
    @custom-variant data-selected {
      &:where([data-selected="true"]) {
        @slot;
      }
    }
    
    @custom-variant data-disabled {
      &:where([data-disabled]:not([data-disabled="false"])) {
        @slot;
      }
    }
    
    @custom-variant data-active {
      &:where([data-active]:not([data-active="false"])) {
        @slot;
      }
    }
    
    @custom-variant data-horizontal {
      &:where([data-orientation="horizontal"]) {
        @slot;
      }
    }
    
    @custom-variant data-vertical {
      &:where([data-orientation="vertical"]) {
        @slot;
      }
    }
    
    @utility no-scrollbar {
      -ms-overflow-style: none;
      scrollbar-width: none;
    
      &::-webkit-scrollbar {
        display: none;
      }
    }
    
    styles/effects.css
    /* Keyframes used by components/effects. Copy alongside the effect you use. */
    @theme inline {
      --animate-marquee: marquee var(--duration) infinite linear;
      --animate-marquee-vertical: marquee-vertical var(--duration) linear infinite;
    
      @keyframes marquee {
        from {
          transform: translateX(0);
        }
        to {
          transform: translateX(calc(-100% - var(--gap)));
        }
      }
    
      @keyframes marquee-vertical {
        from {
          transform: translateY(0);
        }
        to {
          transform: translateY(calc(-100% - var(--gap)));
        }
      }
    }
    
  4. Import the styles

    app/globals.css
    @import "tailwindcss";
    @import "tw-animate-css";
    @import "../styles/theme.css";
    @import "../styles/variants.css";
    @import "../styles/effects.css";
  5. Copy what you need

    Open any item and switch to the Code tab. Install the listed packages, then paste each file at the path shown. Primitives under components/ui are shared, so you only add them once.