Default
Sized to its content. Press it — the lit top edge dips into the face.
<Button onClick={handleClick}>Post</Button>Solid button with depth you feel more than see.
A foundational button that looks flat but is not — a vertical gradient catches light on the top edge, a hairline ring keeps it crisp on any background, and twin inner shadows give it form. Press it and the highlight collapses into the face, so it dips in like a real key. Three sizes, full-width, and a built-in loading spinner. Forwards refs and spreads every native button attribute.
Add the package to your project. Components ship with their own CSS — no global setup.
pnpm add @roy-ui/uiimport { Button } from '@roy-ui/ui';
// or import just this component (its own 'use client' island):
import { Button } from '@roy-ui/ui/button';Inline by default. The depth is intrinsic — gradient and inner shadows — so it reads the same on light or dark backgrounds without a heavy drop shadow.
Sized to its content. Press it — the lit top edge dips into the face.
<Button onClick={handleClick}>Post</Button>Three scales. The corner radius scales with the box, so the proportions hold.
<Button size="sm">Post</Button>
<Button size="md">Post</Button>
<Button size="lg">Post</Button>Three weights for hierarchy. Primary is the solid depth button; secondary is a quieter raised chip; ghost stays flat until hovered.
<Button variant="primary">Post</Button>
<Button variant="secondary">Save draft</Button>
<Button variant="ghost">Cancel</Button>Set asChild to paint the button onto a link instead of a <button> — real anchor semantics (open-in-new-tab, right-click, prefetch, a crawlable href) are preserved. Works with Next's <Link> too.
import Link from 'next/link';
<Button asChild>
<Link href="/components">Browse components</Link>
</Button>
// or a plain anchor
<Button asChild variant="secondary">
<a href="https://github.com/DibbayajyotiRoy/RoyUI">GitHub</a>
</Button>Pass one color and the whole depth treatment derives from it — a lighter top and darker base, a ring that adapts to the tone, and a label color chosen for contrast. Click a swatch or open the picker, then copy the config straight into your project.
<Button color="#4ec6ff">Post</Button>Pass loading={true} to swap the label for a spinner and disable the button. Click to simulate an async submit.
const [loading, setLoading] = useState(false);
<Button
type="submit"
loading={loading}
onClick={handleSubmit}
>
Post
</Button>Set fullWidth to stretch into a form column.
<Button fullWidth>Post</Button>Hover lift and press are suppressed. Pointer falls back to not-allowed.
<Button disabled>Can't post</Button>Every surface is a CSS variable. Override them inline or in a stylesheet to retint the button without touching the depth recipe.
.royui-btn {
--royui-btn-top: #323232; /* gradient — top (lit) */
--royui-btn-bottom: #222222; /* gradient — base */
--royui-btn-ring: #3a3a3a; /* hairline edge */
--royui-btn-fg: #ffffff; /* label */
--royui-btn-radius: 14px;
}Every native button attribute is forwarded — onClick, type, aria-*, data-*, ref, etc.
| Prop | Type | Default | Description |
|---|---|---|---|
size | 'sm' | 'md' | 'lg' | 'md' | Visual scale. Radius scales with the box. |
variant | 'primary' | 'secondary' | 'ghost' | 'primary' | Visual weight. Solid depth button, quieter raised chip, or flat-until-hover. |
asChild | boolean | false | Render the single child element (e.g. a link) with the button styles merged onto it. |
color | string | near-black | Base color (hex or rgb). Gradient, ring, label, and press all derive from it. |
fullWidth | boolean | false | Stretch the button to fill its container. |
loading | boolean | false | Replaces children with a spinner and disables the button. |
loadingLabel | ReactNode | spinner | Optional override for the loading visual. |
disabled | boolean | false | Standard disabled — applied automatically when loading. |
...rest | ButtonHTMLAttributes | — | All native button props (onClick, type, aria-*, data-*, ref). |