Works with shadcn/uiUnder construction

Designed down to the last tap and the last prop.

Interactions that feel right to the humans using them. Code that's obvious to the agents building with them.

Installs with the shadcn CLI, uses shadcn's theme tokens, and sits alongside the shadcn/ui components you already have.

Try the interactions

Real components, in small local examples. Open only the ones you want to try.

Form

Validate required fields and try a local recipe-name rejection.

Form docs

Sheet

Open a sheet and choose cookbooks with a touch or the keyboard.

Sheet docs

Menu

Navigate actions and a submenu. This example does not change a recipe.

Menu docs

Swipe actions

Delete a local list item, then undo. Changes stay on this page.

Swipe actions docs

Table

Read a small recipe table with row links. This example does not sort or filter.

Table docs

How agents use it

Agents read the types

An agent that learned on shadcn/ui writes what it knows. Ouro HX answers where the agent already looks: the type check, the source it just installed, and the console. Every message here is real output from the code, checked against the code on every CI run.

  1. The agent writes

    shadcn's destructive variant

    <Button variant="destructive">Delete</Button>

    The type check

    error TS2322: Type '"destructive"' is not assignable to type '"solid" | "soft" | "outline" | "plain" | (string & { 'From shadcn? Use tone="destructive" or tone="secondary". default is "solid"; ghost and link are "plain"': never; }) | undefined'.

    The installed source components/ouro/button/button.tsx

    /** Visual weight: `solid` for the primary action, `soft` for secondary actions, `outline` for a quiet bordered control, `plain` for a borderless one. shadcn's `default` is `solid` (the default), `ghost` and `link` are `plain`, and `destructive` and `secondary` are tones: `tone="destructive"`. Any other string is a type error whose message names the fix. Variant is visual only. For a toggle, pass `aria-pressed`; for the current nav item, `aria-current="page"`. */variant?:
        | "solid"
        | "soft"
        | "outline"
        | "plain"
        | (string & { 'From shadcn? Use tone="destructive" or tone="secondary". default is "solid"; ghost and link are "plain"': never })
        | undefined;

    The console, in development

    [ouro-ui] Button: variant="destructive" is not a variant. Fix: <Button tone="destructive">.

  2. The agent writes

    A loading flag named the usual way

    <Button isLoading>Save</Button>

    The type check

    error TS2322: Type 'boolean' is not assignable to type '{ 'Use <Button loading>': never; }'.

    The installed source components/ouro/button/button.tsx

    /** Not a prop. Ouro UI uses `loading`: `<Button loading>`. */isLoading?: { 'Use <Button loading>': never };

    The console, in development

    [ouro-ui] Button: isLoading is not a prop. Fix: <Button loading>.

  3. The agent writes

    Radix's asChild for a link

    <Button asChild>
      <a href="/recipes">Recipes</a>
    </Button>

    The type check

    error TS2322: Type 'boolean' is not assignable to type '{ 'Use <Button href="/path">': never; }'.

    The installed source components/ouro/button/button.tsx

    /** Not a prop. To render a link, pass `href`: `<Button href="/recipes">`. For other elements, use parts. */asChild?: { 'Use <Button href="/path">': never };

    The console, in development

    [ouro-ui] Button: asChild is not a prop. Fix: <Button href="/path">.

Principles

Rules an agent doesn't have to learn

These rules replace what an agent would otherwise need a skill or a rules file for.

  1. Props first

    A component takes props that describe the whole thing, and building-block parts wait in a separate file for when props can't say what you need.

  2. Required means required

    What a component needs, like a sheet's title, is a required prop, so leaving it out is a type error.

  3. One state, one prop

    loading handles the spinner, aria-busy, the screen reader announcement and the layout, so nothing else needs setting.

  4. Named appearance

    tone values are the theme's token names (primary reads --primary), and variant and size are named options, so the props speak the theme's language instead of color names.

  5. className is for layout

    Margins and widths go in className, and an appearance class gets a one-time warning that names the prop to use.

  6. The guessed prop exists

    When an agent guesses a common name, it either works or fails the type check with an error that names the real prop, and hovering it in the editor shows the same note.

In use

Spoonjoy is built with it

Spoonjoy, a recipe app, uses 60 Ouro HX components, from its navigation to its recipe pages, forms and cooking timers.

Install

Copy it in, then make it yours

Alpha

The shadcn CLI copies a component into components/ouro/ with the helpers it imports. Its tests and stories are an optional -tests item that never changes your test tools' versions. Ouro HX is under construction, so props can still change.

  1. Set up shadcn once

    In an app with Tailwind CSS v4 and an @/* path alias. --base base because Ouro HX is built on Base UI; skip -t in an existing app.

    npx shadcn@latest init --base base --preset nova --yes --no-monorepo

    Then add the base tokens, once per app. From v0.8.0 it is the only add that writes to your stylesheet, so later adds leave it alone.

    npx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13
  2. Add a component from GitHub

    npx shadcn@latest add ourostack/ouro-ui/sheet#v1.2.13

    The tag pins the newest release. Without it, the address installs main, which is for contributors.

  3. Or add the @ouro namespace once

    It serves the newest release. Put this in components.json:

    {
      "registries": {
        "@ouro": "https://ui.ouro.bot/r/{name}.json"
      }
    }
    npx shadcn@latest add @ouro/ouro-base @ouro/sheet
  4. Use it

    import { Button } from "@/components/ouro/button/button";
    import { Sheet } from "@/components/ouro/sheet/sheet";
    
    export function SaveRecipe() {
      return (
        <Sheet title="Save to cookbook" trigger={<Button>Save</Button>}>
          <CookbookList />
        </Sheet>
      );
    }

Switching from shadcn

Keep your theme

Ouro HX reads the same theme variables as shadcn/ui, so your theme carries over unchanged. Its components install to components/ouro beside your components/ui, so you can switch one at a time.

Switching from shadcn

Next

Coming soon: Ouro AX

Ouro AX is the agent-facing direction within Ouro UI. Its scope is still being defined.