Actions
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. It shares the page's own address unless given url.
In Spoonjoy
Spoonjoy, a recipe app, is built with Ouro HX (how).
See it live, no sign-in needed: A recipe
In its source: app/routes/cookbooks.$id.tsx · app/routes/recipes.$id.tsx
Install
Once per app, with Tailwind CSS v4 and an @/* path alias (Ouro HX is built on Base UI, so --base base; skip -t in an existing app):
npx shadcn@latest init --base base --preset nova --yes --no-monoreponpx shadcn@latest add ourostack/ouro-ui/ouro-base#v1.2.13Then the component. From v0.8.0 it installs only the helpers it imports and never touches your stylesheet (Installing):
npx shadcn@latest add ourostack/ouro-ui/share-button#v1.2.13Or, with the @ouro namespace set up:
npx shadcn@latest add @ouro/ouro-base @ouro/share-buttonIts tests, stories and Playwright spec are optional. They need Vitest 4.1.11 or later, and from v0.8.0 never change a version your app has (Testing):
npx shadcn@latest add ourostack/ouro-ui/share-button#v1.2.13 ourostack/ouro-ui/share-button-tests#v1.2.13import { ShareButton } from "@/components/ouro/share-button/share-button";Paths use shadcn's default @/ alias; the CLI rewrites them to yours.
Example
<ShareButton url={`https://example.com/notes/${note.id}`} title={note.title} />Use instead when
- Use
CopyButtoninstead to copy any text without a share sheet, and theshare()helper for a share action inside a menu.
Props
Type: ShareButtonProps.
className- Type:
string - Layout only: margins, grid placement (
ms-auto). Appearance classes warn in development. copiedLabel- Type:
string - Default:
"Link copied" - What it says, and screen readers hear, when there was no share sheet and the link was copied instead.
failedLabel- Type:
string - Default:
"Couldn't share" - What it says, and screen readers hear, when neither sharing nor copying worked.
hideLabel- Type:
boolean - Shows only the icon; the label stays the button's name for screen readers.
label- Type:
string - Default:
"Share" - The button's label, and its name for screen readers with
hideLabel. onShare- Type:
((result: ShareResult) => void) - Called with how the share ended:
shared,copied,cancelledorfailed. ref- Type:
Ref<HTMLButtonElement> - Reaches the rendered element.
size- Type:
"sm" | "md" | "lg" - Default:
"md" - Control size, as Button's.
text- Type:
string - A line about the link, where the receiving app takes one.
title- Type:
string - A title for the link, where the receiving app takes one.
url- Type:
string - Default: the page's own address
- The address to share.
variant- Type:
"solid" | "soft" | "outline" | "plain" - Default:
"outline" - Visual weight, as Button's.
Native attributes
Also accepts the attributes in React's ButtonHTMLAttributes and HTMLAttributes (id, aria-*, event handlers), passed to the rendered element, except those named on this page.
Don't use
These are not props. Passing one fails the type check with the fix in the error, and in development a console message names the fix.
children: Not a prop. The label islabel:<ShareButton label="Share recipe" />.href: Not a prop. The address isurl:<ShareButton url={link} />.onClick: Not a prop. ShareButton shares on click itself; to react to it, passonShare.
className is for layout only
Pass margins, width and grid placement (mt-4, w-full, col-span-2). Your classes always win over Ouro UI's: ShareButton merges className last with cn (tailwind-merge), so a class that conflicts with its own replaces it.layout-classes
Appearance comes from props, not classes. In development, a class that sets appearance logs one warning per component instance, naming the prop to use instead. A class sets appearance when, after its variants (hover:, sm:) and any ! are removed, it starts with bg-, text-, border-, rounded-, shadow-, font-, ring-, from-, via-, to-, fill- or stroke-, or is border, rounded, shadow or ring, or has a dark: variant. Text wrapping, overflow and table layout classes (text-wrap, text-ellipsis, border-collapse) are layout and do not warn.
Alignment classes (text-center) warn too: set alignment on a parent element.
canShare()
Whether the browser has a share sheet for this data: most phones and Safari do, most desktop browsers do not.
import { canShare } from "@/lib/ouro/share";Example
const label = canShare({ url }) ? "Share" : "Copy link";Parameters
data- Type:
ShareContent
Returns
boolean
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). Resolves whether it worked; never throws.
import { copyText } from "@/lib/ouro/share";Example
if (await copyText(url)) toast("Link copied");Parameters
text- Type:
string
Returns
Promise<boolean>
share()
Shares a link through the system's share sheet, or copies it where there is none, and says which happened. Call it from a click: browsers open the share sheet only for one. It never throws.
import { share } from "@/lib/ouro/share";Example
const result = await share({ url: location.href, title: document.title });
if (result === "copied") toast("Link copied");Use instead when
- Use
ShareButtoninstead for the whole button, with its "Link copied" confirmation.
Parameters
data- Type:
ShareContent
Returns
Promise<ShareResult>
Claim sources
- layout-classes: test:registry/ouro/components/ouro/share-button/share-button.test.tsx::ShareButton > forwards a ref, takes button attributes, and lets app layout classes win
verify:testExists(registry/ouro/components/ouro/share-button/share-button.test.tsx::ShareButton > forwards a ref, takes button attributes, and lets app layout classes win)