Skip to content
ourostackPublic

About

Ouro UI for React: Ouro HX components that feel right to people and read clearly to the agents building with them. A shadcn registry.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

850 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

A person and a small robot building a green Ouro HX card together at a workbench while Ouro watches, at dawn

Ouro UI

A design system for people and agents. Ouro UI renders A2UI, the format agents compose interfaces in, and works alongside it: it ships a catalog of its components for A2UI and renderers for it on the web and in SwiftUI.1 It adds what A2UI leaves to the app:

  • One action declaration. Declare an action once, and core's generator writes its CLI command, MCP and WebMCP tools, web control, App Intent, SwiftUI button and Reins action, with a check that fails your CI when a generated file drifts.2
  • Person-only actions, refused. An action only the person may start, such as paying, is offered through no agent door, and each door answers an agent with a typed refusal.3
  • X-ray checks. ax-xray calls an app's CLI, HTTP API or MCP server the way an agent would and reports which claims hold, and ouro-ax-xray checks an app's App Intents.4

See how agents do with it in the agent benchmark, and what frontier models chose when they compared it with A2UI and other options in the model reviews.

Ouro UI runs on the web (this repository) and on Apple (ourostack/ouro-ui-apple), and the Apple package has not reached 1.0.5 There is no Android or Windows kit yet.6 The Android plan and the Windows plan say what one needs.

Ouro HX

React components for people, and for the agents who build them. 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.

See it live: ui.ouroboros.bot, with the docs and Storybook. Spoonjoy, a recipe app, is built with it (source); each component's docs page links to where Spoonjoy shows it.7

Ouro HX is the half of Ouro UI for the interfaces people use: React components you copy in with the shadcn CLI. Ouro AX is the other half, for the interfaces agents use.

Ouro UI has a web half (this repository) and an Apple half (ourostack/ouro-ui-apple, a Swift package), with a side for people and a side for agents in both:

Web Apple
People (Ouro HX) React components you copy in, below SwiftUI OuroButton, OuroEmptyState, OuroBanner, .ouroSheet and toasts, with the spring, haptics and themes8
Agents (Ouro AX) the kit and X-ray for command lines, HTTP APIs and MCP servers typed outcomes, an X-ray for an app's App Intents, and Ouro Reins to drive its screens8
Both, from one declaration a command line, MCP and WebMCP tools and a web control an App Intent, a SwiftUI button and a Reins action9

Web and Apple parity is a table built from ouro-ui-core's claim files, and it marks the gaps. The Apple half has releases but has not reached a first stable version. The one action model is in ouro-ui-core: declare an action once, and its generator writes the surfaces above, with a check that fails your CI when a generated file drifts from the declaration.9 Android and Windows are open for a builder.

# In an app set up with shadcn init (see Quick start):
npx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.3.17  # once per app: the tokens
npx shadcn@latest add ourostack/ouro-ui/dialog#v1.3.17     # then any component

New to shadcn? Quick start has the init line.

Browse first with npx shadcn@latest search 'ourostack/ouro-ui#v1.3.17' (list the items) and npx shadcn@latest view 'ourostack/ouro-ui/dialog#v1.3.17' (read one). The same commands and the item list, for agents: llms.txt.

Ouro HX is copy-in source in the shadcn/ui registry format: the shadcn CLI copies each component into your app, and your app's own Tailwind CSS v4 compiles its classes.10 Like shadcn/ui's own items, a component item installs only the component's source; its tests are a separate, optional item. The components read shadcn's theme variables (--primary, --background, --radius…), so an app that already has a shadcn theme keeps it.

import { Button } from "@/components/ouro/button/button";
import { Dialog } from "@/components/ouro/dialog/dialog";

<Dialog title="Edit recipe" trigger={<Button>Edit</Button>}>
  <RecipeForm />
</Dialog>;

These are the paths in your app once the CLI has installed a component, with your components alias in place of @/components (aliases.components in components.json). The files in this repository and the docs/ pages use the registry source paths, @/registry/ouro/components/ouro/button/button, which the CLI rewrites on install. The components need react and react-dom 19; the CLI installs the rest (@base-ui/react, clsx, tailwind-merge).11 They don't depend on the Motion library12: motion is CSS, and reduced motion is honored in CSS.13 If your app animates with Motion, wrap it in <MotionConfig reducedMotion="user">.

Install

Quick start

# Existing Vite/Next app with Tailwind CSS v4 and an "@/*" path alias:
npx shadcn@latest init --base base --preset nova --yes --no-monorepo
# Once per app, before your first component: the tokens every component needs.
npx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.3.17
npx shadcn@latest add ourostack/ouro-ui/dialog#v1.3.17
  • Add ouro-base once, before your first component. It is the only item that writes to your stylesheet (apart from the optional ouro-theme), so no later add touches it (from v0.8.0; in v0.7.0 every component still installs it, and an add can rewrite the stylesheet).141516 Installing says what it writes.

  • Pass --base base: Ouro HX is built on Base UI. Don't pass -t/--template in an existing app; it adds shadcn's components/ui/button.tsx.

  • New Vite app? Add @tailwindcss/vite, @import "tailwindcss";, a Vite @ alias, and "paths": { "@/*": ["./src/*"] } in both tsconfig.json and tsconfig.app.json (no baseUrl on TypeScript 6) before init. shadcn reads the root file, and tsc -b builds the app one.

  • --preset nova lets init run without prompts. It sets shadcn's starting theme, font and icons (nova adds the Geist font, lucide-react and tw-animate-css), which Ouro components read like any shadcn theme. The install test runs nova and vega; other presets are untested.

  • A GitHub source is owner/repo#ref; an item is owner/repo/item#ref. @v1.3.17, github: and /tree/ URLs don't work.

Check what you built

npx shadcn@latest add ourostack/ouro-ui/ouro-check#v1.3.17
node lib/ouro/check/check.ts dist   # your built folder

Build your app, run the check on the built folder and fix what it reports: it opens every page at every screen size from 320 to 3840 wide, light and dark, and finds overlap, sideways scroll, clipped text, blurry pictures and videos, serious axe-core violations, design problems (a banner where content belongs, a heading with no room above it, a frame inside a frame, a recording's own corner inside its frame, too many text sizes, weights or shadows, grey text on colour, a heading larger than the one above it), jank as it hovers, opens, scrolls and pans each page, animations that repaint at rest, and a site that doesn't move to HTTPS, and long lines only where you declare a measure (Guardrails guide). Long lines are opt-in, because a fixed text measure is a design choice.17 Declare one in ouro-check.json only where your design or the request sets it, and don't cap text width just to quiet the check. It exits as every Ouro tool does: 0 ok, 1 error (the check stopped before it finished), 2 usage, 3 findings to fix.18

Next.js

In a Next.js App Router app, init writes "rsc": true, so the components keep their "use client" line and a Server Component page can render them, parts included. Pass them only what can cross from the server: elements, strings, numbers, and a server action ("use server") for ConfirmDialog's onConfirm. CI builds such an app with next build (scripts/install-test.sh next).

In a React Router app, OuroProvider needs a small adapter, because the router's Link takes to; OuroProvider has both files in full.

OuroProvider routes hrefs through next/link and groups tooltips. It comes with every component that renders links (button, link, card, item, table and the navigation components) and with tooltip.19 With only other components installed, such as dialog above, add it by name (v0.7.0 has no such item: there it comes with every component):16

npx shadcn@latest add ourostack/ouro-ui/lib-ouro-provider#v1.3.17

Put it in a client file, because a Server Component can't pass Link, a function, to a client component. app/providers.tsx:

"use client";

import Link from "next/link";
import type { ReactNode } from "react";
import { OuroProvider } from "@/lib/ouro/ouro-provider";

// A Server Component can't pass next/link's Link (a function) to a Client Component, so the
// provider gets it here, in a client file.
export function Providers({ children }: { children: ReactNode }) {
  return <OuroProvider linkComponent={Link}>{children}</OuroProvider>;
}

Then wrap the app in app/layout.tsx, which stays a Server Component:

import type { ReactNode } from "react";
import { Providers } from "./providers";
import "./globals.css";

export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

Details

  • Every install needs a release tag: npx shadcn@latest add ourostack/ouro-ui/<item>#v1.3.17, or the @ouro namespace below.20 The CLI reads registry.json at this repository's root at the ref you give, and only a release commit has one: a tag such as #v1.3.17 installs the item and every Ouro dependency (ouro-lib, button and so on) at that tag.21 Without a ref, or with a branch or a commit, the CLI stops with "did not return a root registry.json file", because main commits none.20
  • Or add the @ouro namespace to components.json once, "registries": { "@ouro": "https://ui.ouroboros.bot/r/{name}.json" }, and run npx shadcn@latest add @ouro/ouro-base @ouro/dialog (@ouro/ouro-base once per app, as above). The namespace serves the newest release, the same code as #v1.3.17.22
  • Components land in components/ouro/<component>/, beside shadcn's components/ui/, and the helpers they import in lib/ouro/, one file each: a component installs only the helpers it imports.23 No component installs ouro-base; you add it once, as above.14 If you skip it, overlays have no backdrop or motion and fields no border, and in development the first Ouro HX component to mount logs the command to run.24
  • init is shadcn's: it writes lib/utils.ts and adds cn, class-variance-authority, lucide-react and tw-animate-css. Ouro HX doesn't use them; keep them for shadcn components. Ouro never writes to components/ui/, so don't pass --overwrite on a first install.25
  • Tests are optional. Each component's Vitest browser tests, type tests, stories and Playwright spec are its -tests item, with the tests of everything the component installs26: npx shadcn@latest add ourostack/ouro-ui/dialog#v1.3.17 ourostack/ouro-ui/dialog-tests#v1.3.17. CI verifies 100% line and branch coverage of the installed runtime files for five sets: button, dialog, sheet, the forms set and their union (the Testing guide lists the forms set).27 Other items receive their full test dependency closure but are not measured individually. ouro-tests adds every component with all of its tests in one command.28 The install message of ouro-testing (the test kit the -tests items bring) lists the config files to add, then run npx vitest run.
  • The tests need Vitest 4.1.11 or later in browser mode, Playwright 1.58.1 or later, axe-core 4.13.0 or later and Storybook 10.2.10 or later.29 Ouro HX's own CI runs them on Vitest 5 only; Vitest 4 is the stated minimum, not a tested one.3029 A -tests item names those tools without a version, so the CLI installs one only when your app lacks it and never changes a version you have.31 v0.7.0's items still carry versions: after add, restore every test-tool version you had pinned (git diff package.json).16 Testing has the requirements, what to do on Vitest 4, and how to find Ouro HX's fields in your own tests.32
  • In a Vite or React Router app the components keep their "use client"; line, which is harmless there. Installing says why, and how to drop Rollup's notice about it.
  • For the Ouro look, add ourostack/ouro-ui/ouro-theme#v1.3.17. Skip it to keep your app's shadcn theme.
  • To update to the newest release, list the changed files with npx shadcn@latest add ourostack/ouro-ui/<item>#v1.3.17 --dry-run (or @ouro/<item>), review each one marked overwrite with --diff <path>, then add the item again at the same ref with --overwrite (with its -tests item if you have it: add <item> <item>-tests --overwrite) and reapply your edits. Coming from v0.1.0? Follow Upgrading from v0.1.0 instead.

Upgrading from v0.1.0

v0.1.0 installed each component with its tests, and v0.2.0 moves some shared helpers to new files, so update every Ouro component in one go.3334 Run the commands in your app's root, the folder with components.json. They find your folders the way shadcn does, from the aliases in components.json and the paths in your tsconfig (npx shadcn@latest info shows them), so they work in a src/ or an app/ layout as written.

What changed, by version

What an app on v0.1.0 has to know beyond these steps.35 The release notes list every change.36

  • v0.2.0: each component's tests became an optional <name>-tests item, some helpers moved to new files (step 4), and v0.1.0's self-referential theme lines can go (step 2).34

  • v0.3.0: sheets and dialogs stack at --ouro-layer, a z-index of 50 by default, instead of level 0, so an app element at z-index 1 to 49 that used to show above an open sheet or dialog now sits under it.37 Raise the token in a :root rule of your own if your app has fixed bars above z-50 (see "Theme" in the README), and drop any portal z-index workaround. A controlled Dialog opened by a touch or pen tap focuses its popup instead of its first input, and ConfirmDialog's finalFocus applies only after a confirmed action; drop workarounds for either. Every coverage hint is /* istanbul ignore … @preserve */, so a build plugin that kept the hints can go.38 A -tests item sets Vitest to ^5.

  • v0.3.1: every component starts with "use client", so Next.js App Router pages can render them, and the @ouro namespace serves the newest release instead of main.39

  • After v0.7.0: a required field's * is drawn with CSS on a new element around the label and is no longer a <span data-ouro-required> inside it, so tests that expected the label text "Email*" now find "Email".40 The clear, show-password and choose-date buttons in TextField and DatePicker are named by hidden text, not aria-label, so replace button[aria-label="…"] selectors with [data-ouro-clear], [data-ouro-show-password] or [data-ouro-choose-date]. The same goes for a file's remove button in FileField ([data-ouro-file-remove]), Combobox's clear and chip remove buttons ([data-ouro-combobox-clear], [data-ouro-combobox-chip-remove]) and NumberField's steppers ([data-ouro-increment], [data-ouro-decrement]). A remove button's hidden text holds the file or item name, so a loose getByText("pasta.png") now finds it too; pass { exact: true }. FieldLabel's className now goes on the new element around the label, which also carries [data-ouro-required], and a group's label id is on the inner <span>; style, id, data-* and handlers still go on the <label>. A numeric size on TextField is now a type error and a development warning; set a width with className="w-40". FormErrors values are now ErrorMessages | null | undefined, so code that reads errors.email as string | string[] no longer type checks until it handles null and React nodes.40 For action answers, import formErrors from @/lib/ouro/form-answer and pass errors={formErrors(actionData, actionData?.errors)} from the initial render; this replaces the unreleased separate result prop. It identifies equal new answers while keeping cleared fields masked on pending/client renders and rendering current uncleared messages. Cached/reused answers need { key: acceptedAnswerKey }, changed on actual acceptance, not submit start. Raw records remain compatible but content-only. Reconstruct the value beside Form after JSON/spread/cloning or server-component transport: only messages serialize, not private identity; a safely transported acceptance key can identify the locally reconstructed answer. On a phone, a Link that is not in running text is now a box at least 44px tall and wide that takes its room in the layout, so a column of links grows; pass inline to a link in a sentence inside your own <div> or <span>.41 A Form's top message element exists only while there is a message. Component roots no longer set m-0, so a parent's space-y-* now spaces them; remove any margin you added to work around that.40

  • v0.8.0: no component installs ouro-base; add it once (step 1 does), and later adds leave your stylesheet alone.40 A component installs only the helpers it imports, one item per file in lib/ouro, and a -tests item brings the tests of everything its component installs.40 The -tests items no longer set a version for Vitest, Storybook or Playwright.42 The specs import test and expect from e2e/ouro-fixtures.ts, a file of yours (step 1).40 lib/ouro/use-dev-warnings.ts is now lib/ouro/use-dev-messages.ts (step 4).40 Earlier releases installed every helper in lib/ouro with any component, and an update deletes nothing: audit references before deleting unused historical helpers and their own tests (step 5).40 OuroProvider now comes only with the components that render links and with tooltip; your copy stays, and a new app adds lib-ouro-provider by name.

  • After v0.8.2: ouroTestProjects({ page }) also sizes the touch project's Playwright host, preventing Vitest 4 from scaling landscape touch frames.43 The logical touch viewport still starts at 390x844 with mobile/touch input, but an explicitly enlarged host changes outer-window/screen dimensions and screen orientation; do not treat those as hardware-phone measurements.43 Without page, the touch host stays 390x844.43 On Vitest 4, retain a host large enough for every requested test viewport, such as the Testing guide's 1600x1200 example.43

  • v1.0.0: the first stable release keeps the component APIs, theme tokens, defaults and test-tool minimums from v0.8.4.44 An app already on v0.8.4 needs no component API migration: review the update with --dry-run and --diff, then overwrite only the items it already uses and reapply its local edits.44 Runtime components and their optional -tests items remain separate; keep your app-owned fixtures and test configuration. Apps on older releases still need the applicable migration steps above and below.

  • After v1.0.2: sheets, and a Menu shown as a bottom sheet, read their shadow from --ouro-shadow-sheet-<side>, and Checkbox's tick draws over --ouro-duration-draw, both of which only a fresh ouro-base adds (step 1 adds it again); without it a sheet draws no shadow and the tick appears at once.45 Every other shadow reads shadcn's --shadow-* tokens, and headings and overlay titles --font-heading, so a theme that sets them now restyles Ouro too.4546

  • After v1.0.1: formErrors now declares its already-frozen message arrays readonly.47 Keep mutable FormErrors and ErrorMessages for raw builders; annotate returned snapshots as FormErrorValue or ReadonlyFormErrors, or let their type infer. Form, EditSheet, field error props and the normalization helper accept readonly projections. This corrects a false mutation permission, so a snapshot no longer fits a mutable raw-map annotation.47 TypeScript's Array.isArray narrows to any[] and can bypass readonly; unchanged runtime freezing still rejects container mutation, without deeply freezing React nodes.

  • After v1.2.2: Button accepts reloadDocument for a document request through an internal href, and its downloads use a plain anchor like Link's.48 A false or null download no longer forces document navigation; pass reloadDocument when a resource route needs it without a download.48

  • After v1.2.4: Select, Menu, Combobox, Popover, Tooltip, HoverCard and NavigationMenu open their popups at the end of <body> unless OuroProvider is given a portalContainer, and in development each warns once when it opens outside every landmark of a page with a <main>.49 To keep popups in a landmark, make const popups = useRef<HTMLDivElement>(null), wrap the app in <OuroProvider portalContainer={popups}> and render <div ref={popups} /> last inside <main>, outside any element with a transform, filter or contain, which would clip or move the popups. In a Next.js App Router layout, which is a server component, put the provider and the ref in a small client component. Inside a dialog your app draws itself, wrap its content in <OuroProvider portalContainer={null}> so its popups open where Base UI puts them.50 Dialogs, sheets and toasts still open at the end of <body>. The landmark warning goes to console.warn, so a test that fails on console output fails until the popups are in a landmark. SidebarLayout's header now renders in a <header>, the page's banner, so remove a banner your page draws around SidebarLayout.51 The testing/axe kit has a new audit, which fails every violation at any impact and every result axe could not decide unless it measures it, in both color schemes.52 The ouro-testing item now names axe-core among its devDependencies, so an update adds it to an app that lacks it.53

  • After v1.2.8: the Toaster's Notifications region is no longer a live region: two live regions last inside it say each toast's message and description, the assertive one a role="alert", so for its first seven seconds a toast's words are in the page twice.54 Find a toast in a test by its role and name, getByRole("dialog", { name: "Saved" }), or "alertdialog" for a destructive toast, not with getByText, and expect no aria-live on the region. Dismiss buttons and destructive toasts are no longer hidden from screen readers while the stack is collapsed.55 The testing/axe kit's audit now leaves focus, the selection and every scroll position as it found them, and throws, naming what changed, when its Tab walk closes a popup it cannot open again; audit the page before that popup opens.56

  • After v1.2.14: Avatar's initials take a hue from the name, from the tokens --avatar-1 to --avatar-8, which only a fresh ouro-base adds (step 1 adds it again); without them the initials show with no tint.57 Pass color="neutral" to keep the plain grey. Avatar's presence dot sits over its edge, so the picture now rounds its own corners instead of the avatar clipping it.58

  • After v1.3.3: Avatar's initials and AvatarGroup's "+N" count are drawn with CSS from a data attribute and are no longer text in the page, so a link or button that wraps an avatar and names itself passes WCAG 2.5.3, Label in Name.59 A test that found an avatar by its letters, getByText("AL"), or expected them in a row's text, now finds it by role and name, getByRole("img", { name: "Ana Lind" }), or reads data-ouro-avatar-initials.59

  • After v1.3.4: ShowMore no longer reads estimate, which it keeps only so an app that passes it still compiles; drop it when you like.60

  • After v1.3.5: several default failure messages changed, so a test that matches the old words needs the new ones.61 SyncStatus's error label is "Not saved: the server refused the changes". CopyButton and ShareButton show "Browser blocked copying" or "Browser blocked sharing" for two seconds, with a sentence their new failedMessage sets. ImageCropper's labels.openError and ImageField's refusal begin "The browser can't read". EditSheet's rejected save says "Your changes were not saved: " with the error's own message, so reject onSubmit with a message a person can read, or pass failureMessage. MessageList draws one action bar for all its messages, on the message the pointer is over, so a test that looked for a bar inside each message finds that one; a Message outside a MessageList keeps its own.61

  • After v1.3.6: Moment's quiet is words only: the compact row drops its 40px art (a loading spinner stays), and OuroProvider's quiet sets it for the whole app; for a compact moment with art, use size="inline" and its 24px art instead.62 Celebrate's confetti stays inside its own box, so give it room with layout classes (h-24 w-full) for a wider burst. At phone width Navbar builds its menu sheet while the page is idle and keeps it hidden between opens, so a test that finds something in the bar by its label or text alone (getByLabelText("Menu"), getByText("Recipes")) also finds its copy in the sheet: find it by its role. Porthole's fresh headline reads "Up to date", not "Updated just now"; pass labels.fresh for other words. The site and the @ouro namespace moved to https://ui.ouroboros.bot/, so set "@ouro": "https://ui.ouroboros.bot/r/{name}.json" in components.json; the old host forwards until you do.62

  • After v1.3.9: Ouro check has new gated rules (giant-picture, cramped-heading, nested-frame, baked-corner, type-scale, shadow-scale, grey-on-colour, heading-hierarchy, jank, repaint and, for a site on the web, https), and blurry-image covers videos, so a run that passed can now report findings.63 Fix them, or let a rule report without failing while you do: waive it in ouro-check.json with the reason, and remove the waiver once its findings are fixed. jank walks every page at one size and theme; "walk": false turns it off.63

  • After v1.3.9 (Navbar): on a page below a link, Navbar and NavigationMenu mark that link aria-current="true", not "page", so a test that expected "page" on a section's link finds "true"; the current page is underlined in the primary colour instead of a bar at the start edge.64

  • After v1.3.10: Ouro check has new gated rules, console-error (an uncaught error, console.error or console.warn while a page loads), failed-request (a request to the page's own server that fails), off-screen (text or a control the screen cuts off), duplicate-control (the same control twice in view), stray-scrollbar (a region that scrolls by a few pixels), leaked-value (undefined, NaN, [object Object], an id, a translation key or a TODO: on screen) and odd-timestamp (Invalid Date, 1 January 1970, a raw or machine timestamp, a negative age) stretched-image (a picture or a video drawn out of its own shape), focus-ring (a control that shows no keyboard focus), dark-miss (a light surface left on a dark page) and disabled-no-reason (a disabled control with no reason tied to it), so a run that passed can now report findings.65 Fix them, or mark people's own words, such as a message list, with data-ouro-allow="leaked-value odd-timestamp" and a reason.65 check.ts imports four new files, text-check.ts, layout-check.ts, theme-check.ts and interaction-check.ts: if your app pins the check's files in a list, add registry/ouro/check/text-check.ts, registry/ouro/check/layout-check.ts, registry/ouro/check/theme-check.ts and registry/ouro/check/interaction-check.ts to it.65 blurry-image can no longer be waived: it holds a picture to exactly its file's pixels at the density, checks raster CSS backgrounds too, reports data-ouro-allow="blurry-image" as a finding of its own, and a waiver of it in ouro-check.json makes the check exit 2.66 Give each picture it reports a larger source; --images-report <file.json> lists them with the width each needs. ouro-images refuses a picture too small to draw sharp, and exits 1 for it.66

  • After v1.3.10 (Skeleton): Skeleton's shimmer slides a band with translate on the keyframes ouro-shimmer-band, which only a fresh ouro-base adds (step 1 adds it again); without it the band holds still in the middle, as it does with reduced motion.67

  • After v1.3.11: Chart draws its legend in a row below the plot, so a chart with a legend is one row taller.68 A centered Dialog whose body scrolls opens with focus on the body's first control, or its close button, not on the scrolling body.68

  • After v1.3.12: an icon-only Button sizes its svg child as it sizes startIcon, unless the icon has a size class of its own, so an icon that kept its own fixed size now matches startIcon.69

  • After v1.3.14: the testing kit keeps each test's media emulation (reduced motion, forced colours, colour scheme and contrast) to the test that set it, so a test that relied on one before it leaving reduced motion on now sets it itself, or in its file's beforeAll.70

  • After v1.3.16: the testing kit moves the mouse off the page before each test in the mouse projects, so a test that relied on a hover left by the one before now hovers itself.71

  • After v1.3.19 (Hero): Hero's picture takes { light, dark }, the same scene by day and at night, and no longer a single picture, so a hero that passed one picture fails to compile until it passes both; the dark one shows while the page is in dark mode.72 Ouro check has a new gated rule, hero-dark-art (a hero whose picture has no night art), so a run that passed can now report it.73

  • After v1.3.20: ouro-images installs to @lib/ouro/images/, beside the Ouro AX kit it now runs on, where it was ~/lib/ouro/images/, so re-adding it leaves the old copy behind: delete lib/ouro/images/ at your project root once nothing imports it, and point any script that ran it at the new path.74 A refused option prints one line with the fix and exits 2.74

  • After v1.3.33: ouro-check installs to @lib/ouro/check/, beside the Ouro AX kit it now runs on, where it was ~/lib/ouro/check/, so delete lib/ouro/check/ at your project root once nothing imports it, and point any script that ran it at the new path.75 Its --help and --json come from the kit, a refused option or target prints one line with the fix and exits 2, and a flag given twice is refused, except --ready-event, which repeats.75 ax-flows runs on the same kit: a refused option or suite module prints one line with the fix and exits 2 (a missing suite module or X-ray report used to stop with a stack trace and exit 1), and under --json stdout holds the outcome alone, with each flow's line on stderr.76

  • After v1.3.34: Ouro check has new gated rules for text that wraps by accident, lone-word, tall-heading, one-word-line, hard-break and narrow-heading, and dark-art, a dark page that shows its light theme's hero art, so a run that passed can now report findings.77 Balance headings and wrap prose pretty, give hero art a night twin, or mark a finding you keep on purpose with data-ouro-allow and a reason.77 check.ts imports a new file, wrap-check.ts: if your app pins the check's files in a list, add registry/ouro/check/wrap-check.ts to it.77

  • After v1.3.45: ax-xray runs on the Ouro AX kit, and its --json report is unchanged, but under --json a refused option now prints its error as JSON on stdout, where it printed nothing, so a script that reads xray.json must gate on the exit code.78

  • After v1.3.49 (Dialog): a Dialog opened from the keyboard whose body has nothing to focus now gives first focus to a field in its footer (an input, a text area or a select), where it gave it to Close, so a test that expected Close there now finds the field.79

0. Commit first

--overwrite replaces every Ouro file, including files you edited.80 Commit before you start. Afterwards, git diff shows your own edits being overwritten, so reapply the ones you need.

Review the diff of your Tailwind CSS file too, the one components.json names in tailwind.css, because step 1 adds ouro-base, the one item that writes there, and shadcn add rewrites more than Ouro's tokens. It adds @custom-variant dark (&:is(.dark *)); when the file has no @custom-variant (shadcn@4.21.1 does this for any item with theme variables on Tailwind CSS v4), adds the tokens your file lacks to :root, .dark and @theme inline (among them --input, --radius and the radius scale, if your theme has none), and collapses blank lines.81 Keep the new lines and step 2's deletions, and revert the rest. After this upgrade no component add opens the file again.40

1. Update every Ouro component, with its tests

Update every Ouro component in your app, not only the items you typed: v0.1.0 added each component's dependencies too, so an app that added only dialog also has button and sheet. Each component is one .tsx file in your components/ouro/ folder (dialog/ holds dialog and confirm-dialog, and text/ holds text and heading).33 Add each one as <name> and <name>-tests, plus ouro-base, in one command at the same ref, with --overwrite (without it the CLI stops to ask about each file). For the app that added only dialog, that is ouro-base button button-tests dialog dialog-tests sheet sheet-tests. No component installs ouro-base any more, so this is where your stylesheet gets the tokens added since v0.1.0.40

This command finds the components, prints them and adds them. If it finds none, it stops before adding anything82; run it in the folder with components.json. If you use the Ouro theme, add ourostack/ouro-ui/ouro-theme#v1.3.17 to the add in it.

( p() { npx shadcn@latest info --json | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).config.resolvedPaths.$1"; }; ouro="$(p components)/ouro"; items=$(find "$ouro" -name '*.tsx' ! -name '*-parts.tsx' ! -name '*.test.tsx' ! -name '*.stories.tsx' 2>/dev/null | sed -E 's|.*/(.*)\.tsx$|\1|' | sort); [ -n "$items" ] || { echo "No Ouro components in $ouro. Run this in the folder with components.json." >&2; exit 1; }; echo "Updating: $(echo "$items" | paste -sd " " -)"; npx shadcn@latest add $(echo "$items" | sed -E 's|.*|ourostack/ouro-ui/&#v1.3.17 ourostack/ouro-ui/&-tests#v1.3.17|') ourostack/ouro-ui/ouro-base#v1.3.17 --overwrite )

This keeps the tests v0.1.0 installed, deletes none of your files and adds no component you didn't have. shadcn may print Skipped N files: (files might be identical, use --overwrite to overwrite) even with --overwrite: those files already equal the release's, so nothing was missed.80 The -tests items name Vitest, Storybook and Playwright without a version, so the versions in your package.json stay as they are. v0.7.0's items still carry versions: after an add at v0.7.0, restore every test-tool version you had pinned (git diff package.json): Vitest and its companions, Playwright and Storybook.83 The tests need Vitest 4.1.11 or later, Playwright 1.58.1 or later and Storybook 10.2.10 or later; update those yourself if yours are older (the README's "Details" links the Testing page).84 The specs in e2e/ouro now import test and expect from e2e/ouro-fixtures.ts, a file of yours: add it with the one line export { expect, test } from "@playwright/test";, or export your own fixtures from it.

2. Remove the self-referential theme lines

v0.1.0 wrote a line into @theme inline for each Ouro token that points at itself, such as --ouro-press-scale: var(--ouro-press-scale);, and --color-ouro-backdrop-color: var(--ouro-backdrop-color);.85 Delete them from your Tailwind CSS file, the one components.json names in tailwind.css. The values stay in :root and .dark. This command finds the file and deletes the lines:

perl -i -ne 'print unless /^\s*--(color-)?ouro-[\w-]+:\s*var\(--ouro-[\w-]+\);\s*$/' "$(npx shadcn@latest info --json | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).config.resolvedPaths.tailwindCss")"

3. Optional: drop Ouro's tests

To keep only the components, delete the files Ouro owns for its tests: the test, type-test and story files in your components/ouro/ and lib/ouro/ folders, the test kit in lib/ouro/testing and the specs in e2e/ouro. The command stops if it can't find those folders.

( p() { npx shadcn@latest info --json | node -p "JSON.parse(require('fs').readFileSync(0, 'utf8')).config.resolvedPaths.$1"; }; c="$(p components)/ouro"; l="$(p lib)/ouro"; [ -d "$c" ] && [ -d "$l" ] || { echo "No Ouro folders at $c and $l. Run this in the folder with components.json." >&2; exit 1; }; find "$c" "$l" \( -name '*.test.ts' -o -name '*.test.tsx' -o -name '*.test-d.ts' -o -name '*.stories.tsx' \) -delete; find "$l/testing" e2e/ouro -delete )

Then remove the ouroTestProjects import and call from your Vitest config. Only if Ouro's tests were the only tests using them, also delete vitest.config.ts, playwright.config.ts and .storybook/, and uninstall vitest, @vitest/browser-playwright, playwright, @playwright/test, storybook and @storybook/react-vite with your package manager.

4. Change imports of moved helpers

If your own code imports these helpers, change the import:

  • useOuro: from lib/ouro/ouro-provider to lib/ouro/use-ouro.
  • sheetSides, sideWarning, sheetHint, iosBackdrop and footerClass: from components/ouro/sheet/sheet-parts to components/ouro/sheet/sheet-styles.
  • dialogSizes and dialogSizeWarning: from components/ouro/dialog/dialog-parts to components/ouro/dialog/dialog-styles.
  • alignClasses and checkAlign: from components/ouro/text/text to components/ouro/text/text-styles.
  • useDevWarnings: from lib/ouro/use-dev-warnings to lib/ouro/use-dev-messages. Delete the old lib/ouro/use-dev-warnings.ts; nothing imports it after step 1.

5. Delete the helpers nothing imports

Releases before v0.8.0 installed every helper in lib/ouro with any component.40 From v0.8.0 a component installs only the helpers it imports, but an update deletes nothing.40 Remove unused historical helpers rather than excluding them from coverage.

Before deleting anything, perform a read-only import/reference audit across the whole app, including scripts, tests, stories and configuration outside src/. Use the app's actual tsconfig and bundler aliases to resolve references to each helper's full path. Check relative imports, explicit extensions, re-exports and dynamic imports too; an importing file with the same basename is still a different file. A text search for lib/ouro/<name> alone cannot prove a helper is unused.

Delete a helper and its own tests only after verifying that no remaining app file or retained Ouro file references it (references from that helper's own tests do not require keeping an otherwise unused helper). Check again after each removal: another historical helper may have been used only by the one you removed. If a reference is unresolved or you cannot verify a file's use, keep it until you can. Keep ouro-provider.tsx if you mean to use it and have not yet, and retain ambient declarations such as env.d.ts that the app's type check needs even without an import.

Then type check and build your app. CI runs steps 1 to 3 on a v0.1.0 app, with these commands read from this file (scripts/install-test.sh upgrade).35

Items

Components: a2ui-surface, accordion, agent-presence, alert, aspect-ratio, attachment, attention-card, auth-layout, autocomplete, avatar, badge, breadcrumb, button, button-group, calendar, card, carousel, celebrate, chart, checkbox, checkbox-group, checklist, choice-card, code-block, collapsible, color-field, color-scheme-toggle, combobox, command, composer, confirm-dialog, conversation-list, copy-button, date-picker, description-list, dialog, edit-sheet, emoji, emoji-picker, empty, family-bar, family-footer, field, fieldset, file-field, form, heading, hero, hover-card, image-cropper, image-field, inspector, install-prompt, item, item-group, kbd, level-meter, lightbox, link, live-captions, local-date, media-tile, menu, message, message-list, moment, native-select, navbar, navigation-menu, navigation-rail, number-field, otp-field, ouro-mcp-app, page-header, pagination, popover, porthole, progress, radio-group, reaction-bar, relative-time, reorderable, resizable, route-progress, row, scroll-area, segmented-control, select, separator, share-button, sheet, show-more, sidebar, sidebar-layout, skeleton, skip-link, slider, snap-pip, spinner, stack, stacked-layout, swipe-actions, switch, sync-status, tab-bar, table, tabs, text, text-area, text-field, thread-rail, thread-summary, time-grid, timer, timer-tray, toaster, toggle, toggle-group, toolbar, tooltip.

Each component has an optional <name>-tests item (button-tests) with its Vitest browser tests, type tests, stories and Playwright spec; it also installs the tests of everything the component installs. ouro-tests adds every component with all of its tests, and ouro-testing is the test kit the -tests items bring.

Once per app: ouro-base, the tokens every component needs in your stylesheet. Optional: ouro-theme, the Ouro look.

Ouro AX, for the interfaces agents use (Ouro AX): the example tool ax-example, ouro-ax-tests with their <name>-tests items, and ouro-ax, which is: ax-checks, ax-claims, ax-cli, ax-command, ax-component, ax-dev-commands, ax-effects, ax-exit-codes, ax-flows, ax-lint, ax-mcp, ax-outcome, ax-output, ax-precise-copy, ax-problem, ax-surfaces, ax-terminal, ax-voice, ax-wait, ax-work-event, ax-xray.

Guardrails, optional: ouro-census, which holds the pages and states your journeys list to a baseline of their visible controls, ouro-source-rules, which finds the controls your source still builds by hand, ouro-check, which finds what looks broken in your built pages, from a phone to a wide desktop, and ouro-images, which writes your pictures at the widths a srcset needs (Guardrails guide).

Helpers, one item per file in lib/ouro, each installed with the components that import it (you don't add them by name; ouro-lib is all of them, and a helper with tests has a <name>-tests item): lib-a2ui-core, lib-announce, lib-appearance-hints, lib-celebration-cap, lib-claims, lib-class-name-check, lib-cn, lib-color-scheme, lib-color-scheme-script, lib-control-ref, lib-current-path, lib-dev, lib-env, lib-feedback, lib-floating, lib-focus-handoff, lib-form-answer, lib-halve, lib-insert-text, lib-keep-clear, lib-key-shortcuts, lib-last-input, lib-mcp-app, lib-measure-interaction, lib-navigation-key, lib-open-mode, lib-ouro-provider, lib-page-scroll-hold, lib-people-cache, lib-portal-container, lib-porthole-view, lib-refs, lib-router-link, lib-row-hover, lib-scroll-fade, lib-server-element, lib-share, lib-spring, lib-squircle-polyfill, lib-stretched-link, lib-toast, lib-tones, lib-touch-target, lib-use-backdrop-anchor, lib-use-command-shortcut, lib-use-dev-messages, lib-use-drag, lib-use-field, lib-use-install-prompt, lib-use-keyboard-scrollable, lib-use-media-query, lib-use-mounted-open, lib-use-ouro, lib-use-person, lib-use-reduced-motion, lib-use-sheet-presentation, lib-use-timer. No component imports lib-claims: add it by name to mark your own copy's claims with claim().

For agents

Before you call a screen done, run node lib/ouro/check/check.ts <built folder> and fix what it reports (the screen check).

Each component has a page in docs/ with its purpose, an example, every prop, its modes and what to use instead.86 The same text is in the JSDoc, so your editor shows it on hover.

  • Props first. A component's props describe the whole thing. Parts (dialog-parts, sheet-parts) are an escape hatch, only for layouts the props cannot express.
  • What a component needs is required. A Dialog or Sheet needs a title; hide it with hideTitle.
  • Appearance comes from named props: tone (primary, secondary, destructive, success, warning, named after the theme variables they read, with shadcn's meaning), variant and size, in place of color names.
  • className is for layout only: margins, width and grid placement. Components merge your className with cn, where a later class wins.87 Appearance classes (bg-*, text-<color>, rounded-*, font-*, dark:*…) warn in development with the prop to use instead.
  • The prop you guess is the prop that exists, or the type error names the real one: isLoading says to use loading, and shadcn's variant="destructive" says to use tone="destructive".
  • Modes follow the types. href makes a Button a link; a Dialog takes either trigger or open with onOpenChange.
  • Dev messages teach, in one format: [ouro-ui] <Component>: <what's wrong>. Fix: <exact code>. They are stripped from production builds.
  • axe flags Base UI's focus guards; exclude them. A popup that holds focus, such as a Dialog or a Menu, has Base UI's focus guards: visually hidden spans that keep focus in the popup.88 Plain axe reports them as aria-hidden-focus in Chromium (as an undecided result, incomplete) and as aria-command-name in WebKit on Apple platforms, where they take role="button" for VoiceOver. That is expected: new AxeBuilder({ page }).exclude("[data-base-ui-focus-guard]"), or spread axeExclude from lib/ouro/testing/axe.ts (in ouro-testing).

Agent plugin

plugins/ouro-check ships the ouro-check agent skill as a plugin. It teaches an agent to run Ouro check on a site, a built app or a page in a test, and to fix what its JSON findings report without looking at screenshots. Install it with Agency (agency plugin install github:ourostack/ouro-ui:plugins/ouro-check@main --cache-policy auto --cache-ttl 0) or point any plugin-aware host at that folder. The skill moved here from ourostack/ouroboros-skills; change it here, in the same pull request as the change to the check it describes.

Components

Actions

  • Button — A button, or a link styled as one. Docs
  • ButtonGroup — Related buttons joined into one control, such as Previous, a page number and Next, or a split button. Docs
  • canShare — Whether the browser has a share sheet for this data: most phones and Safari do, most desktop browsers do not. Docs
  • colorSchemeScript — The script that sets the page's color scheme before it paints, so a reload in dark mode never flashes light. Docs
  • ColorSchemeToggle — The control that switches the app between light, dark and the system's scheme, and keeps the choice. Docs
  • CopyButton — A button that puts a piece of text on the clipboard: an API key, a link, a command. Docs
  • copyText — Puts text on the clipboard: through the Clipboard API, or a text selection where the page may not use it (plain http, an older browser). Docs
  • share — Shares a link through the system's share sheet, or copies it where there is none, and says which happened. Docs
  • ShareButton — A button that shares a link: through the system's share sheet where there is one (phones, Safari), and by copying it where there is not, when it says "Link copied" for two seconds and screen readers hear it once. Docs
  • Toggle — A button that stays pressed until it is pressed again, such as Bold in a text toolbar. Docs
  • ToggleGroup — A row of related toggles that share one value, such as text alignment or bold, italic and underline in a toolbar. Docs
  • Toolbar — A row of controls for one surface, such as an editor's formatting: Button commands and Toggle states, in groups with separators. Docs
  • useColorScheme — The person's color scheme, the scheme the page shows, and the setter that changes both, for a control of your own. Docs

Text

  • claim — Marks a sentence of copy as a claim, with the primary source it comes from and a check that it still holds. Docs
  • Heading — A section title, <h1> to <h6>, in the theme's foreground color. Docs
  • Kbd — A keyboard shortcut, in the reader's platform's notation: keys={["mod", "k"]} shows ⌘K on a Mac, iPhone or iPad and Ctrl+K elsewhere. Docs
  • Link — A text link that navigates through your router. Docs
  • LocalDate — A date (and time) in the reader's own time zone and language, in a <time> that carries the exact instant. Docs
  • RelativeTime — How long ago (or how soon) something happens, in the reader's language: "3 days ago", "yesterday", "in 5 minutes", "now", kept current while it shows. Docs
  • Text — Body text in the theme's type scale and colors. Docs

Forms

  • Autocomplete — Suggestions at the text caret: typing a trigger character, such as @ for people or : for emoji, in a TextArea or TextField opens a list beside the caret, filtered by what follows the character. Docs
  • Calendar — A month grid to pick a day or a range of days, with the arrow keys or a press. Docs
  • Checkbox — A labeled checkbox on the Field contract, with the label to its right: pass label, and checked with onCheckedChange (or defaultChecked). Docs
  • CheckboxGroup — A group of checkboxes under one legend, on the Field contract, for picking any number of choices: pass legend, and options (or Checkbox children). Docs
  • ChoiceCard — A set of choices shown as cards, each with a picture, a label and a line of description, for picking exactly one: a cover photo, a plan, a layout. Docs
  • ColorField — A color on the Field contract: a swatch that opens the system color picker, next to the same color as hex text, kept in step both ways. Docs
  • Combobox — A text input that filters a list of items as people type, on the Field contract: pass label, items, and error from your server action or client check. Docs
  • DatePicker — A labeled date field. Docs
  • Field — A labeled form control: the label, an optional description, the control and its error, wired together with ids that match between the server and the browser. Docs
  • Fieldset — A titled group of related fields, such as an address, rendered as a <fieldset> named by its legend. Docs
  • FileField — A labeled file input with a drop zone, on the Field contract. Docs
  • Form — A form that validates with the browser's own constraints (required, type="email", pattern) and shows server errors on the matching fields. Docs
  • ImageCropper — A dialog for cropping a photo to a shape before it is used: drag the photo (or move it the way the arrow keys point) to choose what shows in the frame, zoom with the slider (or Plus and Minus), and Save hands the cropped image to onCrop as a File, ready for a form or an upload. Docs
  • ImageField — A field for one photo, shown large: the photo already saved (src) or the one just chosen, with Choose photo (Change photo once one shows) and Remove. Docs
  • NativeSelect — A labeled native <select> on the Field contract: pass label, options (or <option> children) and error. Docs
  • NumberField — A labeled number input on the Field contract: pass label, and error from your server action or client check. Docs
  • OtpField — A one-time code entry on the Field contract: one slot per character, with focus that moves as the person types, a whole code pasted at once, and autoComplete="one-time-code" so phones offer the code from a text message. Docs
  • Radio — One choice in a RadioGroup, with its label to its right. Docs
  • RadioGroup — A group of radios under one legend, on the Field contract, for picking exactly one choice: pass legend, and options (or Radio children). Docs
  • Select — A labeled list of choices on the Field contract: pass label, options (or SelectItem children) and error from your server action or client check. Docs
  • Slider — A labeled slider on the Field contract: pass label, and a defaultValue (or value with onValueChange). Docs
  • Switch — A labeled on/off switch on the Field contract, with the label to its right: pass label, and checked with onCheckedChange (or defaultChecked). Docs
  • TextArea — A labeled multi-line text field on the Field contract, a native <textarea>: pass label, and error from your server action or client check. Docs
  • TextField — A labeled one-line text input on the Field contract: pass label, and error from your server action or client check. Docs

Overlays

  • ConfirmDialog — Asks the user to confirm an action, with Cancel focused first. Docs
  • Dialog — A dialog that presents itself for the screen: a centered dialog on wider screens and a bottom sheet you can drag away on phones (below 640px). Docs
  • EditSheet — Edits something in a form inside a dialog: the whole interaction, done right. Docs
  • HoverCard — A preview of where a link goes, shown when a mouse rests on the link or the keyboard focuses it: a cook's profile, a recipe's summary. Docs
  • Popover — A small non-modal panel beside its trigger, for a short form or a few choices: a share link, filters, a quick edit. Docs
  • Sheet — A panel that slides in from an edge and is dragged back to dismiss: the mobile form of a dialog. Docs
  • Tooltip — A short label that appears beside its trigger on hover and keyboard focus, for an icon button's purpose or a control's shortcut. Docs

Menus

  • Command — A command palette: a searchable list of every action in the app, in a dialog. Docs
  • Menu — A list of actions that opens from a button, from a right-click or long press, or from a menubar, with one API for all three. Docs
  • MenuBar — A row of menus, as at the top of a desktop app: File, Edit, View. Docs
  • MenuCheckboxItem — An on/off setting in a Menu, with a check mark when it is on. Docs
  • MenuGroup — Items under a label in a Menu, such as "Recipe" and "Account" sections. Docs
  • MenuItem — One action in a Menu: onClick runs it, href makes it a link, shortcut shows its keys and tone="destructive" marks an action that cannot be undone. Docs
  • MenuRadioGroup — One choice of several in a Menu, such as a sort order: its MenuRadioItems show a mark on the chosen one, and choosing keeps the menu open. Docs
  • MenuRadioItem — One choice in a MenuRadioGroup, marked when it is the group's value. Docs
  • MenuSeparator — A line between items in a Menu, to set apart a group of actions such as the destructive ones at the end. Docs
  • MenuSub — A nested menu in a Menu: an item that opens more items beside it, on hover, click or the arrow key. Docs

Feedback

  • AgentPresence — The person's always-visible sign of which of their agents are listening to something on their behalf, such as a meeting, with Stop one click away. Docs
  • buzz — Vibrates the device in a short alert pattern, where it can (most Android phones; not iOS or desktops). Docs
  • Celebrate — A small moment of celebration for something the person finished: confetti bursts from a badge with the message ("List cleared") and screen readers hear the message once. Docs
  • chime — Plays a short alert, three soft tones, for something that just finished: a timer at zero. Docs
  • createChimeContext — Makes an audio context for chime, or returns null where the browser has no Web Audio. Docs
  • formatDuration — Seconds as a clock reads them: 9:05, or 1:02:03 from an hour up. Docs
  • formatPortholeAge — An age in ms as short English words, rounded down so it never claims more recent than it is: "less than a minute", "4 min", "3 hr", "1 day", "2 days". Docs
  • InstallPrompt — A quiet offer to install the web app, shown only where the browser can install it (Chromium on Android and desktops), never while it already runs installed, and not again for two weeks after Not now. Docs
  • LevelMeter — A small live input level: a row of bars that light from the start edge as a microphone picks up sound, so the person can tell it hears them before anyone else does (before joining a call, in a device menu, in a "test your microphone" row). Docs
  • Moment — A moment in an app's life, shown in the space it is given: nothing here yet, on its way, something failed, a first run, or something finished. Docs
  • MomentProvider — Sets quiet for every Moment inside it, so an app can make its moments compact everywhere, for a dense layout or for people who asked for less. Docs
  • Porthole — The status spot: a small round window with one line about how current the app's data is, which opens into the facts behind it. Docs
  • portholeStatus — The agent read of a view: the stable, versioned JSON an agent reads through the status spot's status read and a CLI status command prints, so a person, the window and an agent see one truth. Docs
  • portholeView — Decides what the status spot shows, as a pure function of the service's snapshot, its last heartbeat and this clock, so a window, a CLI and a test reach the same answer. Docs
  • Progress — A bar that shows how far along a task is, or that it is under way when you can't tell. Docs
  • resetTimer — Cancels or dismisses a timer by its timerId, including when its screen is not mounted. Docs
  • restoreTimer — Imports a saved countdown, leaving an existing native timer intact. Docs
  • RouteProgress — The thin bar across the top of the screen while the app loads the next page or submits a form, so a click that waits on the server never feels frozen: one global affordance instead of a pending state on every action. Docs
  • Spinner — A small spinning ring that says an action is under way, inline: in a field's adornment, a toast's or a menu entry's icon, or beside the words that say what is happening. Docs
  • SyncStatus — One line that says where the person's changes are: Saved, Saving…, Offline (kept on this device) or Not saved (the server refused the changes), each with its icon, and Retry while there is a problem. Docs
  • Timer — A countdown timer with its own controls: the time left in large numbers, Start, then Pause and Cancel, Resume when paused, and at zero "Time's up" with Dismiss and Restart. Docs
  • TimerTray — The timers that are running, paused or finished, in one strip, so a person who leaves a timer's screen can still see it count down, hear it at zero, and stop it. Docs
  • toast — Shows a toast: a short message that appears at the bottom of the screen, is read to screen readers once, and dismisses itself. Docs
  • Toaster — Shows the toasts you send with toast("Saved") from @/lib/ouro/toast: render it once, at the root of your app, inside OuroProvider if you use one, or alone. Docs
  • useInstallPrompt — Whether the browser offers to install this web app, and the means to offer it at a good moment rather than when the browser chooses: install() opens the browser's own install dialog, dismiss() puts the offer away for two weeks. Docs
  • useTimer — A countdown timer that keeps time by the clock, not by ticks, so it stays right in a background tab, across a reload (with timerId) and when the screen sleeps. Docs

Display

  • A2UISurface — Shows the UI an agent composed in A2UI, with Ouro HX. Docs
  • Alert — An inline message in the page: something saved, something failed, something to know before going on. Docs
  • Avatar — A person's picture, or their initials until it loads or when it fails, in a color picked from the name. Docs
  • AvatarGroup — Avatars stacked with a slight overlap, such as the cooks on a recipe, with a "+N" count after max. Docs
  • Badge — A short label for a status, category or count: "Draft", "Vegan", "3". Docs
  • Card — A surface that groups one thing's content: a media slot across the top, a title with a description and an action, a body, and a footer. Docs
  • Carousel — A row of slides the user swipes or steps through with Previous and Next buttons: recipe photos, a few featured cards. Docs
  • CodeBlock — Code or a command on a muted surface in the theme's monospace font, with a caption and a button that copies it. Docs
  • DescriptionList — One thing's details as pairs of a term and its value: a recipe's prep time and servings, an order's date and total, an account's email. Docs
  • Empty — What a list, search or page shows when it has nothing in it: an icon, a title that says what is empty, a sentence on what to do, and the action that does it. Docs
  • Lightbox — Thumbnails that open a full-screen image viewer. Docs
  • LiveCaptions — A caption band for a live call or video: what people are saying, as they say it. Docs
  • MediaTile — A tile for one live picture in a call: a person's camera, their photo when the camera is off, or a shared screen. Docs
  • OuroMcpApp — The view an MCP server returns for agent-composed UI (MCP Apps): the host, such as Claude or ChatGPT, frames it, and it shows the latest tool result's structuredContent.a2ui with A2UISurface. Docs
  • Separator — A thin line in the theme's border color between groups of content, horizontal or vertical. Docs
  • Skeleton — A shimmering placeholder in the shape and size of content that is still loading, so the content takes its place without moving anything. Docs

Lists

  • Accordion — A stack of titles that each open their own content, for FAQs and long settings pages. Docs
  • Checklist — A list of things to tick off, such as a recipe's ingredients or a shopping list: each row a checkbox the whole row toggles, with an optional value and action. Docs
  • Collapsible — One section that opens and closes under its title, such as nutrition facts under a recipe. Docs
  • Item — One row of a list: leading media, a title with a description, and trailing actions. Docs
  • ItemGroup — A list of Item rows, with an optional label that names it, lines between the rows, and what to show when it is empty. Docs
  • Reorderable — A list the user puts in order: recipe steps, a queue, favorites. Docs
  • ShowMore — "Show more" for a long list or a long text. Docs
  • SwipeActions — A list row with actions beside it that a horizontal swipe reveals: swipe toward the start for the trailing actions (Delete, Archive), toward the end for the leading ones (Pin). Docs

Layout

  • AspectRatio — A frame that keeps an image, video or map at a fixed shape while its width follows the layout, so the page does not jump when the media loads. Docs
  • Hero — The top of a page: a headline with an optional line above it (eyebrow) and a status beside that (badge), a trail above them (breadcrumb), a lede under the headline (description), the page's buttons (actions), more such as a byline (children), and a big picture, or something live in its place (media), below. Docs
  • PageHeader — The top of a page: its title with an optional line above it (eyebrow), a description under it, the page's actions beside it, and a trail and an image above. Docs
  • Resizable — Panels side by side (or stacked) with a draggable handle between each pair: a file list beside an editor, a preview above a console. Docs
  • ResizableHandle — The draggable line between two panels in a ResizablePanelGroup, a focusable window splitter (role="separator") for the panel before it. Docs
  • ResizablePanel — One resizable area of a Resizable (or ResizablePanelGroup), sized in percent of the group. Docs
  • ResizablePanelGroup — A group of panels with handles you place yourself, shadcn's shape: ResizablePanel and ResizableHandle elements as direct children. Docs
  • Row — Lays its children out side by side with an even gap, centered up and down. Docs
  • ScrollArea — A scrolling region with a slim scrollbar that matches the theme, inside a height you set. Docs
  • SnapPiP — A floating picture inside a container, such as the person's own camera in a call, that they drag and throw into a corner, where it comes to rest, as a phone's picture in picture does. Docs
  • Stack — Lays its children out top to bottom with an even gap. Docs

Navigation

  • AuthLayout — The page for signing in, signing up or resetting a password: one Card in the middle of the screen, with your brand, the title as the page's <h1>, the form, and a line under the card. Docs
  • Breadcrumb — The trail from the top of the site to the current page: a labelled <nav> with an ordered list of links, the last item marked aria-current="page". Docs
  • FamilyBar — The slim strip above a site's own header that links every site of a family, the same links in the same order on each, with the site being shown marked aria-current="true", in the text colour, underlined in the primary colour as Navbar marks the current page. Docs
  • FamilyFooter — The foot of a family's site: the FamilyBar's links, in the same order, in a quiet row that wraps, with the site being shown marked the same way, and the site's own extras (children) after them. Docs
  • Navbar — The bar across the top of an app: your brand, the main links and a few controls, with the current page marked aria-current="page", and the link of the section it is in aria-current="true", both underlined in the primary colour as in FamilyBar, distinct from hover and press highlights. Docs
  • NavigationMenu — A row of links where some open a panel of further links, each with a line of description: the navigation of a site with more pages than fit in a bar. Docs
  • NavigationRail — The vertical bar of the app's top-level pages on the start edge of a wide screen, as in a chat or mail app or an editor's activity bar: each item an icon over a short label, the current one with its currentIcon, a bar on the rail's edge and aria-current="page". Docs
  • Pagination — Page navigation for a long list: Previous, the page numbers with gaps, and Next, with the current page marked aria-current="page". Docs
  • SegmentedControl — An iOS-style segmented control: a row of equal segments with a raised thumb that slides to the checked one, for switching how one view shows its content in place, such as list or grid. Docs
  • Sidebar — The column of links down the side of an app, in sections, with the current page marked aria-current="page" and a bar at the start edge, distinct from hover and press highlights. Docs
  • SidebarLayout — The shell of an app with a Sidebar: the sidebar beside the page from 640px up, in view while the page scrolls, and a sticky top bar with the sidebar's menu button below 640px. Docs
  • SkipLink — The "Skip to main content" link a keyboard or screen-reader user meets first on every page, so one Tab and Enter jump past the header and navigation. Docs
  • StackedLayout — The shell of an app with a Navbar: the bar across the top, in view while the page scrolls, an optional toolbar under it for the page's own tools, and the page below them in a centered column. Docs
  • TabBar — The bar of tabs at the bottom of a phone screen: two to five of the app's top-level pages, each an icon over a short label, with the current page marked aria-current="page" and an accent wash. Docs
  • Tabs — Tabs that switch between panels in place: an accessible tab list with roving focus, arrow-key navigation, a line that slides to the selected tab, and 44px tabs on touch screens. Docs

Data

  • Chart — A chart drawn in the theme's colors, which people can use without seeing it: a figure named by its title, a summary for screen readers, and a table of every value (visible with showTable). Docs
  • ChartContainer — shadcn's name for Chart: the same component with the same props, so code ported from shadcn's chart docs finds it. Docs
  • ChartLegend — The legend for a recharts chart inside Chart: each series' marker shape and label from the chart's config, under the plot. Docs
  • ChartTooltip — The tooltip for a recharts chart inside Chart: the category, then each series' marker, label and value from the chart's config, in the theme's popover colors. Docs
  • Table — A data table: a caption, column headers, and rows, from columns and rows or from parts. Docs
  • TimeGrid — A day, work week or week of hours, with events placed by their times: the calendar grid of a mail or meeting app. Docs

Chat

  • Attachment — A file attached to a chat message, or waiting to be sent with one: an icon for its kind, its name and size. Docs
  • AttentionCard — Something that waits on the person: a mention, a question asked of them or a request still unanswered. Docs
  • Composer — A chat message box: text that grows from one line as the person writes, a send button, and room for the rest of a composer, in one frame with one focus outline. Docs
  • ConversationList — A list of conversations to choose from: each row shows the avatar, the title, when the latest message arrived and a line of it, and marks unread conversations with a bold title and a dot. Docs
  • createPeopleCache — A cache of people's names, photos or profiles for an app's avatars and message names, read from your service in batches. Docs
  • Emoji — One emoji from an image catalogue, drawn as an <img> the size of the text around it, named by its alt (the emoji's name). Docs
  • EmojiPicker — An emoji picker for reactions and for putting emoji into text. Docs
  • EmojiText — A message's text with its emoji drawn from an image catalogue: each token (:party:, an emoji's first shortcode or else its id, as EmojiPicker and Autocomplete put into a Composer) and each emoji's text the catalogue lists becomes the emoji's image, which screen readers hear by name. Docs
  • Message — One message in a chat: who wrote it, their avatar and what they wrote, with any attachments under it. Docs
  • MessageList — A conversation: Messages in a scrolling, labeled region that opens at the newest one. Docs
  • ReactionBar — A message's reactions: each one's emoji and how many chose it, with the person's own pressed and tinted, and quick reactions to add with a single press. Docs
  • readFailure — Whether a failed read's HTTP status is the service's ("failed": no answer, signed out, throttled, timed out or failing, so every read waits) or this person's ("missing": the service refused them alone, so they are kept as missing and no one waits). Docs
  • ThreadRail — A thread beside its conversation: a labelled landmark with a header and a close button. Docs
  • ThreadSummary — The line under a post that leads to its thread: who replied, "N replies" (a button that opens the thread) and when the last reply came. Docs
  • usePerson — One person from a cache made with createPeopleCache, for a component that shows them, such as an avatar or a message's author: their entry, read while the component shows it and again whenever it runs out. Docs
  • webStorage — Storage for createPeopleCache in localStorage, or another Storage, under one key. Docs

App setup

  • ColorSchemeProvider — Keeps the page's color scheme: it applies the person's choice (light, dark or system) as the .dark class on <html>, which is how shadcn themes switch, keeps it in localStorage, and follows the system's setting live while the choice is system. Docs
  • Inspector — X-ray for a page built with Ouro HX: outlines every Ouro component on the page with its name, linked to its docs, and lists them all for keyboard and screen reader users. Docs
  • measureInteraction — Runs fn in an event handler and names the interaction it belongs to, so the reports onInteraction sends carry name. Docs
  • onInteraction — Sends the page's interactions to sink, measured from the input to the next paint, so an app can hold them to a budget. Docs
  • OuroProvider — Optional app-level provider that routes internal hrefs through your router's link and groups tooltips: once one Tooltip shows, its neighbours open instantly instead of each waiting its own delay. Docs

Parts

  • Parts — building blocks for Checklist, Dialog, Field, Popover, Select, Sheet, Table, Tabs and TimeGrid, only for layouts the props cannot express. Docs

Theme

Ouro HX ships its stylesheets as registry items that write into your app's CSS:

  • ouro-base (registry/ouro/theme/ouro-base.css) is required in every app: add it once, before your first component.89 No component installs it, so it is the only add that writes to your stylesheet.14 It adds what shadcn's themes lack: --success, --warning and their foregrounds, and --destructive-foreground, with defaults that pass 4.5:1 on shadcn's stock themes90; the motion tokens (--ouro-spring-duration, --ouro-ease-spring and the other --ouro-* values); the press transition; and the spinner keyframes.91 For a theme that has neither, it also adds --input, the border color of every field, and --radius with its scale, at shadcn's stock values; a theme that has them keeps its own.92
  • ouro-theme (registry/ouro/theme/ouro-theme.css) is optional: the Ouro color look as a shadcn theme, light values on :root and dark values on .dark. An app that already has a shadcn theme keeps its own.

Dark mode follows the .dark class, as in shadcn. To change a color, set its variable in your theme.

To change any --ouro-* token (the spring and durations, --ouro-press-scale, the backdrop color and opacity, --ouro-layer), set it in a :root or .dark rule of your own after the Ouro tokens, outside any @layer, such as :root { --ouro-press-scale: 0.98; } at the end of your CSS file. A rule of your own survives adding ouro-base again, which rewrites the lines it wrote.

Sheets and dialogs stack at --ouro-layer, a z-index of 50 by default, the level of the Base UI dialog of shadcn@4.21.1.93 If your app has fixed bars above z-50, raise it the same way, such as :root { --ouro-layer: 60; }; no class edit is needed. At an equal z-index the open sheet or dialog still wins, because its portal is the last child of <body>.

Ouro AX

Ouro AX is Ouro UI's half for agents: it is for the interfaces an agent uses, such as a CLI, an HTTP API or an MCP server, as Ouro HX is for the screens a person uses (decision record). Its parts:

  • A standard with an X-ray. ax-claims holds the standard as checkable claims about agent-facing surfaces. ax-xray calls an interface from outside, the way an agent would, and reports which claims hold on which command, route and tool, with the fix where one does not.
  • A kit. With ax-command, one definition of a command gives its parser, its help and help --json, --json output, exit codes, one-line errors that name the fix, and the same command as an MCP tool. An action returns a typed outcome (ax-outcome), a wait returns a typed wait with a cursor (ax-wait), and ax-work-event records who acted for whom.
  • The evaluation gate. A claim is must only when an agent evaluation shows agents failing without it, or when it guards against a stated safety, compatibility or integrity hazard.
npx shadcn@latest add ourostack/ouro-ui/ouro-ax#v1.3.17         # the kit and the X-ray, in lib/ouro/ax/
npx shadcn@latest add ourostack/ouro-ui/ouro-ax-tests#v1.3.17   # with their node:test tests

Guides: Ouro AX, make your CLI conformant, the claims, the X-ray and the evaluation. The items' pages are in docs/ax.

Build with it, and give back what you learn. Interfaces that an agent or person uses, in your tools and your products alike, can follow Ouro UI's guidance: Ouro HX for screens, Ouro AX for agents. When an interface teaches you something general, contribute it back here as a principle, a component or a check with a test, kept generic, so the other products and tools that use Ouro UI get it too. A tool that needs an instruction booklet is a design bug: put the guidance in its surface (tool descriptions, replies, errors, automatic hooks).

Where things live

The site moved from ui.ouro.bot to ui.ouroboros.bot.94 Old addresses still land, forwarded to the same path on the new host, but new links use ui.ouroboros.bot. The repository, @ouro namespace and install addresses are unchanged.

  • ourostack/ouro-ui-core: the tokens, the decision records and the Ouro AX for Apple claims, with the design language, the Ouro AX standard and the site to follow.
  • ourostack/ouro-ui-apple: the Swift package (OuroHX, OuroAX, ouro-ax-xray).
  • ourostack/ouro-ui (web, this repository): React and the registry.
  • plugins/ouro-check (this repository): the ouro-check agent skill, moved from ourostack/ouroboros-skills, which is being retired. Change it here, in the same pull request as the change to the check it describes.

The rules:

  • A token change goes to web first, then to core, with a webTag bump in core's parity.json.
  • New decision records go only in ourostack/ouro-ui-core/docs/decisions/; this repository's docs/decisions/ is frozen history.
  • Look for prior art first (Meerkat mode), and give every decision record a "Prior art" section that marks each find adopt, borrow, watch or ignore.

License

MIT

Footnotes

  1. https://github.com/ourostack/ouro-ui-core/blob/20d06ff7ff7ffb3c50e33add65959608148edcd6/standard/hx/a2ui/catalog.json; test:registry/ouro/components/ouro/a2ui-surface/a2ui-surface.test.tsx::A2UISurface > renders the agent's card with HX components, its data bound and its template repeated; https://github.com/ourostack/ouro-ui-apple/blob/9931f6d284f44014648482bde69b114e48b7b7de/README.md#agent-composed-ui-a2ui verify:urlOk(https://github.com/ourostack/ouro-ui-core/blob/20d06ff7ff7ffb3c50e33add65959608148edcd6/standard/hx/a2ui/catalog.json) verify:testExists(registry/ouro/components/ouro/a2ui-surface/a2ui-surface.test.tsx::A2UISurface > renders the agent's card with HX components, its data bound and its template repeated) verify:urlOk(https://github.com/ourostack/ouro-ui-apple/blob/9931f6d284f44014648482bde69b114e48b7b7de/README.md#agent-composed-ui-a2ui) ↩

  2. https://github.com/ourostack/ouro-ui-core/blob/20d06ff7ff7ffb3c50e33add65959608148edcd6/docs/guides/actions.md#L5 verify:urlOk(https://github.com/ourostack/ouro-ui-core/blob/20d06ff7ff7ffb3c50e33add65959608148edcd6/docs/guides/actions.md#L5) ↩

  3. https://github.com/ourostack/ouro-ui-core/blob/20d06ff7ff7ffb3c50e33add65959608148edcd6/docs/guides/actions.md#L45; test:registry/ouro/components/ouro/a2ui-surface/a2ui-surface.test.tsx::A2UISurface > hands a person-only action to the app's own screen and never runs it verify:urlOk(https://github.com/ourostack/ouro-ui-core/blob/20d06ff7ff7ffb3c50e33add65959608148edcd6/docs/guides/actions.md#L45) verify:testExists(registry/ouro/components/ouro/a2ui-surface/a2ui-surface.test.tsx::A2UISurface > hands a person-only action to the app's own screen and never runs it) ↩

  4. test:registry/ouro/ax/xray.test.mts::the kit's example tool conforms, and the X-ray acts on nothing; https://github.com/ourostack/ouro-ui-apple/blob/9931f6d284f44014648482bde69b114e48b7b7de/README.md#whats-in-the-box verify:testExists(registry/ouro/ax/xray.test.mts::the kit's example tool conforms, and the X-ray acts on nothing) verify:urlOk(https://github.com/ourostack/ouro-ui-apple/blob/9931f6d284f44014648482bde69b114e48b7b7de/README.md#whats-in-the-box) ↩

  5. https://github.com/ourostack/ouro-ui-apple/releases/tag/v0.5.1 verify:urlOk(https://github.com/ourostack/ouro-ui-apple/releases/tag/v0.5.1) verify:equals(site/src/apple/kit.json#release)=0.5.1 ↩

  6. docs/platforms/README.md verify:fileLine(docs/platforms/README.md, /^| For Android | none yet/) verify:fileLine(docs/platforms/README.md, /^| For Windows | none yet/) ↩

  7. site/scripts/spoonjoy.mts verify:fileLine(site/src/pages/docs.tsx, /<InSpoonjoy name={item.name} />/) verify:testExists(site/scripts/spoonjoy.test.mjs::the committed Spoonjoy data names only registry components, Spoonjoy's own pages and its source on GitHub) ↩

  8. https://github.com/ourostack/ouro-ui-apple/blob/b5ea94d96a429754743888b8ea162d4f1e1a9d65/README.md#whats-in-the-box verify:urlOk(https://github.com/ourostack/ouro-ui-apple/blob/b5ea94d96a429754743888b8ea162d4f1e1a9d65/README.md#whats-in-the-box) ↩ ↩2

  9. https://github.com/ourostack/ouro-ui-core/blob/3ff42918ed91d5b99930d31a04b751b5c9e8551d/docs/guides/actions.md verify:urlOk(https://github.com/ourostack/ouro-ui-core/blob/3ff42918ed91d5b99930d31a04b751b5c9e8551d/docs/guides/actions.md) ↩ ↩2

  10. test/install-fixture/package.json verify:fileLine(test/install-fixture/package.json, /"tailwindcss": "^4./) verify:ciJob(install) ↩

  11. package.json; registry.json verify:fileLine(test/install-fixture/package.json, /"react": "^19./) verify:fileLine(registry.json, /"@base-ui/react@/) verify:testExists(scripts/registry.test.mjs::npm dependencies are pinned to the ranges this repository tests) ↩

  12. test:scripts/registry.test.mjs::npm dependencies are pinned to the ranges this repository tests verify:testExists(scripts/registry.test.mjs::npm dependencies are pinned to the ranges this repository tests) ↩

  13. test:scripts/motion-coverage.test.mjs::a motion state with no reduced-motion record for a part fails, even when that part's reduced list is empty; .github/workflows/ci.yml verify:testExists(scripts/motion-coverage.test.mjs::a motion state with no reduced-motion record for a part fails, even when that part's reduced list is empty) verify:ciJob(ci.yml#motion-coverage) ↩

  14. scripts/registry.mts verify:testExists(scripts/registry.test.mjs::only ouro-base and ouro-theme touch the app's stylesheet, and no other item installs them) ↩ ↩2 ↩3

  15. https://github.com/ourostack/ouro-ui/releases/tag/v0.8.0 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v0.8.0) verify:fileLine(UPGRADING.md, /**v0.8.0:** no component installs ouro-base/) ↩

  16. https://github.com/ourostack/ouro-ui/releases/tag/v0.7.0 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v0.7.0) ↩ ↩2 ↩3

  17. test:scripts/check/check-pages.test.mts::Ouro check > checks long lines only against a measure the project declares: none without one, a page glob's prose, or a selector's elements verify:testExists(scripts/check/check-pages.test.mts::Ouro check > checks long lines only against a measure the project declares: none without one, a page glob's prose, or a selector's elements) ↩

  18. test:scripts/check/check-command.test.mts::Ouro check's command > exits as every Ouro tool does: the Ouro AX kit's 0 ok, 1 error and 2 usage, then 3 for findings verify:testExists(scripts/check/check-command.test.mts::Ouro check's command > exits as every Ouro tool does: the Ouro AX kit's 0 ok, 1 error and 2 usage, then 3 for findings) ↩

  19. scripts/registry.mts verify:testExists(scripts/registry.test.mjs::a component that renders links brings OuroProvider, which only the app imports) verify:fileLine(scripts/registry.mts, /entry.name === "lib-router-link" || entry.name === "tooltip"/) ↩

  20. .gitignore; scripts/release-commit.sh verify:testExists(scripts/registry.test.mjs::main commits no registry.json, and a release commit adds it, so only a release tag installs from GitHub) ↩ ↩2

  21. scripts/registry.mts verify:testExists(scripts/registry.test.mjs::--release pins every same-repo dependency to the release tag and records the version) ↩

  22. .github/workflows/pages.yml verify:testExists(scripts/workflows.test.mjs::Pages names GitHub's newest release before it builds, whatever main's package.json says) ↩

  23. test:scripts/registry.test.mjs::helpers land in lib/ouro and the test kit in lib/ouro/testing and e2e/ouro; test:scripts/registry.test.mjs::each lib helper is its own item, so an item installs only the helpers it imports verify:testExists(scripts/registry.test.mjs::helpers land in lib/ouro and the test kit in lib/ouro/testing and e2e/ouro) verify:testExists(scripts/registry.test.mjs::each lib helper is its own item, so an item installs only the helpers it imports) ↩

  24. test:registry/ouro/lib/ouro/dev.test.ts::checkTheme > names the command that adds ouro-base, once per page, when the token is unset verify:testExists(registry/ouro/lib/ouro/dev.test.ts::checkTheme > names the command that adds ouro-base, once per page, when the token is unset) ↩

  25. scripts/install-check.mjs; .github/workflows/ci.yml verify:fileLine(scripts/install-check.mjs, /an Ouro item landed in components/ui/) verify:ciJob(ci.yml#install) ↩

  26. scripts/registry.mts verify:testExists(scripts/registry.test.mjs::a -tests item installs the tests of every item it installs: link-tests brings text-tests) ↩

  27. scripts/subset-coverage.mjs verify:testExists(scripts/subset-coverage.test.mjs::CI checks the button, the dialog, the sheet and the forms set, each alone and together) verify:fileLine(.github/workflows/ci.yml, /node scripts/subset-coverage.mjs/) ↩

  28. scripts/registry.mts verify:testExists(scripts/registry.test.mjs::ouro-tests installs every -tests item: the whole suite in one command) ↩

  29. scripts/registry.mts verify:fileLine(scripts/registry.mts, /vitest: "4.1.11"/) verify:fileLine(scripts/registry.mts, /playwright: "1.58.1"/) verify:fileLine(scripts/registry.mts, /storybook: "10.2.10"/) verify:fileLine(registry/ouro/lib/ouro/testing/axe-version.ts, /^const axeMinimum = "4.13.0";$/) ↩ ↩2

  30. package.json verify:fileLine(package.json, /"vitest": "5./) verify:fileLine(.github/workflows/ci.yml, /pnpm test/) ↩

  31. test:scripts/registry.test.mjs::devDependencies are named without a version, so the CLI installs it only when the app lacks it and never changes a version the app has verify:testExists(scripts/registry.test.mjs::devDependencies are named without a version, so the CLI installs it only when the app lacks it and never changes a version the app has) ↩

  32. docs/guides/testing.md verify:fileLine(docs/guides/testing.md, /Vitest 4/) ↩

  33. https://github.com/ourostack/ouro-ui/releases/tag/v0.1.0 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v0.1.0) ↩ ↩2

  34. https://github.com/ourostack/ouro-ui/releases/tag/v0.2.0 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v0.2.0) ↩ ↩2

  35. scripts/install-test.sh; scripts/upgrade-note.mjs verify:testExists(scripts/upgrade-note.test.mjs::the upgrade install test runs the note's commands and copies none) verify:ciJob(install-upgrade) ↩ ↩2

  36. https://github.com/ourostack/ouro-ui/releases; .github/workflows/release.yml; scripts/release-commit.sh verify:urlOk(https://github.com/ourostack/ouro-ui/releases) verify:fileLine(.github/workflows/release.yml, /scripts/release-commit.sh "$VERSION"/) verify:fileLine(scripts/release-commit.sh, /changes.mjs assemble/) ↩

  37. https://github.com/ourostack/ouro-ui/releases/tag/v0.3.0 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v0.3.0) verify:fileLine(registry/ouro/theme/ouro-base.css, /--ouro-layer: 50;/) ↩

  38. scripts/source-rules.mjs verify:testExists(scripts/source-rules.test.mjs::every istanbul hint in registry/ carries @preserve, so it survives esbuild in an app) ↩

  39. https://github.com/ourostack/ouro-ui/releases/tag/v0.3.1 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v0.3.1) ↩

  40. https://github.com/ourostack/ouro-ui/releases/tag/v0.8.0 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v0.8.0) verify:testExists(scripts/registry.test.mjs::only ouro-base and ouro-theme touch the app's stylesheet, and no other item installs them) verify:testExists(scripts/registry.test.mjs::each lib helper is its own item, so an item installs only the helpers it imports) ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12

  41. test:scripts/registry.test.mjs::devDependencies are named without a version, so the CLI installs it only when the app lacks it and never changes a version the app has verify:testExists(scripts/registry.test.mjs::devDependencies are named without a version, so the CLI installs it only when the app lacks it and never changes a version the app has) ↩

  42. registry/ouro/lib/ouro/testing/vitest.ts; https://github.com/ourostack/ouro-ui/releases/tag/v0.8.3 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v0.8.3) verify:testExists(scripts/testing-kit.test.mjs::page sizes every Playwright host while the touch frame keeps its phone viewport and input) verify:testExists(scripts/testing-kit.test.mjs::ouroTestProjects leaves the page size to Vitest unless the app sets one) verify:fileLine(registry/ouro/lib/ouro/testing/vitest.ts, /390x844/) verify:fileLine(docs/guides/testing.md, /1600/) ↩ ↩2 ↩3 ↩4

  43. https://github.com/ourostack/ouro-ui/releases/tag/v1.0.0 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v1.0.0) ↩ ↩2

  44. https://github.com/ourostack/ouro-ui/releases/tag/v1.1.0 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v1.1.0) verify:fileLine(registry/ouro/theme/ouro-base.css, /--ouro-shadow-sheet-/) verify:fileLine(registry/ouro/theme/ouro-base.css, /--ouro-duration-draw:/) ↩ ↩2

  45. test/theme-tokens.test.tsx verify:testExists(test/theme-tokens.test.tsx::standard shadow and font tokens > falls back to the inherited font for headings when the theme names none, and Kbd always keeps it) ↩

  46. https://github.com/ourostack/ouro-ui/releases/tag/v1.0.2 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v1.0.2) verify:fileLine(registry/ouro/lib/ouro/form-answer.ts, /Snapshot arrays are readonly/) ↩ ↩2

  47. https://github.com/ourostack/ouro-ui/releases/tag/v1.2.2; registry/ouro/components/ouro/button/button.test.tsx; registry/ouro/components/ouro/link/link.test.tsx verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v1.2.2) verify:testExists(registry/ouro/components/ouro/button/button.test.tsx::link branch > uses the document for reloadDocument and downloads, retaining router navigation otherwise) verify:testExists(registry/ouro/components/ouro/link/link.test.tsx::Link > treats only a present download as document navigation, including an empty filename) ↩ ↩2

  48. registry/ouro/lib/ouro/portal-container.test.tsx verify:testExists(registry/ouro/lib/ouro/portal-container.test.tsx::usePortalContainer > leaves a popup at the end of , Base UI's default, without a portalContainer) verify:testExists(registry/ouro/lib/ouro/portal-container.test.tsx::usePortalContainer > warns once, in development, when a popup opens outside every landmark of a page that has a main) ↩

  49. registry/ouro/lib/ouro/portal-container.test.tsx verify:testExists(registry/ouro/lib/ouro/portal-container.test.tsx::usePortalContainer > gives popups Base UI's own place again inside an OuroProvider whose portalContainer is null, as an app's own dialog needs) ↩

  50. registry/ouro/components/ouro/sidebar/sidebar-layout.test.tsx verify:testExists(registry/ouro/components/ouro/sidebar/sidebar-layout.test.tsx::SidebarLayout > puts the header in the page's banner landmark, and draws no empty landmark without one) ↩

  51. registry/ouro/lib/ouro/testing/axe.test.ts verify:testExists(registry/ouro/lib/ouro/testing/axe.test.ts::audit > fails a violation whatever its impact: content outside every landmark is only moderate) ↩

  52. scripts/registry.test.mjs verify:testExists(scripts/registry.test.mjs::npm dependencies are pinned to the ranges this repository tests) ↩

  53. registry/ouro/components/ouro/toast/toaster.test.tsx verify:testExists(registry/ouro/components/ouro/toast/toaster.test.tsx::Toaster > renders an empty Notifications landmark in a portal, which holds its own live regions) ↩

  54. registry/ouro/components/ouro/toast/toaster.test.tsx verify:testExists(registry/ouro/components/ouro/toast/toaster.test.tsx::toast() > keeps every toast, its action and its dismiss button reachable by a screen reader while the stack rests collapsed) ↩

  55. registry/ouro/lib/ouro/testing/axe.test.ts verify:testExists(registry/ouro/lib/ouro/testing/axe.test.ts::audit > fails, naming what changed, when its Tab walk closes a popup that focus leaving closes, which it cannot open again) ↩

  56. registry/ouro/theme/ouro-base.css verify:fileLine(registry/ouro/theme/ouro-base.css, /--avatar-8:/) verify:testExists(registry/ouro/components/ouro/avatar/avatar.test.tsx::Avatar colors > picks the same hue for the same name, and spreads names across the hues) ↩

  57. registry/ouro/components/ouro/avatar/avatar.test.tsx verify:testExists(registry/ouro/components/ouro/avatar/avatar.test.tsx::Avatar shapes and icons > clips a square's picture to its corners) ↩

  58. registry/ouro/components/ouro/avatar/avatar.test.tsx verify:testExists(registry/ouro/components/ouro/avatar/avatar.test.tsx::Avatar inside a link or button that has its own name > the same link around initials written as text fails the rule, so the check can fail) verify:testExists(registry/ouro/components/ouro/avatar/avatar.test.tsx::Avatar > shows initials from the name, and the name to screen readers) ↩ ↩2

  59. https://github.com/ourostack/ouro-ui/releases/tag/v1.3.5 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v1.3.5) ↩

  60. https://github.com/ourostack/ouro-ui/releases/tag/v1.3.6 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v1.3.6) ↩ ↩2

  61. https://github.com/ourostack/ouro-ui/releases/tag/v1.3.7 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v1.3.7) ↩ ↩2

  62. registry/ouro/check/check.ts verify:fileLine(registry/ouro/check/check.ts, /^ "giant-picture": "giant picture",$/) verify:testExists(scripts/check/check-rules.test.mts::Ouro check's design rules > fails each seed case where a person saw it, with numbers that say why) ↩ ↩2

  63. https://github.com/ourostack/ouro-ui/releases/tag/v1.3.10 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v1.3.10) ↩

  64. registry/ouro/check/text-check.ts verify:fileLine(registry/ouro/check/check.ts, /^ "odd-timestamp": "odd timestamp",$/) verify:testExists(test/check-text-in-page.test.ts::findTextIssues: leaked-value > finds each kind of value the program meant for itself, quoting what it shows) ↩ ↩2 ↩3

  65. test:scripts/check/check-pages.test.mts::Ouro check > never waives a blurry picture, and lists each one, with the file it needs, in the images report; test:scripts/images/images.test.mts::Ouro images' refusal of pictures too small to draw sharp > writes nothing for a refused picture, and the rest as before verify:testExists(scripts/check/check-pages.test.mts::Ouro check > never waives a blurry picture, and lists each one, with the file it needs, in the images report) verify:testExists(scripts/images/images.test.mts::Ouro images' refusal of pictures too small to draw sharp > writes nothing for a refused picture, and the rest as before) ↩ ↩2

  66. registry/ouro/theme/ouro-base.css verify:fileLine(registry/ouro/theme/ouro-base.css, /@keyframes ouro-shimmer-band/) verify:testExists(registry/ouro/components/ouro/skeleton/skeleton.test.tsx::Skeleton > shimmers a band that moves by translate only, and holds it still in the middle with reduced motion) ↩

  67. https://github.com/ourostack/ouro-ui/releases/tag/v1.3.12 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v1.3.12) ↩ ↩2

  68. https://github.com/ourostack/ouro-ui/releases/tag/v1.3.13 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v1.3.13) ↩

  69. https://github.com/ourostack/ouro-ui/releases/tag/v1.3.15 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v1.3.15) ↩

  70. https://github.com/ourostack/ouro-ui/releases/tag/v1.3.17 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v1.3.17) ↩

  71. registry/ouro/components/ouro/hero/hero.test-d.ts verify:testExists(registry/ouro/components/ouro/hero/hero.test.tsx::Hero > shows the dark picture only while the page is in dark mode) ↩

  72. test/check-theme-in-page.test.ts verify:testExists(test/check-theme-in-page.test.ts::findThemeIssues: hero-dark-art > finds a hero whose picture has no night art, in light mode too, named by its words) ↩

  73. scripts/registry.mts; scripts/images/images.test.mts verify:fileLine(scripts/registry.mts, /@lib/ouro/images//) verify:testExists(scripts/images/images.test.mts::Ouro images' command > refuses what it cannot run with one line that names the fix, and exits 2) ↩ ↩2

  74. scripts/registry.mts; scripts/check/check.test.mts verify:fileLine(scripts/registry.mts, /@lib/ouro/check//) verify:testExists(scripts/check/check-command.test.mts::Ouro check's command > reads every option, and refuses what it cannot run) verify:testExists(scripts/check/check-command.test.mts::Ouro check's command > prints the kit's help for --help, and refuses an unknown flag in one line that names its fix, with exit 2) verify:testExists(scripts/check/check-command.test.mts::Ouro check's command > refuses a target that is no folder or URL in one line that names the fix, and a mistyped merge names merge) verify:testExists(scripts/check/check-command.test.mts::Ouro check's command > --json prints the outcome alone and exits 3 on gated findings, with the progress lines on stderr) ↩ ↩2

  75. registry/ouro/ax/flows.test.mts verify:testExists(registry/ouro/ax/flows.test.mts::the command's --help is the Ouro AX kit's, and an unknown flag is one line that names the fix, exit 2) verify:testExists(registry/ouro/ax/flows.test.mts::--json prints the outcome alone on stdout, and each flow's line on stderr) verify:testExists(registry/ouro/ax/flows.test.mts::the command runs a suite module: exit 0, 1 on an unknown failure or a known that passes, 2 on usage) ↩

  76. registry/ouro/check/wrap-check.ts verify:fileLine(registry/ouro/check/check.ts, /^import { findWrapIssues, readHeroArt, type HeroArt } from "./wrap-check.ts";$/) verify:testExists(test/check-wrap-in-page.test.ts::findWrapIssues: lone-word > finds a block whose last line holds one word, a hyphen's piece included, in a scroller too, named by its words) ↩ ↩2 ↩3

  77. registry/ouro/ax/xray.ts verify:fileLine(registry/ouro/ax/xray.ts, /return runCli(tool, argv, io);/) verify:testExists(registry/ouro/ax/xray.test.mts::the kit answers help, an unknown flag is one line with its fix, and repeat flags keep every value) ↩

  78. https://ui.shadcn.com/docs/cli verify:urlOk(https://ui.shadcn.com/docs/cli) ↩ ↩2

  79. https://github.com/shadcn-ui/ui/blob/3502dbcde11d1eaf967a47ac375744dc336641f3/packages/registry/src/utils/updaters/update-css-vars.ts#L91-L124 verify:urlOk(https://github.com/shadcn-ui/ui/blob/3502dbcde11d1eaf967a47ac375744dc336641f3/packages/registry/src/utils/updaters/update-css-vars.ts#L91-L124) verify:testExists(scripts/install-check.test.mjs::a complete install passes, for each variant and stage) ↩

  80. UPGRADING.md verify:fileLine(UPGRADING.md, /[ -n "$items" ] || { echo "No Ouro components/) ↩

  81. https://github.com/ourostack/ouro-ui/releases/tag/v0.7.0 verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v0.7.0) ↩

  82. scripts/registry.mts verify:fileLine(scripts/registry.mts, /vitest: "4.1.11"/) verify:fileLine(scripts/registry.mts, /playwright: "1.58.1"/) verify:fileLine(scripts/registry.mts, /storybook: "10.2.10"/) ↩

  83. https://github.com/ourostack/ouro-ui/releases/tag/v0.2.0; scripts/install-check.mjs verify:urlOk(https://github.com/ourostack/ouro-ui/releases/tag/v0.2.0) verify:testExists(scripts/install-check.test.mjs::a token that refers to itself fails) ↩

  84. scripts/gen-docs.mts verify:testExists(scripts/gen-docs.test.mjs::a component doc has purpose, import, example, props, and use-instead guidance) verify:testExists(scripts/gen-docs.test.mjs::there is one doc per component, plus parts) ↩

  85. test:registry/ouro/lib/ouro/cn.test.ts::lets a later class win over an earlier one that sets the same property verify:testExists(registry/ouro/lib/ouro/cn.test.ts::lets a later class win over an earlier one that sets the same property) ↩

  86. registry/ouro/components/ouro/menu/menu.test.tsx verify:testExists(registry/ouro/components/ouro/menu/menu.test.tsx::Menu focus guards > reports a context menu's focus guards to plain axe, and the gate passes with them left out) verify:testExists(registry/ouro/components/ouro/menu/menu.test.tsx::Menu focus guards > reports a trigger menu's focus guards to plain axe, and the gate passes with them left out) verify:testExists(test/axe-exclude.test.tsx::matches the open dialog's focus guards, and with them excluded the accessibility gate finds nothing) ↩

  87. registry/ouro/lib/ouro/dev.ts verify:testExists(scripts/readme-steps.test.mjs::every published first-install command through @ouro adds ouro-base, which no component brings) verify:fileLine(registry/ouro/lib/ouro/dev.ts, /ouro-layer/) ↩

  88. test/stock-theme.test.tsx; test/shadcn-stock.css verify:fileLine(test/stock-theme.test.tsx, /4.5/) verify:ciJob(ci) ↩

  89. registry/ouro/theme/ouro-base.css verify:fileLine(registry/ouro/theme/ouro-base.css, /--success:/) verify:fileLine(registry/ouro/theme/ouro-base.css, /--destructive-foreground:/) verify:fileLine(registry/ouro/theme/ouro-base.css, /--ouro-ease-spring:/) verify:fileLine(registry/ouro/theme/ouro-base.css, /@keyframes ouro-spin/) ↩

  90. registry/ouro/theme/ouro-base.css verify:testExists(scripts/install-check.test.mjs::an install whose stylesheet lacks the field border color or the radius scale fails) verify:testExists(scripts/install-check.test.mjs::stripFieldTokens removes --input, --radius and their @theme mappings, and nothing else) ↩

  91. registry/ouro/theme/ouro-base.css; https://github.com/shadcn-ui/ui/blob/3502dbcde11d1eaf967a47ac375744dc336641f3/apps/v4/registry/bases/base/ui/dialog.tsx#L33-L53; scripts/install-test.sh verify:fileLine(registry/ouro/theme/ouro-base.css, /--ouro-layer: 50;/) verify:urlOk(https://github.com/shadcn-ui/ui/blob/3502dbcde11d1eaf967a47ac375744dc336641f3/apps/v4/registry/bases/base/ui/dialog.tsx#L33-L53) verify:fileLine(scripts/install-test.sh, /grep -c 'z-50' src.components.ui.dialog.tsx/) verify:ciJob(ci.yml#install) ↩

  92. site/src/head.ts verify:fileLine(site/src/head.ts, /SITE = "https://ui.ouroboros.bot"/) ↩

About

Ouro UI for React: Ouro HX components that feel right to people and read clearly to the agents building with them. A shadcn registry.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages