Skip to main content

Overview

Noteverse includes specialized button components that extend the base Button component with additional visual effects and loading states.

RainbowButton

An eye-catching button with animated rainbow gradient border and glow effect.

Import

import { RainbowButton } from '@/components/ui/rainbow-button'

Props

children
React.ReactNode
required
Button content
...props
React.ButtonHTMLAttributes<HTMLButtonElement>
All standard HTML button attributes (onClick, disabled, type, etc.)

Basic Usage

import { RainbowButton } from '@/components/ui/rainbow-button'

export default function Example() {
  return (
    <RainbowButton onClick={() => console.log('Clicked!')}>
      Click Me
    </RainbowButton>
  )
}

Visual Design

The RainbowButton features several layers of visual effects:
Animated rainbow gradient border using CSS variables:
background: linear-gradient(
  90deg,
  hsl(var(--color-1)),
  hsl(var(--color-5)),
  hsl(var(--color-3)),
  hsl(var(--color-4)),
  hsl(var(--color-2))
)
background-size: 200%
animation: rainbow

CSS Variables

Define these color variables in your CSS:
:root {
  --color-1: 0 100% 63%;
  --color-2: 270 100% 63%;
  --color-3: 210 100% 63%;
  --color-4: 195 100% 63%;
  --color-5: 90 100% 63%;
}

@keyframes rainbow {
  0% {
    background-position: 0% 50%;
  }
  50% {
    background-position: 100% 50%;
  }
  100% {
    background-position: 0% 50%;
  }
}

.animate-rainbow {
  animation: rainbow 8s linear infinite;
}

Styling

The button uses Tailwind classes with the cn utility:
className={cn(
  'h-11 px-8 py-2',
  'inline-flex items-center justify-center',
  'rounded-xl font-medium',
  'transition-colors',
  'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
  'disabled:pointer-events-none disabled:opacity-50',
  'relative group animate-rainbow cursor-pointer',
  'border-0 bg-[length:200%]',
  'text-primary-foreground',
  '[background-clip:padding-box,border-box,border-box]',
  '[background-origin:border-box]',
  '[border:calc(0.08*1rem)_solid_transparent]',
  // ... gradient backgrounds
)}

TypeScript Interface

interface RainbowButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement> {}

LoadingButton

A button that displays a loading spinner and custom text during async operations.

Import

import LoadingButton from '@/components/ui/loading-button'

Props

loading
boolean
required
Whether to show loading state
children
React.ReactNode
required
Button content when not loading
...props
ButtonProps
All props from the base Button component (variant, size, etc.)

Basic Usage

import LoadingButton from '@/components/ui/loading-button'
import { useState } from 'react'

export default function Form() {
  const [isLoading, setIsLoading] = useState(false)
  
  const handleSubmit = async () => {
    setIsLoading(true)
    try {
      await submitForm()
    } finally {
      setIsLoading(false)
    }
  }
  
  return (
    <LoadingButton 
      loading={isLoading} 
      onClick={handleSubmit}
    >
      Submit
    </LoadingButton>
  )
}

Loading State

When loading={true}, the button displays:
<Loader2 className="mr-2 h-4 w-4 animate-spin" /> Creating...
The loading text “Creating…” is hardcoded. To customize it:
Modify the component to accept a loadingText prop:
type LoadingButtonProps = {
  loading: boolean
  loadingText?: string
} & ButtonProps

export default function LoadingButton({
  children,
  loading,
  loadingText = 'Loading...',
  ...props
}: LoadingButtonProps) {
  return (
    <Button {...props} disabled={props.disabled || loading}>
      {loading ? (
        <>
          <Loader2 className="mr-2 h-4 w-4 animate-spin" />
          {loadingText}
        </>
      ) : (
        children
      )}
    </Button>
  )
}

Disabled State

The button is automatically disabled during loading:
<Button {...props} disabled={props.disabled || loading}>
This prevents multiple submissions and provides clear feedback.

TypeScript Interface

import { ButtonProps } from './button'

type LoadingButtonProps = {
  loading: boolean
} & ButtonProps

Customization

Replace the Loader2 icon:
import { Spinner } from 'lucide-react'

{loading ? (
  <>
    <Spinner className="mr-2 h-4 w-4 animate-spin" />
    Creating...
  </>
) : (
  children
)}
Show only the spinner without text:
{loading ? (
  <Loader2 className="h-4 w-4 animate-spin" />
) : (
  children
)}
Adjust spinner size based on button size:
const spinnerSize = props.size === 'sm' ? 'h-3 w-3' : 'h-4 w-4'

<Loader2 className={`mr-2 animate-spin ${spinnerSize}`} />

Base Button Component

Both specialized buttons extend the base Button component from shadcn/ui:
import { Button } from '@/components/ui/button'

<Button variant="default" size="default">
  Click me
</Button>

Button Variants

  • default: Primary button style
  • destructive: For dangerous actions
  • outline: Outlined button
  • secondary: Secondary button style
  • ghost: Transparent button
  • link: Link-styled button

Button Sizes

  • default: Standard size
  • sm: Small
  • lg: Large
  • icon: Square button for icons

Best Practices

The rainbow effect is attention-grabbing. Use it for primary CTAs only:
// Good: Primary CTA
<RainbowButton onClick={startTrial}>
  Start Free Trial
</RainbowButton>

// Not ideal: Multiple rainbow buttons
<div>
  <RainbowButton>Save</RainbowButton>
  <RainbowButton>Cancel</RainbowButton>
</div>
Prevent multiple submissions and provide feedback:
// Good
<LoadingButton loading={isSubmitting} onClick={submit}>
  Submit
</LoadingButton>

// Bad: No loading state
<Button onClick={submit}>Submit</Button>
Make it clear what’s happening:
// Good
<LoadingButton loading={isSaving} loadingText="Saving changes...">
  Save
</LoadingButton>

// Not helpful
<LoadingButton loading={isSaving} loadingText="Loading...">
  Save
</LoadingButton>

Accessibility

Both components support standard button accessibility features:
// Disabled state
<LoadingButton loading={false} disabled>
  Disabled
</LoadingButton>

// ARIA label
<RainbowButton aria-label="Get started with Noteverse">
  Get Started
</RainbowButton>

// Keyboard navigation
<LoadingButton 
  loading={false}
  onKeyDown={(e) => {
    if (e.key === 'Enter') {
      handleSubmit()
    }
  }}
>
  Submit
</LoadingButton>

Next Steps

Forms

Learn about form input components

Dialogs

Explore modal and drawer components

Build docs developers (and LLMs) love