Installation

Install the package, import the stylesheet, and wire the app shell.

Copy this prompt into Codex or another coding agent. It installs the package, initializes the bundled skill, and uses that skill to configure your app.

Prompt

Install and initialize raft-ui in this repository.

1. Inspect the repository and use its existing package manager. Do not change package managers or lockfile format.
   - Single-package repository: install the latest stable raft-ui as a dependency.
   - Monorepo: install raft-ui in the target app workspace.
2. At the project or workspace root, add raft-ui to package.json under intent.skills without removing existing entries, then run the package-manager equivalent of @tanstack/intent@latest install --map.
3. Load raft-ui#raft-ui-guide and follow it for this task. Run intent load raft-ui#raft-ui-guide --path before opening rules/setup.md or another linked rule, then resolve that file relative to the returned SKILL.md directory.
4. The package also ships raft-ui#raft-ui-critique for visual and experience reviews; do not load it unless the task includes a critique.
5. Follow the skill to initialize raft-ui: wire Tailwind CSS v4, import raft-ui/styles.css once, decide whether to load raft-ui/fonts.css separately, add ThemeProvider, and add TooltipProvider or ToastProvider only when the app needs them.
6. Put the application inside an isolated layout root so body portals stack above app content. Keep default body portals unless this is an iframe, preview, or embedded shell.
7. Inspect node_modules/raft-ui/dist/index.d.mts before using any prop, part, or variant you have not verified. Do not guess APIs or import from raft-ui/wip unless asked.
8. Validate the changed files with the repository's existing checks.

When finished, summarize the files changed, the Intent skill loaded, the providers added, and the validation results.

Load the package source hint and raft-ui/styles.css once from your global stylesheet, in that order before @import "tailwindcss". This step assumes you already have Tailwind v4 wired into your build.

  1. Add the global stylesheet.

    src/styles.css

    @import "tailwindcss";
    @import "raft-ui/styles.css";
    @source "../node_modules/raft-ui/dist/**/*.{js,mjs}";

    @source is relative to this CSS file, and Tailwind v4 does not scan node_modules by default. Adjust the path when your global stylesheet lives deeper in the app.

    src/styles.css

    @source "../../node_modules/raft-ui/dist/**/*.{js,mjs}";
  2. Load fonts (optional).

    Font loading and default family assignments are intentionally separate from raft-ui/styles.css. Load raft-ui/fonts.css as its own stylesheet if you want the package default font setup. Do not nest it inside your main CSS file; it contains a font @import. If you prefer self-hosted fonts, skip this file and assign your own --heading-font, --sans-font, and --mono-font values.

    src/main.tsx

    import "raft-ui/fonts.css";
    import "./styles.css";

Set up providers once near the root.

  1. Wrap the app.

    ThemeProvider syncs theme attributes to the document root. Add TooltipProvider when the app uses Tooltip. Add ToastProvider once when the app uses toast feedback.

    Dark mode is class-driven: the provider resolves the system preference and stamps the .light or .dark class on the root. Server-rendered apps that persist a mode should replay it with a small inline script before first paint (the next-themes pattern) so system-dark visitors do not flash light.

    src/App.tsx

    import { ThemeProvider, ToastProvider, TooltipProvider } from "raft-ui";
    
    export function App() {
      return (
        <ThemeProvider>
          <ToastProvider>
            <TooltipProvider>
              <YourApp />
            </TooltipProvider>
          </ToastProvider>
        </ThemeProvider>
      );
    }
  2. Isolate the layout root.

    Base UI portals render floating surfaces such as Dialog, Popover, Select, DropdownMenu, and Tooltip at the document body. Add Tailwind's isolate class to the application root so portaled surfaces stack above page content instead of competing with app-level z-index values. Keep the default body portal behavior unless the app renders inside an iframe, preview frame, or embedded shell.

    layout.tsx

    <body>
      <div className="isolate">
        {children}
      </div>
    </body>

    styles.css

    .root {
      isolation: isolate;
    }

    If the host layout does not use Tailwind, apply the same CSS with isolation: isolate.

Import components from the package.

src/App.tsx

import { Button } from "raft-ui";

export function App() {
  return <Button>Save</Button>;
}

Optional: theme switching, reading, and customization after the core setup.

Switch themes

Use setTheme. Pass a mode only when the family is elegant: light, dark, or system.

src/ThemeSwitcher.tsx

import { Button, useTheme } from "raft-ui";

export function ThemeSwitcher() {
  const { setTheme } = useTheme();

  return (
    <div>
      <Button onClick={() => setTheme("brutal")}>Brutal</Button>
      <Button onClick={() => setTheme("elegant", { mode: "light" })}>Elegant light</Button>
      <Button onClick={() => setTheme("elegant", { mode: "dark" })}>Elegant dark</Button>
      <Button onClick={() => setTheme("elegant", { mode: "system" })}>Elegant system</Button>
    </div>
  );
}

Read theme state

Read the active family, selected mode, and resolved mode from useTheme.

src/ThemeStatus.tsx

import { useTheme } from "raft-ui";

export function ThemeStatus() {
  const { theme, mode, resolvedMode } = useTheme();

  return (
    <p>
      {theme} / {mode} / resolved {resolvedMode}
    </p>
  );
}

Customize with data-theme

Prefer semantic tokens. Use data-theme variants only for family-specific differences.

src/BillingCard.tsx

<section
  className={cn(
    "rounded-lg border border-line bg-layer-panel p-4 text-foreground-strong shadow-raft-sm",
    "[[data-theme=brutal]_&]:rounded-none [[data-theme=brutal]_&]:border-2 [[data-theme=brutal]_&]:border-black",
    "[[data-theme=elegant]_&]:shadow-raft-md",
    "[[data-theme=elegant].dark_&]:bg-layer-popover",
  )}
>
  Billing
</section>

Optional: add ToastProvider near the app root, then call toast.info, toast.success, toast.warning, or toast.error from event handlers.

src/App.tsx

import { ThemeProvider, ToastProvider, TooltipProvider } from "raft-ui";

export function App() {
  return (
    <ThemeProvider>
      <ToastProvider timeout={3600} limit={3}>
        <TooltipProvider>
          <YourApp />
        </TooltipProvider>
      </ToastProvider>
    </ThemeProvider>
  );
}

src/CopyLinkButton.tsx

import { Button, toast } from "raft-ui";

export function CopyLinkButton() {
  return (
    <Button
      onClick={() => {
        toast.success("Link copied.", {
          description: "Ready to paste into the thread.",
          action: {
            label: "Undo",
            onClick: () => toast.dismiss(),
          },
        });
      }}
    >
      Copy link
    </Button>
  );
}

raft-ui ships raft-ui-guide for implementation and raft-ui-critique for interface review. TanStack Intent discovers both from the installed package, including dependencies of pnpm workspace packages.

package.json

{
  "intent": {
    "skills": ["raft-ui"]
  }
}

Shell

pnpm dlx @tanstack/intent@latest install --map
pnpm dlx @tanstack/intent@latest load raft-ui#raft-ui-guide

Available skills

pnpm dlx @tanstack/intent@latest load raft-ui#raft-ui-guide
pnpm dlx @tanstack/intent@latest load raft-ui#raft-ui-critique

Using skills-npm

The same package remains compatible with skills-npm and other standard Agent Skill installers. skills-npm 1.2.0 has a known limitation when recursively aggregating multiple skills from one package in a monorepo, so prefer Intent for that layout or run skills-npm non-recursively where raft-ui is a direct dependency.

Shell

pnpm add -D skills-npm
pnpm exec skills-npm setup