Sawana Huang Avatar

Sawana Huang

Button

Thu Sep 11 2025

Customizable React button component built on shadcn/ui with 8 color variants, multiple sizes, and TypeScript support for modern web applications

Install - Shadcn UI

我们默认使用 shadcn 作为我们的基础组件库,并且根据 shadcn 组件库的组件增改我们的样式。

另外,我们会使用的是 shadcn ui button 组件的副本,所以可能与您的版本有所不同,可以关注我们是“如何在原有基础上修改”的。

Button - shadcn/ui

Default Button

更改 shadcn ui 的默认 button。

import * as React from "react";import { Slot } from "@radix-ui/react-slot";import { cva, type VariantProps } from "class-variance-authority";import { cn } from "@/lib/utils";const defaultButtonVariants = cva(  "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",  {    variants: {      variant: {        default:          "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",        destructive:          "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",        outline:          "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",        secondary:          "bg-secondary text-secondary-foreground shadow-xs 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",        // Primary 颜色变体        "primary-blue":          "bg-blue-600 text-white shadow-xs hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600",        "primary-green":          "bg-green-600 text-white shadow-xs hover:bg-green-700 dark:bg-green-500 dark:hover:bg-green-600",        "primary-purple":          "bg-purple-600 text-white shadow-xs hover:bg-purple-700 dark:bg-purple-500 dark:hover:bg-purple-600",        "primary-red":          "bg-red-600 text-white shadow-xs hover:bg-red-700 dark:bg-red-500 dark:hover:bg-red-600",        // Secondary 颜色变体 - 与 secondary 变体相似的设计哲学(颜色深度上调一阶)        "secondary-blue":          "bg-blue-100 text-blue-900 shadow-xs hover:bg-blue-200 dark:bg-blue-900 dark:text-blue-100 dark:hover:bg-blue-800",        "secondary-green":          "bg-green-100 text-green-900 shadow-xs hover:bg-green-200 dark:bg-green-900 dark:text-green-100 dark:hover:bg-green-800",        "secondary-purple":          "bg-purple-100 text-purple-900 shadow-xs hover:bg-purple-200 dark:bg-purple-900 dark:text-purple-100 dark:hover:bg-purple-800",        "secondary-red":          "bg-red-100 text-red-900 shadow-xs hover:bg-red-200 dark:bg-red-900 dark:text-red-100 dark:hover:bg-red-800",      },      size: {        default: "h-9 px-4 py-2 has-[>svg]:px-3",        sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",        lg: "h-10 rounded-md px-6 has-[>svg]:px-4",        icon: "size-9",        // customize size        huge: "h-12 rounded-md has-[>svg]:px-6 px-8 text-lg",        badge: "h-8 text-sm px-4 gap-1 rounded-full",      },    },    defaultVariants: {      variant: "default",      size: "default",    },  },);function DefaultButton({  className,  variant,  size,  asChild = false,  ...props}: React.ComponentProps<"button"> &  VariantProps<typeof defaultButtonVariants> & {    asChild?: boolean;  }) {  const Comp = asChild ? Slot : "button";  return (    <Comp      data-slot="button"      className={cn(defaultButtonVariants({ variant, size, className }))}      {...props}    />  );}export { DefaultButton, defaultButtonVariants };

大小

颜色

Primary color

Secondary color

Rainbow Button

来自 M.bin Salman 发布于 21st.dev

"use client";import * as React from "react";import { Slot } from "@radix-ui/react-slot";import { cva, type VariantProps } from "class-variance-authority";import { cn } from "@/lib/utils";const rainbowButtonVariants = cva(  "rainbow-border relative isolate inline-flex items-center justify-center gap-2 rounded-xl font-black bg-black text-white border-none  transition-all duration-200 disabled:opacity-50 disabled:pointer-events-none ",  {    variants: {      size: {        default: "py-2 px-4 text-sm",        sm: "h-8 py-2 px-3 text-xs",        lg: "h-12 px-6 py-3 text-base",        icon: "size-10",        huge: "h-12 text-lg rounded-md has-[>svg]:px-6 px-8",      },    },    defaultVariants: {      size: "default",    },  },);type RainbowButtonProps = React.ComponentProps<"button"> &  VariantProps<typeof rainbowButtonVariants> & {    asChild?: boolean;  };function RainbowBordersButton({  className,  size,  asChild = false,  children,  ...props}: RainbowButtonProps) {  const Comp = asChild ? Slot : "button";  return (    <Comp className={cn(rainbowButtonVariants({ size, className }))} {...props}>      <span className="relative z-10">{children}</span>      {/* 按钮颜色 */}      <span className="absolute inset-0 z-[1] rounded-xl bg-black" />      {/* 彩色 border 和背景 */}      <style jsx>{`        .rainbow-border::before,        .rainbow-border::after {          content: "";          position: absolute;          left: -2px;          top: -2px;          border-radius: 12px;          background: linear-gradient(            45deg,            #fb0094,            #0000ff,            #00ff00,            #ffff00,            #ff0000,            #fb0094,            #0000ff,            #00ff00,            #ffff00,            #ff0000          );          background-size: 400%;          width: calc(100% + 4px);          height: calc(100% + 4px);          z-index: -1;          animation: rainbow 20s linear infinite;        }        .rainbow-border::after {          filter: blur(50px);        }        @keyframes rainbow {          0% {            background-position: 0 0;          }          50% {            background-position: 400% 0;          }          100% {            background-position: 0 0;          }        }      `}</style>    </Comp>  );}export { RainbowBordersButton, rainbowButtonVariants };

Shining Button

Based on Animata

基础(Button)

作为链接(asChild)

Go To Something Secondary Link

尺寸(size)

强调/状态 & withIcon

import * as React from "react";import { Slot } from "@radix-ui/react-slot";import { cva, type VariantProps } from "class-variance-authority";import { ArrowLeft, ArrowRight } from "lucide-react";import { cn } from "@/lib/utils";// Shiny button with a hover sweep "shine" implemented via a pseudo-element.// Button-first API with optional `asChild` Slot (shadcn style).const shiningButtonVariants = cva(  [    // Layout and interaction container    "group relative inline-flex items-center justify-center gap-4 overflow-hidden rounded-lg font-bold",    // Focus/ring/a11y    "transition-colors focus-visible:outline-none",    // Ensure icons inside don't steal pointer events    "[&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-5 [&_svg]:shrink-0",    // Shine sweep overlay via ::before so it works with `asChild`    "before:pointer-events-none before:absolute before:inset-y-0 before:-left-16 before:h-full before:w-12 before:rotate-[30deg]",    "before:bg-gradient-to-r before:from-transparent before:via-white/50 before:to-transparent",    "before:transition-[left] before:duration-700 before:ease-out before:will-change-[left] hover:before:left-[calc(100%+1rem)]",    "before:scale-y-150 before:z-10",  ].join(" "),  {    variants: {      emphasis: {        none: "",        border: "border-2 border-transparent",        ring: "ring-2 ring-transparent",      },      variant: {        default: "bg-primary text-primary-foreground hover:bg-primary/90",        secondary:          "bg-secondary text-secondary-foreground hover:bg-secondary/85",      },      size: {        sm: "px-4 py-2 text-sm",        default: "px-6 py-4 text-lg",        lg: "px-8 py-5 text-xl",      },    },    compoundVariants: [      {        variant: "default",        emphasis: "border",        class: "hover:border-primary/80 focus-visible:border-primary",      },      {        variant: "secondary",        emphasis: "border",        class: "hover:border-secondary/80 focus-visible:border-secondary",      },      {        variant: "default",        emphasis: "ring",        class: " hover:ring-primary/60  focus-visible:ring-primary/80",      },      {        variant: "secondary",        emphasis: "ring",        class: "hover:ring-secondary/60 focus-visible:ring-secondary/80",      },    ],    defaultVariants: {      variant: "default",      emphasis: "none",      size: "default",    },  },);export interface ShiningButtonProps  extends React.ComponentProps<"button">,    VariantProps<typeof shiningButtonVariants> {  asChild?: boolean; // use Slot to style a child element (e.g., next/link)}export default function ShiningButton({  className,  variant = "default",  size,  emphasis,  asChild = false,  children,  ...props}: ShiningButtonProps) {  const Comp = asChild ? Slot : "button"; // shadcn-style Slot  return (    <Comp      data-slot="button"      className={cn(        shiningButtonVariants({          variant,          size,          emphasis,          className,        }),      )}      {...props}    >      {children}    </Comp>  );}// Pre-built arrow components for composition with ShiningButton.// Use them inside children to avoid breaking asChild single-child constraint on the root.export function ShiningButtonLeftArrow({  className,  ...rest}: React.ComponentProps<"svg">) {  return (    <ArrowLeft      aria-hidden="true"      className={cn(        "pointer-events-none size-5 shrink-0 transition-transform duration-300 group-hover:-translate-x-2 group-hover:scale-110",        className,      )}      {...rest}    />  );}export function ShiningButtonRightArrow({  className,  ...rest}: React.ComponentProps<"svg">) {  return (    <ArrowRight      aria-hidden="true"      className={cn(        "pointer-events-none size-5 shrink-0 transition-transform duration-300 group-hover:translate-x-2 group-hover:scale-110",        className,      )}      {...rest}    />  );}