VisorVisor
ComponentsGeneral

Button

A versatile button component with multiple variants and sizes. Built with CVA and CSS Modules — copy it into your project and own it completely.

npx visor add button

Variants

Sizes

Disabled State

Installation

npx visor add button

This copies two files into your project:

  • components/ui/button/button.tsx — the component
  • components/ui/button/button.module.css — the styles

Usage

import { Button } from '@/components/ui/button/button';

export default function Example() {
  return <Button>Click me</Button>;
}

API Reference

ButtonProps

PropTypeDefaultDescription
variant'default' | 'secondary' | 'outline' | 'ghost' | 'destructive''default'Visual style variant of the button.
size'sm' | 'md' | 'lg''md'Size of the button, affecting height and padding.
asChildbooleanfalseMerge props onto the immediate child element instead of rendering a <button>.
disabledbooleanfalseDisables the button and applies reduced-opacity styling.
classNamestringAdditional CSS class names to merge onto the element.
onClickReact.MouseEventHandler<HTMLButtonElement>Click event handler.
...propsReact.ButtonHTMLAttributes<HTMLButtonElement>All standard HTML button attributes are forwarded.

The component also accepts all standard <button> HTML attributes.

Source Files

After running npx visor add button, you'll have:

button.tsx

import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import styles from "./button.module.css";

const buttonVariants = cva(styles.base, {
  variants: {
    variant: {
      default: styles.variantDefault,
      secondary: styles.variantSecondary,
      outline: styles.variantOutline,
      ghost: styles.variantGhost,
      destructive: styles.variantDestructive,
    },
    size: {
      sm: styles.sizeSm,
      md: styles.sizeMd,
      lg: styles.sizeLg,
    },
  },
  defaultVariants: {
    variant: "default",
    size: "md",
  },
});

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, ...props }, ref) => {
    return (
      <button
        className={cn(buttonVariants({ variant, size }), className)}
        ref={ref}
        {...props}
      />
    );
  }
);
Button.displayName = "Button";

export { Button, buttonVariants };

button.module.css

All styles use CSS custom properties from @loworbitstudio/visor-core, so the button automatically adapts to your active theme.

Customization

After copying the component, you own it completely. Common customizations:

// Add a loading state
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, loading, children, ...props }, ref) => {
    return (
      <button
        className={cn(buttonVariants({ variant, size }), className)}
        disabled={loading || props.disabled}
        ref={ref}
        {...props}
      >
        {loading ? <Spinner size={16} /> : children}
      </button>
    );
  }
);