Styled button with variants and sizes, built on the @foldkit/ui Button helper.
Clicked 0 times.
Add this component to your project:
npx shadcn@latest add @foldcn/buttonThe component ships as plain source — no build step, no wrapper. Copy it and make it yours.
registry/default/ui/button.ts96 linesimport { Button as FoldkitButton } from '@foldkit/ui'
import type { Html, HtmlBuilder } from 'foldkit/html'
import { cn } from '@/lib/utils'
/** Button variant keys — keep in sync with `buttonVariants`. */
export const buttonVariantKeys = [
'default',
'destructive',
'outline',
'secondary',
'ghost',
'link',
] as const
export const buttonVariants: Record<ButtonVariant, string> = {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40',
outline:
'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
link: 'text-primary underline-offset-4 hover:underline',
}
export type ButtonVariant = (typeof buttonVariantKeys)[number]
/** Button size keys — keep in sync with `buttonSizes`. */
export const buttonSizeKeys = [
'default',
'xs',
'sm',
'lg',
'icon',
'icon-xs',
'icon-sm',
'icon-lg',
] as const
export const buttonSizes: Record<ButtonSize, string> = {
default: 'h-9 px-4 py-2 has-[>svg]:px-3',
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
icon: 'size-9',
'icon-xs': "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
'icon-sm': 'size-8',
'icon-lg': 'size-10',
}
export type ButtonSize = (typeof buttonSizeKeys)[number]
const buttonBase =
"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
export type ButtonConfig<M> = Readonly<{
onClick?: M
isDisabled?: boolean
type?: 'button' | 'submit' | 'reset'
isAutofocus?: boolean
variant?: ButtonVariant
size?: ButtonSize
className?: string
}>
/** Styled button built on the @foldkit/ui Button helper. */
export const button = <M>(config: ButtonConfig<M>, label: Html | string, h: HtmlBuilder<M>): Html =>
FoldkitButton.view<M>(
{
onClick: config.onClick,
isDisabled: config.isDisabled,
type: config.type,
isAutofocus: config.isAutofocus,
toView: (attributes) =>
h.button(
[
...attributes.button,
h.Class(
cn(
buttonBase,
buttonVariants[config.variant ?? 'default'],
buttonSizes[config.size ?? 'default'],
config.className,
),
),
h.DataAttribute('slot', 'button'),
h.DataAttribute('variant', config.variant ?? 'default'),
h.DataAttribute('size', config.size ?? 'default'),
],
[label],
),
},
h,
)