Meet Frontfriend.Your brand. Your rules. Your agents, connected.Explore what’s new
v3 documentation

Button

A versatile button component with multiple variants, sizes, icon support, and loading states.

Use these guides for Frontfriend v3. To migrate, contact the Frontfriend team .
Read as Markdown

Button

Displays a button or a component that looks like a button with extensive customization options.

Installation

Download the Button component using the Frontfriend CLI:

npx frontfriend download button

This will install the Button component along with its dependencies (Icon, Spinner components).

Usage

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

export default function Demo() {
  return <Button variant="main">Click me</Button>;
}
```



```vue
<script setup>
import { Button } from '@/components/ui/button';
</script>

<template>
  <Button variant="main">Click me</Button>
</template>
```

API Reference

Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `variant` | `'main' \| 'destructive' \| 'tertiary' \| 'secondary' \| 'ghost' \| 'ghostmain' \| 'link' \| 'linkDestructive'` | `'main'` | Visual style variant |
| `size` | `'default' \| 'sm' \| 'lg'` | `'default'` | Button size |
| `icon` | `keyof typeof icons` | `undefined` | Icon to display |
| `iconPosition` | `'prefix' \| 'suffix' \| 'default'` | `'default'` | Icon position. Use 'default' for icon-only buttons |
| `loading` | `boolean` | `false` | Show loading spinner |
| `loadingText` | `string` | `undefined` | Custom text to show during loading |
| `asChild` | `boolean` | `false` | Compose with child element using Radix Slot |
| ...rest | `React.ButtonHTMLAttributes` | - | All standard button HTML attributes |



| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `variant` | `'main' \| 'destructive' \| 'tertiary' \| 'secondary' \| 'ghost' \| 'ghostmain' \| 'link' \| 'linkDestructive'` | `'main'` | Visual style variant |
| `size` | `'default' \| 'sm' \| 'lg'` | `'default'` | Button size |
| `icon` | `IconName` | `undefined` | Icon to display |
| `iconPosition` | `'prefix' \| 'suffix' \| 'default'` | `'default'` | Icon position. Use 'default' for icon-only buttons |
| `loading` | `boolean` | `false` | Show loading spinner |
| `loadingText` | `string` | `undefined` | Custom text to show during loading |
| `as` | `keyof HTMLElementTagNameMap` | `'button'` | HTML element to render as |
| `asChild` | `boolean` | `false` | Compose with child element using Radix Primitive |
| `label` | `string` | `'Button'` | Fallback text when no slot content provided |
| `class` | `string` | `undefined` | Additional CSS classes |
| ...rest | `ButtonHTMLAttributes` | - | All standard button HTML attributes |

Examples

Variants

The Button component supports 8 different visual variants to match different use cases.

Historical interactive example. See the current Button reference for live examples.

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

export default function VariantsDemo() {
  return (
    <div className="flex flex-wrap gap-4">
      <Button variant="main">Main</Button>
      <Button variant="secondary">Secondary</Button>
      <Button variant="tertiary">Tertiary</Button>
      <Button variant="destructive">Destructive</Button>
      <Button variant="ghost">Ghost</Button>
      <Button variant="ghostmain">Ghost Main</Button>
      <Button variant="link">Link</Button>
      <Button variant="linkDestructive">Link Destructive</Button>
    </div>
  );
}
```



```vue
<script setup>
import { Button } from '@/components/ui/button';
</script>

<template>
  <div class="flex flex-wrap gap-4">
    <Button variant="main">Main</Button>
    <Button variant="secondary">Secondary</Button>
    <Button variant="tertiary">Tertiary</Button>
    <Button variant="destructive">Destructive</Button>
    <Button variant="ghost">Ghost</Button>
    <Button variant="ghostmain">Ghost Main</Button>
    <Button variant="link">Link</Button>
    <Button variant="linkDestructive">Link Destructive</Button>
  </div>
</template>
```

Sizes

Three size options are available: small, default, and large.

Historical interactive example. See the current Button reference for live examples.

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

export default function SizesDemo() {
  return (
    <div className="flex items-center gap-4">
      <Button size="sm">Small</Button>
      <Button size="default">Default</Button>
      <Button size="lg">Large</Button>
    </div>
  );
}
```



```vue
<script setup>
import { Button } from '@/components/ui/button';
</script>

<template>
  <div class="flex items-center gap-4">
    <Button size="sm">Small</Button>
    <Button size="default">Default</Button>
    <Button size="lg">Large</Button>
  </div>
</template>
```

With Icons

Buttons can display icons in three different positions: prefix (before text), suffix (after text), or default (icon-only).

Icon Prefix

Historical interactive example. See the current Button reference for live examples.

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

export default function IconPrefixDemo() {
  return (
    <Button icon="Mail" iconPosition="prefix">
      Send Email
    </Button>
  );
}
```



```vue
<script setup>
import { Button } from '@/components/ui/button';
</script>

<template>
  <Button icon="Mail" iconPosition="prefix">
    Send Email
  </Button>
</template>
```

Icon Suffix

Historical interactive example. See the current Button reference for live examples.

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

export default function IconSuffixDemo() {
  return (
    <Button icon="ChevronRight" iconPosition="suffix">
      Next
    </Button>
  );
}
```



```vue
<script setup>
import { Button } from '@/components/ui/button';
</script>

<template>
  <Button icon="ChevronRight" iconPosition="suffix">
    Next
  </Button>
</template>
```

Icon Only

Historical interactive example. See the current Button reference for live examples.

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

export default function IconOnlyDemo() {
  return (
    <div className="flex gap-2">
      <Button icon="Settings" iconPosition="default" aria-label="Settings" />
      <Button icon="Search" iconPosition="default" aria-label="Search" />
      <Button icon="Heart" iconPosition="default" aria-label="Favorite" />
    </div>
  );
}
```



```vue
<script setup>
import { Button } from '@/components/ui/button';
</script>

<template>
  <div class="flex gap-2">
    <Button icon="Settings" iconPosition="default" aria-label="Settings" />
    <Button icon="Search" iconPosition="default" aria-label="Search" />
    <Button icon="Heart" iconPosition="default" aria-label="Favorite" />
  </div>
</template>
```

Loading State

Display a loading spinner and optional loading text while an async action is in progress.

Historical interactive example. See the current Button reference for live examples.

```tsx
import { Button } from '@/components/ui/button';
import { useState } from 'react';

export default function LoadingDemo() {
  const [isLoading, setIsLoading] = useState(false);

  const handleClick = async () => {
    setIsLoading(true);
    await new Promise(resolve => setTimeout(resolve, 2000));
    setIsLoading(false);
  };

  return (
    <div className="flex gap-4">
      <Button loading={isLoading} onClick={handleClick}>
        Submit
      </Button>
      <Button
        loading={isLoading}
        loadingText="Saving..."
        onClick={handleClick}
      >
        Save Changes
      </Button>
    </div>
  );
}
```



```vue
<script setup>
import { Button } from '@/components/ui/button';
import { ref } from 'vue';

const isLoading = ref(false);

const handleClick = async () => {
  isLoading.value = true;
  await new Promise(resolve => setTimeout(resolve, 2000));
  isLoading.value = false;
};
</script>

<template>
  <div class="flex gap-4">
    <Button :loading="isLoading" @click="handleClick">
      Submit
    </Button>
    <Button
      :loading="isLoading"
      loadingText="Saving..."
      @click="handleClick"
    >
      Save Changes
    </Button>
  </div>
</template>
```

As Child (Composition)

Use the asChild prop to compose the Button with other components, such as links.

Historical interactive example. See the current Button reference for live examples.

```tsx
import { Button } from '@/components/ui/button';
import Link from 'next/link';

export default function AsChildDemo() {
  return (
    <Button asChild>
      <Link href="/dashboard">
        Go to Dashboard
      </Link>
    </Button>
  );
}
```



```vue
<script setup>
import { Button } from '@/components/ui/button';
import { RouterLink } from 'vue-router';
</script>

<template>
  <Button asChild>
    <RouterLink to="/dashboard">
      Go to Dashboard
    </RouterLink>
  </Button>
</template>
```

Disabled State

Historical interactive example. See the current Button reference for live examples.

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

export default function DisabledDemo() {
  return (
    <div className="flex gap-4">
      <Button disabled>Disabled Button</Button>
      <Button variant="destructive" disabled>
        Can't Delete
      </Button>
    </div>
  );
}
```



```vue
<script setup>
import { Button } from '@/components/ui/button';
</script>

<template>
  <div class="flex gap-4">
    <Button disabled>Disabled Button</Button>
    <Button variant="destructive" :disabled="true">
      Can't Delete
    </Button>
  </div>
</template>
```

Design Tokens

The Button component uses design tokens from @frontfriend/tailwind for consistent theming. All colors, spacing, and typography are controlled through the design token system, ensuring consistency across your application.

Accessibility

  • Button includes proper ARIA attributes
  • Icon-only buttons should include an aria-label for screen readers
  • Disabled state is properly communicated to assistive technologies
  • Loading state maintains button focus and communicates status
  • Icon: Used internally for displaying icons
  • Spinner: Used for loading states