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

Button

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

This documentation describes an earlier Frontfriend version. Commands and APIs may differ from v4. Read the current v4 documentation →
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