Button

Buttons allow the user to take actions or make choices.

Installation

Install the component Button in your project using the CLI.

Button.tsx

pnpm dlx behsseui@latest add Button

Install the component manually.

Create a ui folder at the root of the project, then a component folder inside it, and finally a Button.tsx file in that folder.

Copy and paste the following code into your project.

ui/components/Button.tsx

1import type { ButtonHTMLAttributes, ReactNode } from "react";
2import { Slot, type AsChildProps } from "./internals/Slot";
3import { cva, type VariantProps } from "class-variance-authority"
4import { cn } from "@/lib/utils";
5
6const buttonVariants = cva(
7 "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none shrink-0 cursor-pointer",
8 {
9 variants: {
10 variant: {
11 default: "bg-primary text-primary-foreground hover:bg-primary/85",
12 secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/40",
13 outline: "border bg-background hover:bg-accent hover:text-accent-foreground",
14 destructive: "bg-destructive text-primary-foreground hover:bg-destructive/85",
15 ghost: "hover:bg-accent hover:text-accent-foreground",
16 text: "text-primary/85 hover:text-primary"
17 },
18 size: {
19 default: "h-9 px-4 py-2 has-[>svg]:px-3",
20 small: "h-8 px-3 py-1.5 has-[>svg]:px-2",
21 large: "h-10 px-5 py-2.5 has-[>svg]:px-4",
22 xl: "h-12 px-6 py-3 has-[>svg]:px-5"
23 },
24 iconSize: {
25 default: "h-9 aspect-square",
26 small: "h-8 aspect-square",
27 large: "h-10 aspect-square",
28 xl: "h-12 aspect-square"
29 },
30 },
31 defaultVariants: {
32 variant: "default"
33 }
34 }
35);
36
37type Props = AsChildProps<
38 {
39 children: ReactNode;
40 className?: string;
41 } & VariantProps<typeof buttonVariants>,
42 ButtonHTMLAttributes<HTMLButtonElement>
43>;
44
45export function Button({
46 variant = "default",
47 size,
48 iconSize,
49 children,
50 className,
51 asChild,
52 ...props
53}: Props) {
54 const Component = asChild ? Slot : "button";
55 return (
56 <Component className={cn(buttonVariants({variant, size: iconSize ? undefined : (size ?? "default"), iconSize: iconSize ?? undefined, className}))} {...props}>
57 {children}
58 </Component>
59 );
60}

For Button.tsx to work, create an internals folder inside ui/component, then create a Slot.tsx file in it with the following code.

ui/components/internals/Slot.tsx

1import {
2 Children,
3 cloneElement,
4 isValidElement,
5 type HTMLAttributes,
6 type PropsWithChildren,
7 type ReactElement,
8 type ReactNode,
9} from "react";
10import { twMerge } from "tailwind-merge";
11
12// Type utilitaire pour simplifier le typage des composant supportant asChild
13export type AsChildProps<BaseProps, SecondaryProps = {}> =
14 | ({ asChild: true; children: ReactNode } & BaseProps)
15 | ({ asChild?: false; children?: ReactNode } & BaseProps & SecondaryProps);
16
17export function Slot(props: PropsWithChildren<HTMLAttributes<HTMLElement>>) {
18 const children = Children.toArray(props.children).filter((c) =>
19 isValidElement(c)
20 );
21
22 if (children.length !== 1) {
23 throw new Error("Slot must have exactly one child element");
24 }
25
26 const child = children[0] as ReactElement<HTMLAttributes<HTMLElement>>;
27
28 return cloneElement(child, {
29 ...props,
30 ...child.props,
31 style:
32 props.style || child.props.style
33 ? {
34 ...props.style,
35 ...child.props.style,
36 }
37 : undefined,
38 className:
39 props.className || child.props.className
40 ? twMerge(props.className, child.props.className)
41 : undefined,
42 });
43}

Usages

Different variants and use cases for the Button component.

Default

Default.tsx

<Button>Default</Button>

Secondary

Secondary.tsx

<Button variant="secondary">Secondary</Button>

Outline

Outline.tsx

<Button variant="outline">Outline</Button>

Destructive

Destructive.tsx

<Button variant="destructive">Destructive</Button>

Ghost

Ghost.tsx

<Button variant="ghost">Ghost</Button>

Text

Text.tsx

<Button variant="text">Text</Button>

Small

A smaller button with reduced padding.

Small.tsx

<Button size="small">Small</Button>

Large

A larger button with increased padding.

Large.tsx

<Button size="large">Large</Button>

XL

An extra large button.

XL.tsx

<Button size="xl">Extra Large</Button>

Icon

A square button for icons.

Icon.tsx

<Button iconSize="default">
  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14"/><path d="M12 5v14"/></svg>
</Button>

Icon Small

A small square button for icons.

Icon Small.tsx

<Button iconSize="small">
  <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14"/><path d="M12 5v14"/></svg>
</Button>

Icon Large

A large square button for icons.

Icon Large.tsx

<Button iconSize="large">
  <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14"/><path d="M12 5v14"/></svg>
</Button>

Icon XL

An extra large square button for icons.

Icon XL.tsx

<Button iconSize="xl">
  <svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M5 12h14"/><path d="M12 5v14"/></svg>
</Button>

AsChild

AsChild.tsx

<Button asChild>
  <a href="https://ui.behsse.com">Link</a>
</Button>