Button
An action button in three variants. Give it an href and it becomes a link, styled identically.
import { Button } from '@/components/custom/button'
export default function ButtonDemo() {
return (
<div className="flex flex-wrap items-center gap-3">
<Button>Approve payout</Button>
<Button variant="outline">Save draft</Button>
<Button variant="danger">Reverse transfer</Button>
<Button href="/components/button" variant="outline">
Renders as a link
</Button>
<Button disabled>Disabled</Button>
</div>
)
}Installation
pnpm dlx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/button.jsonnpx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/button.jsonyarn dlx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/button.jsonbunx --bun shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/button.jsonInstalls components/custom/button.tsx. This is separate from the shadcn button primitive, which stays available at @/components/ui/button for anywhere you need its full variant and size range.
Usage
import { Button } from '@/components/custom/button'
export function PayoutActions() {
return (
<div className="flex gap-2">
<Button onClick={approve}>Approve</Button>
<Button variant="danger" onClick={reverse}>
Reverse
</Button>
<Button href="/payouts" variant="outline">
Back to payouts
</Button>
</div>
)
}Props
Extends React.ComponentProps<'button'>.
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'primary' | 'outline' | 'danger' | 'primary' | Visual weight. |
href | string | — | Renders a next/link anchor instead of a button. |
replace | boolean | — | Replace the history entry instead of pushing. Link only. |
scroll | boolean | — | Scroll to the top after navigating. Link only. |
prefetch | boolean | null | — | Prefetch behaviour. Link only. |
target | string | — | '_blank' also sets rel="noopener noreferrer". |
href and disabled together render a real disabled <button>, because an anchor cannot be disabled — removing its
href to fake it also removes it from the tab order without telling anyone why.
Behaviour notes
A link is one element, not two. With href, the anchor itself carries the button styling. Wrapping a <button> inside an <a> is invalid HTML, and browsers recover from it by hoisting the button out of the anchor — the result looks right and loses the keyboard activation and accessible role the markup appeared to have.
No implicit type. The type attribute is passed through untouched, so a button inside a form still submits it. Pass type="button" on anything that should not.
App Router only for the link props. as, shallow and passHref are Pages Router APIs and are not accepted — they were silently inert in App Router projects.