Skip to main content

App setup

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. Ouro UI components work without it: they honor reduced motion themselves, in CSS (motion-reduce: variants and prefers-reduced-motion queries), and need no animation library. If your app animates with Motion, wrap it in <MotionConfig reducedMotion="user"> for the same behavior.

import { OuroProvider } from "@/lib/ouro/ouro-provider";

Example

// React Router. app/components/ouro-router-link.tsx:
import type { ComponentProps } from "react";
import { Link } from "react-router";
import type { OuroLinkComponent } from "@/lib/ouro/use-ouro";

export function OuroRouterLink({ href, ...props }: ComponentProps<OuroLinkComponent>) {
  return <Link to={href} {...props} />;
}

// app/root.tsx:
import { Outlet } from "react-router";
import { OuroProvider } from "@/lib/ouro/ouro-provider";
import { OuroRouterLink } from "@/components/ouro-router-link";

export default function App() {
  return (
    <OuroProvider linkComponent={OuroRouterLink}>
      <Outlet />
    </OuroProvider>
  );
}

// Next.js App Router. app/providers.tsx:
"use client";

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

export function Providers({ children }: { children: ReactNode }) {
  return <OuroProvider linkComponent={Link}>{children}</OuroProvider>;
}

// app/layout.tsx, a Server Component:
import type { ReactNode } from "react";
import { Providers } from "./providers";

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

Usage

An app with a motion setting of its own passes it as motion: <OuroProvider motion={still ? "reduced" : "system"}>. Its springs, scrolls and moving pictures then follow the app's setting, and your components can too, through useReducedMotion().

It installs with every component that renders links (Button, Link, Card, Item, Table and the navigation components) and with Tooltip. An app with none of those adds it by name, lib-ouro-provider, at the release tag of your other components (the README's Next.js steps show the command). Type a custom adapter with ComponentProps<OuroLinkComponent> so it always compiles, including under exactOptionalPropertyTypes.

Every Ouro UI link takes href: Link, Button, Card, Item and Table rows. An internal href (a path) renders through linkComponent; an absolute URL (https:, mailto:) always renders a plain <a>. Without a provider, or without linkComponent, internal links are plain <a> elements too, so they work but load the page.

React Router's Link takes to, so it needs the adapter in the example, which maps href to to. Wrap the app once, in the root route.

In Next.js, next/link's Link takes href and needs no adapter, but a Server Component can't pass Link, a function, to a client component: render the provider from a client file, app/providers.tsx in the example, and wrap the app in it from app/layout.tsx, which stays a Server Component.

Props

Type: OuroProviderProps.

children (required)
Type: ReactNode
Your app, or the part of it that uses Ouro UI.
linkComponent
Type: OuroLinkComponent
Renders internal hrefs, for example an adapter around a router's Link.
motion
Type: "full" | "system" | "reduced"
Default: "system"
How much Ouro UI moves, for an app with a motion setting of its own. system follows the device's reduced-motion setting, as Ouro UI does without a provider. reduced moves as if that setting were on, whatever the device says: springs settle at once, scrolls jump, and a moment or an emoji holds still. full keeps that motion running even where the device asks for less. Ouro UI's motion in script reads it, as does useReducedMotion for your own: it is in lib/ouro/use-reduced-motion, which you can add by name, lib-use-reduced-motion. A provider that sets it wraps its children in an element with data-motion set to it (and display: contents, so it adds no box), so your CSS can follow the setting too, beside the prefers-reduced-motion media query: [data-motion="reduced"] .wave { animation: none }. While a provider with reduced is on the page, Ouro UI's CSS animations and transitions, and your own, end at once everywhere but inside a provider set to full. It does this with a <style> of its own, the one stylesheet an Ouro UI component brings: an app's own setting must still the dialogs, sheets and toasts that render outside the provider's element too, and Ouro UI's classes follow only the device's media query. With full, a transition Ouro UI keeps for prefers-reduced-motion: no-preference still follows the device. A provider inside another keeps the outer one's setting when it sets none.
portalContainer
Type: HTMLElement | RefObject<HTMLElement | null> | null
The element floating popups render into: the lists of Select and Combobox, and Menu, Popover, DatePicker, Tooltip, HoverCard and NavigationMenu. Unset, they render at the end of <body>, outside every landmark, where screen reader users who move by landmark miss them. Pass an element, or a ref to one, inside <main> that does not clip what it holds (no overflow other than visible) and is not inside an element with a transform, filter or contain, which would hold the phone sheets' position: fixed. Render it last in <main>, after the content, so popups stack over the page; a ref works even when the element comes after a popup that is open at once. A provider inside another keeps the outer one's container when it sets none, and null gives popups Base UI's place again: wrap the content of a dialog that is not Ouro UI's in <OuroProvider portalContainer={null}> so its popups open inside it. Dialogs, sheets and toasts stay at the end of <body>: a dialog is announced when it opens, and the toasts are a landmark of their own.
quiet
Type: boolean
Default: false
For people who want less: Moment shows its words without art or a celebration, and Celebrate its message without confetti. While it is on, <html> carries data-ouro-quiet, so an app hides its own decoration with one rule: [data-ouro-quiet] [data-ouro-quiet-hide] { display: none; }. A provider inside another keeps the outer one's setting when it sets none. Store the person's choice as you store their other settings, and pass it here.

Types

OuroLinkComponent

A component that renders an internal link, such as a router's Link.

type OuroLinkComponent = ComponentType<
  AnchorHTMLAttributes<HTMLAnchorElement> & { href: string; ref?: Ref<HTMLAnchorElement> }
>;